Skip to content

Commit c48fcba

Browse files
committed
Add TaskExecutor conformance to EventLoops
1 parent 4612941 commit c48fcba

File tree

4 files changed

+99
-5
lines changed

4 files changed

+99
-5
lines changed

Sources/NIOCore/EventLoop+SerialExecutor.swift

+34-4
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,7 @@ extension NIOSerialEventLoopExecutor {
5151
/// This type is not recommended for use because it risks problems with unowned
5252
/// executors. Adopters are recommended to conform their own event loop
5353
/// types to `SerialExecutor`.
54-
final class NIODefaultSerialEventLoopExecutor {
54+
final class NIODefaultEventLoopExecutor {
5555
@usableFromInline
5656
let loop: EventLoop
5757

@@ -62,7 +62,7 @@ final class NIODefaultSerialEventLoopExecutor {
6262
}
6363

6464
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
65-
extension NIODefaultSerialEventLoopExecutor: SerialExecutor {
65+
extension NIODefaultEventLoopExecutor: SerialExecutor {
6666
@inlinable
6767
public func enqueue(_ job: consuming ExecutorJob) {
6868
self.loop.enqueue(job)
@@ -71,12 +71,42 @@ extension NIODefaultSerialEventLoopExecutor: SerialExecutor {
7171
@inlinable
7272
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
7373
UnownedSerialExecutor(complexEquality: self)
74-
7574
}
7675

7776
@inlinable
78-
public func isSameExclusiveExecutionContext(other: NIODefaultSerialEventLoopExecutor) -> Bool {
77+
public func isSameExclusiveExecutionContext(other: NIODefaultEventLoopExecutor) -> Bool {
7978
self.loop === other.loop
8079
}
8180
}
8281
#endif
82+
83+
#if compiler(>=6.0)
84+
/// A helper protocol that can be mixed in to a NIO ``EventLoop`` to provide an
85+
/// automatic conformance to `TaskExecutor`.
86+
///
87+
/// Implementers of `EventLoop` should consider conforming to this protocol as
88+
/// well on Swift 6.0 and later.
89+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
90+
public protocol NIOTaskEventLoopExecutor: NIOSerialEventLoopExecutor & TaskExecutor { }
91+
92+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
93+
extension NIOTaskEventLoopExecutor {
94+
@inlinable
95+
func asUnownedTaskExecutor() -> UnownedTaskExecutor {
96+
UnownedTaskExecutor(ordinary: self)
97+
}
98+
99+
@inlinable
100+
public var taskExecutor: any TaskExecutor {
101+
self
102+
}
103+
}
104+
105+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
106+
extension NIODefaultEventLoopExecutor: TaskExecutor {
107+
@inlinable
108+
public func asUnownedTaskExecutor() -> UnownedTaskExecutor {
109+
UnownedTaskExecutor(ordinary: self)
110+
}
111+
}
112+
#endif

Sources/NIOCore/EventLoop.swift

+8-1
Original file line numberDiff line numberDiff line change
@@ -383,7 +383,7 @@ extension EventLoop {
383383
#if compiler(>=5.9)
384384
@available(macOS 14.0, iOS 17.0, watchOS 10.0, tvOS 17.0, *)
385385
public var executor: any SerialExecutor {
386-
NIODefaultSerialEventLoopExecutor(self)
386+
NIODefaultEventLoopExecutor(self)
387387
}
388388

389389
@inlinable
@@ -398,6 +398,13 @@ extension EventLoop {
398398
}
399399
}
400400
#endif
401+
402+
#if compiler(>=6.0)
403+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
404+
public var taskExecutor: any TaskExecutor {
405+
NIODefaultEventLoopExecutor(self)
406+
}
407+
#endif
401408
}
402409

403410
extension EventLoopGroup {

Sources/NIOPosix/SelectableEventLoop.swift

+6
Original file line numberDiff line numberDiff line change
@@ -883,3 +883,9 @@ internal func assertExpression(_ body: () -> Bool) {
883883
return body()
884884
}())
885885
}
886+
887+
// MARK: TaskExecutor conformance
888+
#if compiler(>=6.0)
889+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
890+
extension SelectableEventLoop: NIOTaskEventLoopExecutor { }
891+
#endif
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the SwiftNIO open source project
4+
//
5+
// Copyright (c) 2024 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+
import NIOCore
15+
import NIOEmbedded
16+
import NIOPosix
17+
import XCTest
18+
19+
final class TaskExecutorTests: XCTestCase {
20+
@available(macOS 9999.0, iOS 9999.0, watchOS 9999.0, tvOS 9999.0, *)
21+
func testBasicExecutorFitsOnEventLoop_MTELG() async throws {
22+
#if compiler(>=6.0)
23+
let group = MultiThreadedEventLoopGroup(numberOfThreads: 2)
24+
defer {
25+
try! group.syncShutdownGracefully()
26+
}
27+
let loops = Array(group.makeIterator())
28+
await withTaskGroup(of: Void.self) { taskGroup in
29+
taskGroup.addTask(executorPreference: loops[0].taskExecutor) {
30+
loops[0].assertInEventLoop()
31+
loops[1].assertNotInEventLoop()
32+
33+
withUnsafeCurrentTask { task in
34+
// this currently fails on macOS
35+
XCTAssertEqual(task?.unownedTaskExecutor, loops[0].taskExecutor.asUnownedTaskExecutor())
36+
}
37+
}
38+
39+
taskGroup.addTask(executorPreference: loops[1].taskExecutor) {
40+
loops[0].assertNotInEventLoop()
41+
loops[1].assertInEventLoop()
42+
43+
withUnsafeCurrentTask { task in
44+
// this currently fails on macOS
45+
XCTAssertEqual(task?.unownedTaskExecutor, loops[1].taskExecutor.asUnownedTaskExecutor())
46+
}
47+
}
48+
}
49+
#endif
50+
}
51+
}

0 commit comments

Comments
 (0)