Skip to content

Commit

Permalink
Make EventLoopFuture/Promise conditionally Sendable
Browse files Browse the repository at this point in the history
and `.get()` only available if `Value` is `Sendable`.
Also fix warnings because of that change.
  • Loading branch information
dnadoba committed May 13, 2022
1 parent 4fb931d commit 8aa859e
Show file tree
Hide file tree
Showing 6 changed files with 23 additions and 12 deletions.
2 changes: 1 addition & 1 deletion Sources/NIOAsyncAwaitDemo/AsyncChannelIO.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ import NIOHTTP1

#if canImport(_Concurrency) && compiler(>=5.5.2)
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
struct AsyncChannelIO<Request, Response> {
struct AsyncChannelIO<Request: Sendable, Response: Sendable> {
let channel: Channel

init(_ channel: Channel) {
Expand Down
2 changes: 1 addition & 1 deletion Sources/NIOAsyncAwaitDemo/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
//===----------------------------------------------------------------------===//
import NIOCore
import NIOPosix
import NIOHTTP1
@preconcurrency import NIOHTTP1
import Dispatch

#if canImport(_Concurrency) && compiler(>=5.5.2)
Expand Down
2 changes: 1 addition & 1 deletion Sources/NIOCore/AsyncAwaitSupport+OldXcodes.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#if compiler(>=5.5) && !compiler(>=5.5.2) && canImport(_Concurrency)

extension EventLoopFuture {
extension EventLoopFuture where Value: Sendable {
/// Get the value/error from an `EventLoopFuture` in an `async` context.
///
/// This function can be used to bridge an `EventLoopFuture` into the `async` world. Ie. if you're in an `async`
Expand Down
10 changes: 5 additions & 5 deletions Sources/NIOCore/AsyncAwaitSupport.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@

#if compiler(>=5.5.2) && canImport(_Concurrency)

extension EventLoopFuture {
extension EventLoopFuture where Value: Sendable {
/// Get the value/error from an `EventLoopFuture` in an `async` context.
///
/// This function can be used to bridge an `EventLoopFuture` into the `async` world. Ie. if you're in an `async`
Expand Down Expand Up @@ -98,7 +98,7 @@ extension Channel {
/// Get the value of `option` for this `Channel`.
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@inlinable
public func getOption<Option: ChannelOption>(_ option: Option) async throws -> Option.Value {
public func getOption<Option: ChannelOption>(_ option: Option) async throws -> Option.Value where Option.Value: Sendable {
return try await self.getOption(option).get()
}
}
Expand Down Expand Up @@ -187,20 +187,20 @@ extension ChannelPipeline {
@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, deprecated, message: "ChannelHandlerContext is not Sendable and it is therefore not safe to be used outside of its EventLoop")
public func context(handler: ChannelHandler) async throws -> ChannelHandlerContext {
return try await self.context(handler: handler).get()
return try await self.context(handler: handler).map(UncheckedSendable.init(_:)).get().value
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, deprecated, message: "ChannelHandlerContext is not Sendable and it is therefore not safe to be used outside of its EventLoop")
public func context(name: String) async throws -> ChannelHandlerContext {
return try await self.context(name: name).get()
return try await self.context(name: name).map(UncheckedSendable.init(_:)).get().value
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
@available(*, deprecated, message: "ChannelHandlerContext is not Sendable and it is therefore not safe to be used outside of its EventLoop")
@inlinable
public func context<Handler: ChannelHandler>(handlerType: Handler.Type) async throws -> ChannelHandlerContext {
return try await self.context(handlerType: handlerType).get()
return try await self.context(handlerType: handlerType).map(UncheckedSendable.init(_:)).get().value
}

@available(macOS 10.15, iOS 13, tvOS 13, watchOS 6, *)
Expand Down
7 changes: 3 additions & 4 deletions Sources/NIOCore/EventLoopFuture.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1569,14 +1569,13 @@ public struct _NIOEventLoopFutureIdentifier: Hashable {
}
}

// EventLoopPromise is a reference type, but by its very nature is Sendable.
extension EventLoopPromise: NIOSendable { }

#if swift(>=5.5) && canImport(_Concurrency)
// EventLoopPromise is a reference type, but by its very nature is Sendable.
extension EventLoopPromise: @unchecked Sendable where Value: Sendable { }

// EventLoopFuture is a reference type, but it is Sendable. However, we enforce
// that by way of the guarantees of the EventLoop protocol, so the compiler cannot
// check it.
extension EventLoopFuture: @unchecked NIOSendable { }
extension EventLoopFuture: @unchecked Sendable where Value: Sendable { }

#endif
12 changes: 12 additions & 0 deletions Sources/NIOCore/NIOSendable.swift
Original file line number Diff line number Diff line change
Expand Up @@ -23,3 +23,15 @@ public typealias NIOSendable = Any
#else
public protocol NIOPreconcurrencySendable {}
#endif

#if swift(>=5.5) && canImport(_Concurrency)
@usableFromInline
internal struct UncheckedSendable<Value>: @unchecked Sendable {
@usableFromInline
var value: Value
@inlinable
init(_ value: Value) {
self.value = value
}
}
#endif

0 comments on commit 8aa859e

Please sign in to comment.