From f66497b0239a8b887485c1e8432f316737154552 Mon Sep 17 00:00:00 2001 From: Johannes Weiss Date: Fri, 7 Nov 2025 23:57:38 +0000 Subject: [PATCH] do not waste time when metrics delegate is not set --- .../MultiThreadedEventLoopGroup.swift | 4 +- Sources/NIOPosix/SelectableEventLoop.swift | 56 ++++++++++++------- 2 files changed, 39 insertions(+), 21 deletions(-) diff --git a/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift b/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift index 63a0f55265..c4de5874ad 100644 --- a/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift +++ b/Sources/NIOPosix/MultiThreadedEventLoopGroup.swift @@ -107,9 +107,9 @@ public final class MultiThreadedEventLoopGroup: EventLoopGroup { canBeShutdownIndividually: canEventLoopBeShutdownIndividually, metricsDelegate: metricsDelegate ) - threadSpecificEventLoop.currentValue = loop + Self.threadSpecificEventLoop.currentValue = loop defer { - threadSpecificEventLoop.currentValue = nil + Self.threadSpecificEventLoop.currentValue = nil } callback(loop) try loop.run() diff --git a/Sources/NIOPosix/SelectableEventLoop.swift b/Sources/NIOPosix/SelectableEventLoop.swift index e696d505a2..3f12a251e1 100644 --- a/Sources/NIOPosix/SelectableEventLoop.swift +++ b/Sources/NIOPosix/SelectableEventLoop.swift @@ -187,9 +187,7 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { private let promiseCreationStoreLock = NIOLock() private var _promiseCreationStore: [_NIOEventLoopFutureIdentifier: (file: StaticString, line: UInt)] = [:] - private let metricsDelegate: (any NIOEventLoopMetricsDelegate)? - - private var lastTickEndTime: NIODeadline + private var metricsDelegateState: MetricsDelegateState? @usableFromInline internal func _promiseCreated(futureIdentifier: _NIOEventLoopFutureIdentifier, file: StaticString, line: UInt) { @@ -261,7 +259,9 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { canBeShutdownIndividually: Bool, metricsDelegate: NIOEventLoopMetricsDelegate? ) { - self.metricsDelegate = metricsDelegate + self.metricsDelegateState = metricsDelegate.map { delegate in + MetricsDelegateState(metricsDelegate: delegate, lastTickEndime: .now()) + } self._uniqueID = uniqueID self._parentGroup = parentGroup self._selector = selector @@ -270,7 +270,6 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { self.msgBufferPool = Pool(maxSize: 16) self.tasksCopy.reserveCapacity(Self.tasksCopyBatchSize) self.canBeShutdownIndividually = canBeShutdownIndividually - self.lastTickEndTime = .now() // note: We are creating a reference cycle here that we'll break when shutting the SelectableEventLoop down. // note: We have to create the promise and complete it because otherwise we'll hit a loop in `makeSucceededFuture`. This is // fairly dumb, but it's the only option we have. @@ -747,21 +746,35 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { return nextDeadline } - private func runLoop(selfIdentifier: ObjectIdentifier) -> NIODeadline? { - let tickStartTime: NIODeadline = .now() - let sleepTime: TimeAmount = tickStartTime - self.lastTickEndTime + private func runOneLoopTick(selfIdentifier: ObjectIdentifier) -> NIODeadline? { + let tickStartInfo: + ( + metricsDelegate: any NIOEventLoopMetricsDelegate, + tickStartTime: NIODeadline, + sleepTime: TimeAmount + )? = self.metricsDelegateState.map { metricsDelegateState in + let tickStartTime = NIODeadline.now() // Potentially expensive, only if delegate set. + return ( + metricsDelegate: metricsDelegateState.metricsDelegate, + tickStartTime: tickStartTime, + sleepTime: tickStartTime - metricsDelegateState.lastTickEndime + ) + } + var tasksProcessedInTick = 0 defer { - let tickEndTime: NIODeadline = .now() - let tickInfo = NIOEventLoopTickInfo( - eventLoopID: selfIdentifier, - numberOfTasks: tasksProcessedInTick, - sleepTime: sleepTime, - startTime: tickStartTime, - endTime: tickEndTime - ) - self.metricsDelegate?.processedTick(info: tickInfo) - self.lastTickEndTime = tickEndTime + if let tickStartInfo = tickStartInfo { + let tickEndTime = NIODeadline.now() // Potentially expensive, only if delegate set. + let tickInfo = NIOEventLoopTickInfo( + eventLoopID: selfIdentifier, + numberOfTasks: tasksProcessedInTick, + sleepTime: tickStartInfo.sleepTime, + startTime: tickStartInfo.tickStartTime, + endTime: tickEndTime + ) + tickStartInfo.metricsDelegate.processedTick(info: tickInfo) + self.metricsDelegateState?.lastTickEndime = tickEndTime + } } while true { let nextReadyDeadline = self._tasksLock.withLock { () -> NIODeadline? in @@ -905,7 +918,7 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { } } } - nextReadyDeadline = runLoop(selfIdentifier: selfIdentifier) + nextReadyDeadline = self.runOneLoopTick(selfIdentifier: selfIdentifier) } // This EventLoop was closed so also close the underlying selector. @@ -1036,6 +1049,11 @@ internal final class SelectableEventLoop: EventLoop, @unchecked Sendable { } } +struct MetricsDelegateState { + var metricsDelegate: any NIOEventLoopMetricsDelegate + var lastTickEndime: NIODeadline +} + extension SelectableEventLoop: CustomStringConvertible, CustomDebugStringConvertible { @usableFromInline var description: String {