diff --git a/Sources/NIOCore/NIOLoopBound.swift b/Sources/NIOCore/NIOLoopBound.swift index d3eb66c1a0..0df63a8b63 100644 --- a/Sources/NIOCore/NIOLoopBound.swift +++ b/Sources/NIOCore/NIOLoopBound.swift @@ -58,6 +58,56 @@ public struct NIOLoopBound: @unchecked Sendable { yield &self._value } } + + /// Executes the closure on the event loop the value is bound to. + @inlinable + public func execute(_ task: @escaping @Sendable (Value) -> Void) { + if self.eventLoop.inEventLoop { + task(self._value) + } else { + self.eventLoop.execute { + task(self._value) + } + } + } + + /// Executes the closure on the event loop the value is bound to and returns the result in + /// a future. + /// + /// - Parameter task: The task to execute on the event loop with the loop bound value. + /// - Returns: A future containing the result of the task. + @inlinable + public func submit( + _ task: @escaping @Sendable (Value) throws -> Result + ) -> EventLoopFuture { + if self.eventLoop.inEventLoop { + self.eventLoop.makeCompletedFuture { + try task(self._value) + } + } else { + self.eventLoop.submit { + try task(self._value) + } + } + } + + /// Executes the closure on the event loop the value is bound to and returns the result in + /// a future. + /// + /// - Parameter task: The task to execute on the event loop with the loop bound value. + /// - Returns: A future containing the result of the task. + @inlinable + public func flatSubmit( + _ task: @escaping @Sendable (Value) -> EventLoopFuture + ) -> EventLoopFuture { + if self.eventLoop.inEventLoop { + task(self._value) + } else { + self.eventLoop.flatSubmit { + task(self._value) + } + } + } } /// ``NIOLoopBoundBox`` is an always-`Sendable`, reference-typed container allowing you access to ``value`` if and @@ -175,4 +225,55 @@ public final class NIOLoopBoundBox: @unchecked Sendable { yield &self._value } } + + /// Executes the closure on the event loop the value is bound to. + @inlinable + public func execute(_ task: @escaping @Sendable (Value) -> Void) { + if self.eventLoop.inEventLoop { + task(self._value) + } else { + self.eventLoop.execute { + task(self._value) + } + } + } + + /// Executes the closure on the event loop the value is bound to and returns the result in + /// a future. + /// + /// - Parameter task: The task to execute on the event loop with the loop bound value. + /// - Returns: A future containing the result of the task. + @inlinable + public func submit( + _ task: @escaping @Sendable (Value) throws -> Result + ) -> EventLoopFuture { + if self.eventLoop.inEventLoop { + self.eventLoop.makeCompletedFuture { + try task(self._value) + } + } else { + self.eventLoop.submit { + try task(self._value) + } + } + } + + /// Executes the closure on the event loop the value is bound to and returns the result in + /// a future. + /// + /// - Parameter task: The task to execute on the event loop with the loop bound value. + /// - Returns: A future containing the result of the task. + @inlinable + public func flatSubmit( + _ task: @escaping @Sendable (Value) -> EventLoopFuture + ) -> EventLoopFuture { + if self.eventLoop.inEventLoop { + task(self._value) + } else { + self.eventLoop.flatSubmit { + task(self._value) + } + } + } + } diff --git a/Tests/NIOPosixTests/NIOLoopBoundTests.swift b/Tests/NIOPosixTests/NIOLoopBoundTests.swift index e50ccf79c4..730c587e27 100644 --- a/Tests/NIOPosixTests/NIOLoopBoundTests.swift +++ b/Tests/NIOPosixTests/NIOLoopBoundTests.swift @@ -116,6 +116,46 @@ final class NIOLoopBoundTests: XCTestCase { XCTAssertTrue(loopBoundBox.value.mutateInPlace()) } + func testExecute() { + let loopBound = NIOLoopBound(Ref(31), eventLoop: self.loop) + loopBound.execute { ref in + XCTAssertEqual(ref.value, 31) + ref.value = 415 + } + XCTAssertEqual(loopBound.value.value, 415) + + let loopBoundBox = NIOLoopBoundBox(Ref(31), eventLoop: self.loop) + loopBoundBox.execute { ref in + XCTAssertEqual(ref.value, 31) + ref.value = 415 + } + XCTAssertEqual(loopBoundBox.value.value, 415) + } + + func testSubmit() throws { + let loopBound = NIOLoopBound(Ref(42), eventLoop: self.loop) + let value1 = try loopBound.submit { $0.value }.wait() + XCTAssertEqual(value1, 42) + + let loopBoundBox = NIOLoopBoundBox(Ref(42), eventLoop: self.loop) + let value2 = try loopBoundBox.submit { $0.value }.wait() + XCTAssertEqual(value2, 42) + } + + func testFlatSubmit() throws { + let loopBound = NIOLoopBound(Ref(42), eventLoop: self.loop) + let value1 = try loopBound.flatSubmit { [loop] ref in + loop!.makeSucceededFuture(ref.value) + }.wait() + XCTAssertEqual(value1, 42) + + let loopBoundBox = NIOLoopBoundBox(Ref(42), eventLoop: self.loop) + let value2 = try loopBoundBox.flatSubmit { [loop] ref in + loop!.makeSucceededFuture(ref.value) + }.wait() + XCTAssertEqual(value2, 42) + } + // MARK: - Helpers func sendableBlackhole(_ sendableThing: S) {} @@ -134,3 +174,14 @@ final class NotSendable {} @available(*, unavailable) extension NotSendable: Sendable {} + +final class Ref { + var value: Value + + init(_ value: Value) { + self.value = value + } +} + +@available(*, unavailable) +extension Ref: Sendable {}