Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix EventLoopFuture and EventLoopPromise under strict concurrency checking #2654

Merged
merged 18 commits into from
Oct 18, 2024
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions Sources/NIOCore/AsyncAwaitSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,9 @@ extension EventLoopFuture {
/// This function can be used to bridge an `EventLoopFuture` into the `async` world. Ie. if you're in an `async`
/// function and want to get the result of this future.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@preconcurrency
@inlinable
public func get() async throws -> Value {
public func get() async throws -> Value where Value: Sendable {
return try await withUnsafeThrowingContinuation { (cont: UnsafeContinuation<UnsafeTransfer<Value>, Error>) in
self.whenComplete { result in
switch result {
Expand Down Expand Up @@ -60,8 +61,11 @@ extension EventLoopPromise {
/// - returns: A `Task` which was created to `await` the `body`.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@discardableResult
@preconcurrency
@inlinable
public func completeWithTask(_ body: @escaping @Sendable () async throws -> Value) -> Task<Void, Never> {
public func completeWithTask(
_ body: @escaping @Sendable () async throws -> Value
weissi marked this conversation as resolved.
Show resolved Hide resolved
) -> Task<Void, Never> where Value: Sendable {
Task {
do {
let value = try await body()
Expand Down Expand Up @@ -338,8 +342,11 @@ struct AsyncSequenceFromIterator<AsyncIterator: AsyncIteratorProtocol>: AsyncSeq

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
extension EventLoop {
@preconcurrency
@inlinable
public func makeFutureWithTask<Return>(_ body: @Sendable @escaping () async throws -> Return) -> EventLoopFuture<Return> {
public func makeFutureWithTask<Return: Sendable>(
_ body: @Sendable @escaping () async throws -> Return
) -> EventLoopFuture<Return> {
let promise = self.makePromise(of: Return.self)
promise.completeWithTask(body)
return promise.futureResult
Expand Down
5 changes: 3 additions & 2 deletions Sources/NIOCore/DispatchQueue+WithFuture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,10 @@ extension DispatchQueue {
/// - callbackMayBlock: The scheduled callback for the IO / task.
/// - returns a new `EventLoopFuture<ReturnType>` with value returned by the `block` parameter.
@inlinable
public func asyncWithFuture<NewValue>(
@preconcurrency
public func asyncWithFuture<NewValue: Sendable>(
eventLoop: EventLoop,
_ callbackMayBlock: @escaping () throws -> NewValue
_ callbackMayBlock: @escaping @Sendable () throws -> NewValue
) -> EventLoopFuture<NewValue> {
let promise = eventLoop.makePromise(of: NewValue.self)

Expand Down
50 changes: 10 additions & 40 deletions Sources/NIOCore/EventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -713,12 +713,6 @@ extension EventLoop {
@inlinable
@preconcurrency
public func submit<T>(_ task: @escaping @Sendable () throws -> T) -> EventLoopFuture<T> {
_submit(task)
}
@usableFromInline typealias SubmitCallback<T> = @Sendable () throws -> T

@inlinable
func _submit<T>(_ task: @escaping SubmitCallback<T>) -> EventLoopFuture<T> {
let promise: EventLoopPromise<T> = makePromise(file: #fileID, line: #line)

self.execute {
Expand All @@ -742,18 +736,15 @@ extension EventLoop {
/// - returns: An `EventLoopFuture` identical to the `EventLoopFuture` returned from `task`.
@inlinable
@preconcurrency
public func flatSubmit<T>(_ task: @escaping @Sendable () -> EventLoopFuture<T>) -> EventLoopFuture<T> {
self._flatSubmit(task)
}
@usableFromInline typealias FlatSubmitCallback<T> = @Sendable () -> EventLoopFuture<T>

@inlinable
func _flatSubmit<T>(_ task: @escaping FlatSubmitCallback<T>) -> EventLoopFuture<T> {
public func flatSubmit<T: Sendable>(_ task: @escaping @Sendable () -> EventLoopFuture<T>) -> EventLoopFuture<T> {
self.submit(task).flatMap { $0 }
}

/// Schedule a `task` that is executed by this `EventLoop` at the given time.
///
/// - Note: The `T` must be `Sendable` since the isolation domains of the event loop future returned from `task` and
/// this event loop might differ.
///
/// - parameters:
/// - task: The asynchronous task to run. As with everything that runs on the `EventLoop`, it must not block.
/// - returns: A `Scheduled` object which may be used to cancel the task if it has not yet run, or to wait
Expand All @@ -763,23 +754,11 @@ extension EventLoop {
@discardableResult
@inlinable
@preconcurrency
public func flatScheduleTask<T>(
public func flatScheduleTask<T: Sendable>(
deadline: NIODeadline,
file: StaticString = #fileID,
line: UInt = #line,
_ task: @escaping @Sendable () throws -> EventLoopFuture<T>
) -> Scheduled<T> {
self._flatScheduleTask(deadline: deadline, file: file, line: line, task)
}
@usableFromInline typealias FlatScheduleTaskDeadlineCallback<T> = () throws -> EventLoopFuture<T>

@discardableResult
@inlinable
func _flatScheduleTask<T>(
deadline: NIODeadline,
file: StaticString,
line: UInt,
_ task: @escaping FlatScheduleTaskDelayCallback<T>
) -> Scheduled<T> {
let promise: EventLoopPromise<T> = self.makePromise(file: file, line: line)
let scheduled = self.scheduleTask(deadline: deadline, task)
Expand All @@ -790,6 +769,9 @@ extension EventLoop {

/// Schedule a `task` that is executed by this `EventLoop` after the given amount of time.
///
/// - Note: The `T` must be `Sendable` since the isolation domains of the event loop future returned from `task` and
/// this event loop might differ.
///
/// - parameters:
/// - task: The asynchronous task to run. As everything that runs on the `EventLoop`, it must not block.
/// - returns: A `Scheduled` object which may be used to cancel the task if it has not yet run, or to wait
Expand All @@ -799,23 +781,11 @@ extension EventLoop {
@discardableResult
@inlinable
@preconcurrency
public func flatScheduleTask<T>(
public func flatScheduleTask<T: Sendable>(
in delay: TimeAmount,
file: StaticString = #fileID,
line: UInt = #line,
_ task: @escaping @Sendable () throws -> EventLoopFuture<T>
) -> Scheduled<T> {
self._flatScheduleTask(in: delay, file: file, line: line, task)
}

@usableFromInline typealias FlatScheduleTaskDelayCallback<T> = @Sendable () throws -> EventLoopFuture<T>

@inlinable
func _flatScheduleTask<T>(
in delay: TimeAmount,
file: StaticString,
line: UInt,
_ task: @escaping FlatScheduleTaskDelayCallback<T>
) -> Scheduled<T> {
let promise: EventLoopPromise<T> = self.makePromise(file: file, line: line)
let scheduled = self.scheduleTask(in: delay, task)
Expand Down Expand Up @@ -951,7 +921,7 @@ extension EventLoop {
notifying promise: EventLoopPromise<Void>?,
_ task: @escaping ScheduleRepeatedTaskCallback
) -> RepeatedTask {
let futureTask: (RepeatedTask) -> EventLoopFuture<Void> = { repeatedTask in
let futureTask: @Sendable (RepeatedTask) -> EventLoopFuture<Void> = { repeatedTask in
do {
try task(repeatedTask)
return self.makeSucceededFuture(())
Expand Down
68 changes: 51 additions & 17 deletions Sources/NIOCore/EventLoopFuture+Deprecated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,65 +13,99 @@
//===----------------------------------------------------------------------===//

extension EventLoopFuture {
@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func flatMap<NewValue>(file: StaticString = #fileID, line: UInt = #line, _ callback: @escaping (Value) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
public func flatMap<NewValue: Sendable>(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Value) -> EventLoopFuture<NewValue>
) -> EventLoopFuture<NewValue> {
return self.flatMap(callback)
}

@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func flatMapThrowing<NewValue>(file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping (Value) throws -> NewValue) -> EventLoopFuture<NewValue> {
public func flatMapThrowing<NewValue: Sendable>(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Value) throws -> NewValue
) -> EventLoopFuture<NewValue> {
return self.flatMapThrowing(callback)
}

@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func flatMapErrorThrowing(file: StaticString = #fileID, line: UInt = #line, _ callback: @escaping (Error) throws -> Value) -> EventLoopFuture<Value> {
public func flatMapErrorThrowing(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Error) throws -> Value
) -> EventLoopFuture<Value> {
return self.flatMapErrorThrowing(callback)
}

@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func map<NewValue>(file: StaticString = #fileID, line: UInt = #line, _ callback: @escaping (Value) -> (NewValue)) -> EventLoopFuture<NewValue> {
public func map<NewValue>(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Value) -> (NewValue)
) -> EventLoopFuture<NewValue> {
return self.map(callback)
}

@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func flatMapError(file: StaticString = #fileID, line: UInt = #line, _ callback: @escaping (Error) -> EventLoopFuture<Value>) -> EventLoopFuture<Value> {
public func flatMapError(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Error) -> EventLoopFuture<Value>
) -> EventLoopFuture<Value> where Value: Sendable {
return self.flatMapError(callback)
}

@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func flatMapResult<NewValue, SomeError: Error>(file: StaticString = #fileID,
line: UInt = #line,
_ body: @escaping (Value) -> Result<NewValue, SomeError>) -> EventLoopFuture<NewValue> {
public func flatMapResult<NewValue, SomeError: Error>(
file: StaticString = #fileID,
line: UInt = #line,
_ body: @escaping @Sendable (Value) -> Result<NewValue, SomeError>
weissi marked this conversation as resolved.
Show resolved Hide resolved
) -> EventLoopFuture<NewValue> {
return self.flatMapResult(body)
}

@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func recover(file: StaticString = #fileID, line: UInt = #line, _ callback: @escaping (Error) -> Value) -> EventLoopFuture<Value> {
public func recover(
file: StaticString = #fileID,
line: UInt = #line,
_ callback: @escaping @Sendable (Error) -> Value
) -> EventLoopFuture<Value> {
return self.recover(callback)
}

@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func and<OtherValue>(_ other: EventLoopFuture<OtherValue>,
file: StaticString = #fileID,
line: UInt = #line) -> EventLoopFuture<(Value, OtherValue)> {
public func and<OtherValue: Sendable>(
_ other: EventLoopFuture<OtherValue>,
file: StaticString = #fileID,
line: UInt = #line
) -> EventLoopFuture<(Value, OtherValue)> {
return self.and(other)
}

@preconcurrency
@inlinable
@available(*, deprecated, message: "Please don't pass file:line:, there's no point.")
public func and<OtherValue>(value: OtherValue,
file: StaticString = #fileID,
line: UInt = #line) -> EventLoopFuture<(Value, OtherValue)> {
public func and<OtherValue: Sendable>(
value: OtherValue,
file: StaticString = #fileID,
line: UInt = #line
) -> EventLoopFuture<(Value, OtherValue)> {
return self.and(value: value)
}
}
13 changes: 9 additions & 4 deletions Sources/NIOCore/EventLoopFuture+WithEventLoop.swift
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ extension EventLoopFuture {
/// - returns: A future that will receive the eventual value.
@inlinable
@preconcurrency
public func flatMapWithEventLoop<NewValue>(_ callback: @escaping @Sendable (Value, EventLoop) -> EventLoopFuture<NewValue>) -> EventLoopFuture<NewValue> {
public func flatMapWithEventLoop<NewValue: Sendable>(
_ callback: @escaping @Sendable (Value, EventLoop) -> EventLoopFuture<NewValue>
) -> EventLoopFuture<NewValue> {
let next = EventLoopPromise<NewValue>.makeUnleakablePromise(eventLoop: self.eventLoop)
self._whenComplete { [eventLoop = self.eventLoop] in
switch self._value! {
Expand Down Expand Up @@ -75,7 +77,9 @@ extension EventLoopFuture {
/// - returns: A future that will receive the recovered value.
@inlinable
@preconcurrency
public func flatMapErrorWithEventLoop(_ callback: @escaping @Sendable (Error, EventLoop) -> EventLoopFuture<Value>) -> EventLoopFuture<Value> {
public func flatMapErrorWithEventLoop(
_ callback: @escaping @Sendable (Error, EventLoop) -> EventLoopFuture<Value>
) -> EventLoopFuture<Value> where Value: Sendable {
let next = EventLoopPromise<Value>.makeUnleakablePromise(eventLoop: self.eventLoop)
self._whenComplete { [eventLoop = self.eventLoop] in
switch self._value! {
Expand Down Expand Up @@ -114,10 +118,11 @@ extension EventLoopFuture {
/// - returns: A new `EventLoopFuture` with the folded value whose callbacks run on `self.eventLoop`.
@inlinable
@preconcurrency
public func foldWithEventLoop<OtherValue>(
public func foldWithEventLoop<OtherValue: Sendable>(
_ futures: [EventLoopFuture<OtherValue>],
with combiningFunction: @escaping @Sendable (Value, OtherValue, EventLoop) -> EventLoopFuture<Value>
) -> EventLoopFuture<Value> {
) -> EventLoopFuture<Value> where Value: Sendable {
@Sendable
func fold0(eventLoop: EventLoop) -> EventLoopFuture<Value> {
let body = futures.reduce(self) { (f1: EventLoopFuture<Value>, f2: EventLoopFuture<OtherValue>) -> EventLoopFuture<Value> in
let newFuture = f1.and(f2).flatMap { (args: (Value, OtherValue)) -> EventLoopFuture<Value> in
Expand Down
Loading