From 78469d40ac493413651e7e8b4534cb978d5c0789 Mon Sep 17 00:00:00 2001 From: Wasim Date: Wed, 22 Jul 2026 12:07:02 +0530 Subject: [PATCH 1/3] feat: Add unchecked variants to NIOLoopBound(Box) Fixes #2506 --- Sources/NIOCore/NIOLoopBound.swift | 72 ++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/Sources/NIOCore/NIOLoopBound.swift b/Sources/NIOCore/NIOLoopBound.swift index 40c9721d443..e8f03588acc 100644 --- a/Sources/NIOCore/NIOLoopBound.swift +++ b/Sources/NIOCore/NIOLoopBound.swift @@ -44,6 +44,16 @@ public struct NIOLoopBound: @unchecked Sendable { self._value = value } + /// Initialise a ``NIOLoopBound`` to `value` with the assertion that the code is running on `eventLoop`. + /// + /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. + @inlinable + public init(unchecked value: Value, eventLoop: EventLoop) { + eventLoop.assertInEventLoop() + self.eventLoop = eventLoop + self._value = value + } + /// Access the `value` with the precondition that the code is running on `eventLoop`. /// /// - Note: ``NIOLoopBound`` itself is value-typed, so any writes will only affect the current value. @@ -58,6 +68,22 @@ public struct NIOLoopBound: @unchecked Sendable { yield &self._value } } + + /// Access the `value` with the assertion that the code is running on `eventLoop`. + /// + /// - Note: ``NIOLoopBound`` itself is value-typed, so any writes will only affect the current value. + /// - Note: This is an unchecked variant of ``value`` that only asserts in debug mode. + @inlinable + public var uncheckedValue: Value { + get { + self.eventLoop.assertInEventLoop() + return self._value + } + _modify { + self.eventLoop.assertInEventLoop() + yield &self._value + } + } } /// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and @@ -104,6 +130,15 @@ public final class NIOLoopBoundBox: @unchecked Sendable { self.init(_value: value, uncheckedEventLoop: eventLoop) } + /// Initialise a ``NIOLoopBoundBox`` to `value` with the assertion that the code is running on `eventLoop`. + /// + /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. + @inlinable + public convenience init(unchecked value: Value, eventLoop: EventLoop) { + eventLoop.assertInEventLoop() + self.init(_value: value, uncheckedEventLoop: eventLoop) + } + /// Initialise a ``NIOLoopBoundBox`` that is empty (contains `nil`), this does _not_ require you to be running on `eventLoop`. public static func makeEmptyBox( valueType: NonOptionalValue.Type = NonOptionalValue.self, @@ -174,6 +209,22 @@ public final class NIOLoopBoundBox: @unchecked Sendable { } } + /// Access the `value` with the assertion that the code is running on `eventLoop`. + /// + /// - Note: ``NIOLoopBoundBox`` itself is reference-typed, so any writes will affect anybody sharing this reference. + /// - Note: This is an unchecked variant of ``value`` that only asserts in debug mode. + @inlinable + public var uncheckedValue: Value { + get { + self.eventLoop.assertInEventLoop() + return self._value + } + _modify { + self.eventLoop.assertInEventLoop() + yield &self._value + } + } + /// Safely access and potentially modify the contained value with a closure. /// /// This method provides a way to perform operations on the contained value while ensuring @@ -193,4 +244,25 @@ public final class NIOLoopBoundBox: @unchecked Sendable { self.eventLoop.preconditionInEventLoop() return try handler(&self._value) } + + /// Safely access and potentially modify the contained value with a closure. + /// + /// This method provides a way to perform operations on the contained value while ensuring + /// thread safety through EventLoop verification. The closure receives an `inout` parameter + /// allowing both read and write access to the value. + /// + /// - Parameter handler: A closure that receives an `inout` reference to the contained value. + /// The closure can read from and write to this value. Any modifications made within the + /// closure will be reflected in the box after the closure completes, even if the closure throws. + /// - Returns: The value returned by the `handler` closure. + /// - Note: This method is particularly useful when you need to perform read and write operations + /// on the value because it reduces the on EventLoop checks. + /// - Note: This is an unchecked variant of ``withValue(_:)`` that only asserts in debug mode. + @inlinable + public func withUncheckedValue( + _ handler: (inout Value) throws(Failure) -> Success + ) throws(Failure) -> Success { + self.eventLoop.assertInEventLoop() + return try handler(&self._value) + } } From 22e4a5c1b6a72f9fed7e6eccf6c38a12a6211670 Mon Sep 17 00:00:00 2001 From: Wasim Date: Wed, 22 Jul 2026 14:24:50 +0530 Subject: [PATCH 2/3] refactor: rename unchecked to uncheckedUnsafe --- Sources/NIOCore/NIOLoopBound.swift | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/NIOCore/NIOLoopBound.swift b/Sources/NIOCore/NIOLoopBound.swift index e8f03588acc..546d3de078b 100644 --- a/Sources/NIOCore/NIOLoopBound.swift +++ b/Sources/NIOCore/NIOLoopBound.swift @@ -48,7 +48,7 @@ public struct NIOLoopBound: @unchecked Sendable { /// /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. @inlinable - public init(unchecked value: Value, eventLoop: EventLoop) { + public init(uncheckedUnsafe value: Value, eventLoop: EventLoop) { eventLoop.assertInEventLoop() self.eventLoop = eventLoop self._value = value @@ -74,7 +74,7 @@ public struct NIOLoopBound: @unchecked Sendable { /// - Note: ``NIOLoopBound`` itself is value-typed, so any writes will only affect the current value. /// - Note: This is an unchecked variant of ``value`` that only asserts in debug mode. @inlinable - public var uncheckedValue: Value { + public var uncheckedUnsafeValue: Value { get { self.eventLoop.assertInEventLoop() return self._value @@ -134,7 +134,7 @@ public final class NIOLoopBoundBox: @unchecked Sendable { /// /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. @inlinable - public convenience init(unchecked value: Value, eventLoop: EventLoop) { + public convenience init(uncheckedUnsafe value: Value, eventLoop: EventLoop) { eventLoop.assertInEventLoop() self.init(_value: value, uncheckedEventLoop: eventLoop) } @@ -214,7 +214,7 @@ public final class NIOLoopBoundBox: @unchecked Sendable { /// - Note: ``NIOLoopBoundBox`` itself is reference-typed, so any writes will affect anybody sharing this reference. /// - Note: This is an unchecked variant of ``value`` that only asserts in debug mode. @inlinable - public var uncheckedValue: Value { + public var uncheckedUnsafeValue: Value { get { self.eventLoop.assertInEventLoop() return self._value @@ -259,7 +259,7 @@ public final class NIOLoopBoundBox: @unchecked Sendable { /// on the value because it reduces the on EventLoop checks. /// - Note: This is an unchecked variant of ``withValue(_:)`` that only asserts in debug mode. @inlinable - public func withUncheckedValue( + public func withUncheckedUnsafeValue( _ handler: (inout Value) throws(Failure) -> Success ) throws(Failure) -> Success { self.eventLoop.assertInEventLoop() From 6cc6b8b5bb78e35f010c6df49a293b753a201f41 Mon Sep 17 00:00:00 2001 From: Wasim Date: Thu, 23 Jul 2026 12:07:29 +0530 Subject: [PATCH 3/3] refactor: rename init parameter to uncheckedOnEventLoop for clarity Address review feedback from Lukasa: the init parameter should be 'uncheckedOnEventLoop' to clarify what specifically is unchecked (the event loop assertion), not 'uncheckedUnsafe' which better describes the property/method accessors. --- Sources/NIOCore/NIOLoopBound.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/NIOCore/NIOLoopBound.swift b/Sources/NIOCore/NIOLoopBound.swift index 546d3de078b..d89a9cfe5b6 100644 --- a/Sources/NIOCore/NIOLoopBound.swift +++ b/Sources/NIOCore/NIOLoopBound.swift @@ -48,7 +48,7 @@ public struct NIOLoopBound: @unchecked Sendable { /// /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. @inlinable - public init(uncheckedUnsafe value: Value, eventLoop: EventLoop) { + public init(uncheckedOnEventLoop value: Value, eventLoop: EventLoop) { eventLoop.assertInEventLoop() self.eventLoop = eventLoop self._value = value @@ -134,7 +134,7 @@ public final class NIOLoopBoundBox: @unchecked Sendable { /// /// - Note: This is an unchecked variant of ``init(_:eventLoop:)`` that only asserts in debug mode. @inlinable - public convenience init(uncheckedUnsafe value: Value, eventLoop: EventLoop) { + public convenience init(uncheckedOnEventLoop value: Value, eventLoop: EventLoop) { eventLoop.assertInEventLoop() self.init(_value: value, uncheckedEventLoop: eventLoop) }