Skip to content

Commit 6b00972

Browse files
authored
Add make*Future methods to isolated event loop (#3152)
Motivation: Creating a completed future of a non-sendable value when on the current event loop is currently a little tedious. It requires you to create a promise, succeed it using the isolated view, or fail it using the non-isolated view. This is more tedious than it needs to be. Modifications: - Add `make{Succeeded,Failed,Completed}Future` methods to the isolated event loop. These don't require the `Success` value to be `Sendable`. Result: It's easier to create completed futures of non sendable values from an isolated event loop.
1 parent 6ff70b5 commit 6b00972

File tree

2 files changed

+117
-0
lines changed

2 files changed

+117
-0
lines changed

Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -125,6 +125,62 @@ public struct NIOIsolatedEventLoop {
125125
return .init(promise: promise, cancellationTask: { scheduled.cancel() })
126126
}
127127

128+
/// Creates and returns a new `EventLoopFuture` that is already marked as success. Notifications
129+
/// will be done using this `EventLoop` as execution `NIOThread`.
130+
///
131+
/// - Parameters:
132+
/// - value: the value that is used by the `EventLoopFuture`.
133+
/// - Returns: a succeeded `EventLoopFuture`.
134+
@inlinable
135+
@available(*, noasync)
136+
public func makeSucceededFuture<Success>(_ value: Success) -> EventLoopFuture<Success> {
137+
let promise = self._wrapped.makePromise(of: Success.self)
138+
promise.assumeIsolatedUnsafeUnchecked().succeed(value)
139+
return promise.futureResult
140+
}
141+
142+
/// Creates and returns a new `EventLoopFuture` that is already marked as failed. Notifications
143+
/// will be done using this `EventLoop`.
144+
///
145+
/// - Parameters:
146+
/// - error: the `Error` that is used by the `EventLoopFuture`.
147+
/// - Returns: a failed `EventLoopFuture`.
148+
@inlinable
149+
@available(*, noasync)
150+
public func makeFailedFuture<Success>(_ error: Error) -> EventLoopFuture<Success> {
151+
let promise = self._wrapped.makePromise(of: Success.self)
152+
promise.fail(error)
153+
return promise.futureResult
154+
}
155+
156+
/// Creates and returns a new `EventLoopFuture` that is marked as succeeded or failed with the
157+
/// value held by `result`.
158+
///
159+
/// - Parameters:
160+
/// - result: The value that is used by the `EventLoopFuture`
161+
/// - Returns: A completed `EventLoopFuture`.
162+
@inlinable
163+
@available(*, noasync)
164+
public func makeCompletedFuture<Success>(_ result: Result<Success, Error>) -> EventLoopFuture<Success> {
165+
let promise = self._wrapped.makePromise(of: Success.self)
166+
promise.assumeIsolatedUnsafeUnchecked().completeWith(result)
167+
return promise.futureResult
168+
}
169+
170+
/// Creates and returns a new `EventLoopFuture` that is marked as succeeded or failed with the
171+
/// value returned by `body`.
172+
///
173+
/// - Parameters:
174+
/// - body: The function that is used to complete the `EventLoopFuture`
175+
/// - Returns: A completed `EventLoopFuture`.
176+
@inlinable
177+
@available(*, noasync)
178+
public func makeCompletedFuture<Success>(
179+
withResultOf body: () throws -> Success
180+
) -> EventLoopFuture<Success> {
181+
self.makeCompletedFuture(Result(catching: body))
182+
}
183+
128184
/// Returns the wrapped event loop.
129185
@inlinable
130186
public func nonisolated() -> any EventLoop {
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2025 Apple Inc. and the SwiftNIO project authors
6+
// Licensed under Apache License v2.0
7+
//
8+
// See LICENSE.txt for license information
9+
// See CONTRIBUTORS.txt for the list of SwiftNIO project authors
10+
//
11+
// SPDX-License-Identifier: Apache-2.0
12+
//
13+
//===----------------------------------------------------------------------===//
14+
15+
import NIOCore
16+
import NIOEmbedded
17+
import XCTest
18+
19+
final class NIOIsolatedEventLoopTests: XCTestCase {
20+
func withEmbeddedEventLoop(_ body: (EmbeddedEventLoop) throws -> Void) rethrows {
21+
let loop = EmbeddedEventLoop()
22+
defer { try! loop.syncShutdownGracefully() }
23+
try body(loop)
24+
}
25+
26+
func testMakeSucceededFuture() throws {
27+
try self.withEmbeddedEventLoop { loop in
28+
let future = loop.assumeIsolated().makeSucceededFuture(NotSendable())
29+
XCTAssertNoThrow(try future.map { _ in }.wait())
30+
}
31+
}
32+
33+
func testMakeFailedFuture() throws {
34+
try self.withEmbeddedEventLoop { loop in
35+
let future: EventLoopFuture<NotSendable> = loop.assumeIsolated().makeFailedFuture(
36+
ChannelError.alreadyClosed
37+
)
38+
XCTAssertThrowsError(try future.map { _ in }.wait())
39+
}
40+
}
41+
42+
func testMakeCompletedFuture() throws {
43+
try self.withEmbeddedEventLoop { loop in
44+
let result = Result<NotSendable, any Error>.success(NotSendable())
45+
let future: EventLoopFuture<NotSendable> = loop.assumeIsolated().makeCompletedFuture(result)
46+
XCTAssertNoThrow(try future.map { _ in }.wait())
47+
}
48+
}
49+
50+
func testMakeCompletedFutureWithResultOfClosure() throws {
51+
try self.withEmbeddedEventLoop { loop in
52+
let future = loop.assumeIsolated().makeCompletedFuture { NotSendable() }
53+
XCTAssertNoThrow(try future.map { _ in }.wait())
54+
}
55+
}
56+
}
57+
58+
private struct NotSendable {}
59+
60+
@available(*, unavailable)
61+
extension NotSendable: Sendable {}

0 commit comments

Comments
 (0)