Skip to content

Commit

Permalink
Unavailable from async
Browse files Browse the repository at this point in the history
  • Loading branch information
Lukasa committed Nov 17, 2024
1 parent 8a67fe3 commit 4714b90
Show file tree
Hide file tree
Showing 2 changed files with 40 additions and 0 deletions.
16 changes: 16 additions & 0 deletions Sources/NIOCore/Docs.docc/loops-futures-concurrency.md
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,14 @@ These isolated views can be obtained by calling ``EventLoopFuture/assumeIsolated
``EventLoop``, use ``EventLoopFuture/hop(to:)`` to move it to your isolation domain
before using these types.

> Warning: ``EventLoopFuture/assumeIsolated()`` and ``EventLoopPromise/assumeIsolated()``
**must not** be called from a Swift concurrency context, either an async method or
from within an actor. This is because it uses runtime checking of the event loop
to confirm that the value is not being sent to a different concurrency domain.

When using an ``EventLoop`` as a custom actor executor, this API can be used to retrieve
a value that region based isolation will then allow to be sent to another domain.

## Interacting with Event Loops on the Event Loop

As with Futures, there are occasionally times where it is necessary to schedule
Expand Down Expand Up @@ -204,3 +212,11 @@ This isolated view can be obtained by calling ``EventLoop/assumeIsolated()``.
runtime ones. This makes it possible to introduce crashes in your code. Please ensure
that you are 100% confident that the isolation domains align. If you are not sure that
the your code is running on the relevant ``EventLoop``, prefer the non-isolated type.

> Warning: ``EventLoop/assumeIsolated()`` **must not** be called from a Swift concurrency
context, either an async method or from within an actor. This is because it uses runtime
checking of the event loop to confirm that the value is not being sent to a different
concurrency domain.

When using an ``EventLoop`` as a custom actor executor, this API can be used to retrieve
a value that region based isolation will then allow to be sent to another domain.
24 changes: 24 additions & 0 deletions Sources/NIOCore/EventLoopFuture+AssumeIsolated.swift
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ public struct NIOIsolatedEventLoop {
}

/// Submit a given task to be executed by the `EventLoop`
@available(*, noasync)
@inlinable
public func execute(_ task: @escaping () -> Void) {
let unsafeTransfer = UnsafeTransfer(task)
Expand All @@ -44,6 +45,7 @@ public struct NIOIsolatedEventLoop {
/// - Parameters:
/// - task: The closure that will be submitted to the `EventLoop` for execution.
/// - Returns: `EventLoopFuture` that is notified once the task was executed.
@available(*, noasync)
@inlinable
public func submit<T>(_ task: @escaping () throws -> T) -> EventLoopFuture<T> {
let unsafeTransfer = UnsafeTransfer(task)
Expand All @@ -62,6 +64,7 @@ public struct NIOIsolatedEventLoop {
///
/// - Note: You can only cancel a task before it has started executing.
@discardableResult
@available(*, noasync)
@inlinable
public func scheduleTask<T>(
deadline: NIODeadline,
Expand All @@ -84,6 +87,7 @@ public struct NIOIsolatedEventLoop {
/// - Note: You can only cancel a task before it has started executing.
/// - Note: The `in` value is clamped to a maximum when running on a Darwin-kernel.
@discardableResult
@available(*, noasync)
@inlinable
public func scheduleTask<T>(
in delay: TimeAmount,
Expand All @@ -110,6 +114,7 @@ public struct NIOIsolatedEventLoop {
///
/// - Note: You can only cancel a task before it has started executing.
@discardableResult
@available(*, noasync)
@inlinable
public func flatScheduleTask<T: Sendable>(
deadline: NIODeadline,
Expand All @@ -133,6 +138,7 @@ public struct NIOIsolatedEventLoop {
extension EventLoop {
/// Assumes the calling context is isolated to the event loop.
@inlinable
@available(*, noasync)
public func assumeIsolated() -> NIOIsolatedEventLoop {
self.preconditionInEventLoop()
return NIOIsolatedEventLoop(self)
Expand Down Expand Up @@ -203,6 +209,7 @@ extension EventLoopFuture {
/// a new `EventLoopFuture`.
/// - Returns: A future that will receive the eventual value.
@inlinable
@available(*, noasync)
public func flatMap<NewValue: Sendable>(
_ callback: @escaping (Value) -> EventLoopFuture<NewValue>
) -> EventLoopFuture<NewValue>.Isolated {
Expand All @@ -227,6 +234,7 @@ extension EventLoopFuture {
/// a new value lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the eventual value.
@inlinable
@available(*, noasync)
public func flatMapThrowing<NewValue>(
_ callback: @escaping (Value) throws -> NewValue
) -> EventLoopFuture<NewValue>.Isolated {
Expand All @@ -251,6 +259,7 @@ extension EventLoopFuture {
/// a new value lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the eventual value or a rethrown error.
@inlinable
@available(*, noasync)
public func flatMapErrorThrowing(
_ callback: @escaping (Error) throws -> Value
) -> EventLoopFuture<Value>.Isolated {
Expand Down Expand Up @@ -287,6 +296,7 @@ extension EventLoopFuture {
/// a new value lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the eventual value.
@inlinable
@available(*, noasync)
public func map<NewValue>(
_ callback: @escaping (Value) -> (NewValue)
) -> EventLoopFuture<NewValue>.Isolated {
Expand All @@ -311,6 +321,7 @@ extension EventLoopFuture {
/// a new value lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the recovered value.
@inlinable
@available(*, noasync)
public func flatMapError(
_ callback: @escaping (Error) -> EventLoopFuture<Value>
) -> EventLoopFuture<Value>.Isolated where Value: Sendable {
Expand All @@ -334,6 +345,7 @@ extension EventLoopFuture {
/// a new value or error lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the eventual value.
@inlinable
@available(*, noasync)
public func flatMapResult<NewValue, SomeError: Error>(
_ body: @escaping (Value) -> Result<NewValue, SomeError>
) -> EventLoopFuture<NewValue>.Isolated {
Expand All @@ -356,6 +368,7 @@ extension EventLoopFuture {
/// a new value lifted into a new `EventLoopFuture`.
/// - Returns: A future that will receive the recovered value.
@inlinable
@available(*, noasync)
public func recover(
_ callback: @escaping (Error) -> Value
) -> EventLoopFuture<Value>.Isolated {
Expand All @@ -376,6 +389,7 @@ extension EventLoopFuture {
/// - Parameters:
/// - callback: The callback that is called with the successful result of the `EventLoopFuture`.
@inlinable
@available(*, noasync)
public func whenSuccess(_ callback: @escaping (Value) -> Void) {
let unsafeTransfer = UnsafeTransfer(callback)
return self._wrapped.whenSuccess {
Expand All @@ -394,6 +408,7 @@ extension EventLoopFuture {
/// - Parameters:
/// - callback: The callback that is called with the failed result of the `EventLoopFuture`.
@inlinable
@available(*, noasync)
public func whenFailure(_ callback: @escaping (Error) -> Void) {
let unsafeTransfer = UnsafeTransfer(callback)
return self._wrapped.whenFailure {
Expand All @@ -407,6 +422,7 @@ extension EventLoopFuture {
/// - Parameters:
/// - callback: The callback that is called when the `EventLoopFuture` is fulfilled.
@inlinable
@available(*, noasync)
public func whenComplete(
_ callback: @escaping (Result<Value, Error>) -> Void
) {
Expand All @@ -423,6 +439,7 @@ extension EventLoopFuture {
/// - callback: the callback that is called when the `EventLoopFuture` is fulfilled.
/// - Returns: the current `EventLoopFuture`
@inlinable
@available(*, noasync)
public func always(
_ callback: @escaping (Result<Value, Error>) -> Void
) -> EventLoopFuture<Value>.Isolated {
Expand All @@ -444,6 +461,7 @@ extension EventLoopFuture {
/// - replacement: the value of the returned `EventLoopFuture` when then resolved future's value is `Optional.some()`.
/// - Returns: an new `EventLoopFuture` with new type parameter `NewValue` and the value passed in the `orReplace` parameter.
@inlinable
@available(*, noasync)
public func unwrap<NewValue>(
orReplace replacement: NewValue
) -> EventLoopFuture<NewValue>.Isolated where Value == NewValue? {
Expand All @@ -470,6 +488,7 @@ extension EventLoopFuture {
/// - Returns: an new `EventLoopFuture` with new type parameter `NewValue` and with the value returned by the closure
/// passed in the `orElse` parameter.
@inlinable
@available(*, noasync)
public func unwrap<NewValue>(
orElse callback: @escaping () -> NewValue
) -> EventLoopFuture<NewValue>.Isolated where Value == NewValue? {
Expand All @@ -493,6 +512,7 @@ extension EventLoopFuture {
/// ``EventLoop`` to which this ``EventLoopFuture`` is bound, will crash
/// if that invariant fails to be met.
@inlinable
@available(*, noasync)
public func assumeIsolated() -> Isolated {
self.eventLoop.preconditionInEventLoop()
return Isolated(_wrapped: self)
Expand All @@ -507,6 +527,7 @@ extension EventLoopFuture {
/// omits the runtime check in release builds. This improves performance, but
/// should only be used sparingly.
@inlinable
@available(*, noasync)
public func assumeIsolatedUnsafeUnchecked() -> Isolated {
self.eventLoop.assertInEventLoop()
return Isolated(_wrapped: self)
Expand Down Expand Up @@ -546,6 +567,7 @@ extension EventLoopPromise {
/// - Parameters:
/// - value: The successful result of the operation.
@inlinable
@available(*, noasync)
public func succeed(_ value: Value) {
self._wrapped._setValue(value: .success(value))._run()
}
Expand All @@ -565,6 +587,7 @@ extension EventLoopPromise {
/// - Parameters:
/// - result: The result which will be used to succeed or fail this promise.
@inlinable
@available(*, noasync)
public func completeWith(_ result: Result<Value, Error>) {
self._wrapped._setValue(value: result)._run()
}
Expand All @@ -581,6 +604,7 @@ extension EventLoopPromise {
/// ``EventLoop`` to which this ``EventLoopPromise`` is bound, will crash
/// if that invariant fails to be met.
@inlinable
@available(*, noasync)
public func assumeIsolated() -> Isolated {
self.futureResult.eventLoop.preconditionInEventLoop()
return Isolated(_wrapped: self)
Expand Down

0 comments on commit 4714b90

Please sign in to comment.