diff --git a/Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift b/Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift index d71fb823faa..74fcdd1bee7 100644 --- a/Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift +++ b/Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift @@ -125,6 +125,62 @@ public struct NIOIsolatedEventLoop { return .init(promise: promise, cancellationTask: { scheduled.cancel() }) } + /// Creates and returns a new `EventLoopFuture` that is already marked as success. Notifications + /// will be done using this `EventLoop` as execution `NIOThread`. + /// + /// - Parameters: + /// - value: the value that is used by the `EventLoopFuture`. + /// - Returns: a succeeded `EventLoopFuture`. + @inlinable + @available(*, noasync) + public func makeSucceededFuture(_ value: Success) -> EventLoopFuture { + let promise = self._wrapped.makePromise(of: Success.self) + promise.assumeIsolatedUnsafeUnchecked().succeed(value) + return promise.futureResult + } + + /// Creates and returns a new `EventLoopFuture` that is already marked as failed. Notifications + /// will be done using this `EventLoop`. + /// + /// - Parameters: + /// - error: the `Error` that is used by the `EventLoopFuture`. + /// - Returns: a failed `EventLoopFuture`. + @inlinable + @available(*, noasync) + public func makeFailedFuture(_ error: Error) -> EventLoopFuture { + let promise = self._wrapped.makePromise(of: Success.self) + promise.fail(error) + return promise.futureResult + } + + /// Creates and returns a new `EventLoopFuture` that is marked as succeeded or failed with the + /// value held by `result`. + /// + /// - Parameters: + /// - result: The value that is used by the `EventLoopFuture` + /// - Returns: A completed `EventLoopFuture`. + @inlinable + @available(*, noasync) + public func makeCompletedFuture(_ result: Result) -> EventLoopFuture { + let promise = self._wrapped.makePromise(of: Success.self) + promise.assumeIsolatedUnsafeUnchecked().completeWith(result) + return promise.futureResult + } + + /// Creates and returns a new `EventLoopFuture` that is marked as succeeded or failed with the + /// value returned by `body`. + /// + /// - Parameters: + /// - body: The function that is used to complete the `EventLoopFuture` + /// - Returns: A completed `EventLoopFuture`. + @inlinable + @available(*, noasync) + public func makeCompletedFuture( + withResultOf body: () throws -> Success + ) -> EventLoopFuture { + self.makeCompletedFuture(Result(catching: body)) + } + /// Returns the wrapped event loop. @inlinable public func nonisolated() -> any EventLoop { diff --git a/Tests/NIOCoreTests/NIOIsolatedEventLoopTests.swift b/Tests/NIOCoreTests/NIOIsolatedEventLoopTests.swift new file mode 100644 index 00000000000..d149815d0fa --- /dev/null +++ b/Tests/NIOCoreTests/NIOIsolatedEventLoopTests.swift @@ -0,0 +1,61 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftNIO open source project +// +// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftNIO project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import NIOCore +import NIOEmbedded +import XCTest + +final class NIOIsolatedEventLoopTests: XCTestCase { + func withEmbeddedEventLoop(_ body: (EmbeddedEventLoop) throws -> Void) rethrows { + let loop = EmbeddedEventLoop() + defer { try! loop.syncShutdownGracefully() } + try body(loop) + } + + func testMakeSucceededFuture() throws { + try self.withEmbeddedEventLoop { loop in + let future = loop.assumeIsolated().makeSucceededFuture(NotSendable()) + XCTAssertNoThrow(try future.map { _ in }.wait()) + } + } + + func testMakeFailedFuture() throws { + try self.withEmbeddedEventLoop { loop in + let future: EventLoopFuture = loop.assumeIsolated().makeFailedFuture( + ChannelError.alreadyClosed + ) + XCTAssertThrowsError(try future.map { _ in }.wait()) + } + } + + func testMakeCompletedFuture() throws { + try self.withEmbeddedEventLoop { loop in + let result = Result.success(NotSendable()) + let future: EventLoopFuture = loop.assumeIsolated().makeCompletedFuture(result) + XCTAssertNoThrow(try future.map { _ in }.wait()) + } + } + + func testMakeCompletedFutureWithResultOfClosure() throws { + try self.withEmbeddedEventLoop { loop in + let future = loop.assumeIsolated().makeCompletedFuture { NotSendable() } + XCTAssertNoThrow(try future.map { _ in }.wait()) + } + } +} + +private struct NotSendable {} + +@available(*, unavailable) +extension NotSendable: Sendable {}