Skip to content

Commit 5ac5fad

Browse files
committed
[Concurrency] Provide a Swift interface for custom main and global executors.
Reorganise the Concurrency code so that it's possible to completely implement executors (both main and global) in Swift. Provide API to choose the desired executors for your application. Also make `Task.Sleep` wait using the current executor, not the global executor, and expose APIs on `Clock` to allow for conversion between time bases. rdar://141348916
1 parent b6639e1 commit 5ac5fad

34 files changed

+1890
-390
lines changed

Diff for: include/swift/Runtime/Concurrency.h

+23
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,29 @@ void *swift_task_alloc(size_t size);
119119
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
120120
void swift_task_dealloc(void *ptr);
121121

122+
/// Deallocate memory in a task.
123+
///
124+
/// The pointer provided must be the last pointer allocated on
125+
/// this task that has not yet been deallocated; that is, memory
126+
/// must be allocated and deallocated in a strict stack discipline.
127+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
128+
void swift_task_dealloc(void *ptr);
129+
130+
/// Allocate memory in a job.
131+
///
132+
/// All allocations will be rounded to a multiple of MAX_ALIGNMENT;
133+
/// if the job does not support allocation, this will return NULL.
134+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
135+
void *swift_job_allocate(Job *job, size_t size);
136+
137+
/// Deallocate memory in a job.
138+
///
139+
/// The pointer provided must be the last pointer allocated on
140+
/// this task that has not yet been deallocated; that is, memory
141+
/// must be allocated and deallocated in a strict stack discipline.
142+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift)
143+
void swift_job_deallocate(Job *job, void *ptr);
144+
122145
/// Cancel a task and all of its child tasks.
123146
///
124147
/// This can be called from any thread.

Diff for: stdlib/public/Concurrency/Actor.cpp

+35
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
#include "../CompatibilityOverride/CompatibilityOverride.h"
2626
#include "swift/ABI/Actor.h"
2727
#include "swift/ABI/Task.h"
28+
#include "ExecutorBridge.h"
2829
#include "TaskPrivate.h"
2930
#include "swift/Basic/HeaderFooterLayout.h"
3031
#include "swift/Basic/PriorityQueue.h"
@@ -289,6 +290,40 @@ static SerialExecutorRef swift_task_getCurrentExecutorImpl() {
289290
return result;
290291
}
291292

293+
#pragma clang diagnostic push
294+
#pragma clang diagnostic ignored "-Wreturn-type-c-linkage"
295+
296+
extern "C" SWIFT_CC(swift)
297+
SerialExecutorRef _swift_getActiveExecutor() {
298+
auto currentTracking = ExecutorTrackingInfo::current();
299+
if (currentTracking) {
300+
SerialExecutorRef executor = currentTracking->getActiveExecutor();
301+
// This might be an actor, in which case return nil ("generic")
302+
if (executor.isDefaultActor())
303+
return SerialExecutorRef::generic();
304+
return executor;
305+
}
306+
return swift_getMainExecutor();
307+
}
308+
309+
extern "C" SWIFT_CC(swift)
310+
TaskExecutorRef _swift_getCurrentTaskExecutor() {
311+
auto currentTracking = ExecutorTrackingInfo::current();
312+
if (currentTracking)
313+
return currentTracking->getTaskExecutor();
314+
return TaskExecutorRef::undefined();
315+
}
316+
317+
extern "C" SWIFT_CC(swift)
318+
TaskExecutorRef _swift_getPreferredTaskExecutor() {
319+
AsyncTask *task = swift_task_getCurrent();
320+
if (!task)
321+
return TaskExecutorRef::undefined();
322+
return task->getPreferredTaskExecutor();
323+
}
324+
325+
#pragma clang diagnostic pop
326+
292327
/// Determine whether we are currently executing on the main thread
293328
/// independently of whether we know that we are on the main actor.
294329
static bool isExecutingOnMainThread() {

Diff for: stdlib/public/Concurrency/CFExecutor.swift

+66
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 - 2025 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
#if !$Embedded && (os(macOS) || os(iOS) || os(tvOS) || os(watchOS) || os(visionOS))
14+
15+
import Swift
16+
17+
internal import Darwin
18+
19+
// .. Dynamic binding ..........................................................
20+
21+
enum CoreFoundation {
22+
static let path =
23+
"/System/Library/Frameworks/CoreFoundation.framework/CoreFoundation"
24+
25+
static let handle = dlopen(path, RTLD_NOLOAD)
26+
27+
static var isPresent: Bool { return handle != nil }
28+
29+
static func symbol<T>(_ name: String) -> T {
30+
guard let result = dlsym(handle, name) else {
31+
fatalError("Unable to look up \(name) in CoreFoundation")
32+
}
33+
return unsafeBitCast(result, to: T.self)
34+
}
35+
36+
static let CFRunLoopRun: @convention(c) () -> () =
37+
symbol("CFRunLoopRun")
38+
static let CFRunLoopGetMain: @convention(c) () -> OpaquePointer =
39+
symbol("CFRunLoopGetMain")
40+
static let CFRunLoopStop: @convention(c) (OpaquePointer) -> () =
41+
symbol("CFRunLoopStop")
42+
}
43+
44+
// .. Main Executor ............................................................
45+
46+
@available(SwiftStdlib 6.2, *)
47+
public final class CFMainExecutor: DispatchMainExecutor, @unchecked Sendable {
48+
49+
override public func run() throws {
50+
CoreFoundation.CFRunLoopRun()
51+
}
52+
53+
override public func stop() {
54+
CoreFoundation.CFRunLoopStop(CoreFoundation.CFRunLoopGetMain())
55+
}
56+
57+
}
58+
59+
// .. Task Executor ............................................................
60+
61+
@available(SwiftStdlib 6.2, *)
62+
public final class CFTaskExecutor: DispatchTaskExecutor, @unchecked Sendable {
63+
64+
}
65+
66+
#endif

Diff for: stdlib/public/Concurrency/CMakeLists.txt

+20-16
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,7 @@ set(SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
7777
ConcurrencyHooks.cpp
7878
EmbeddedSupport.cpp
7979
Error.cpp
80+
ExecutorBridge.cpp
8081
ExecutorChecks.cpp
8182
Setup.cpp
8283
Task.cpp
@@ -90,25 +91,13 @@ set(SWIFT_RUNTIME_CONCURRENCY_C_SOURCES
9091
linker-support/magic-symbols-for-install-name.c
9192
)
9293

93-
if("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "dispatch")
94-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
95-
DispatchGlobalExecutor.cpp
96-
)
97-
elseif("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "singlethreaded")
98-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
99-
CooperativeGlobalExecutor.cpp
100-
)
101-
elseif("${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "hooked" OR
102-
"${SWIFT_CONCURRENCY_GLOBAL_EXECUTOR}" STREQUAL "none")
103-
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
104-
NonDispatchGlobalExecutor.cpp
94+
set(SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES
95+
DispatchGlobalExecutor.cpp
10596
)
106-
endif()
10797

10898
set(LLVM_OPTIONAL_SOURCES
10999
CooperativeGlobalExecutor.cpp
110100
DispatchGlobalExecutor.cpp
111-
NonDispatchGlobalExecutor.cpp
112101
)
113102

114103
set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
@@ -117,6 +106,7 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
117106
CheckedContinuation.swift
118107
Errors.swift
119108
Executor.swift
109+
ExecutorBridge.swift
120110
ExecutorAssertions.swift
121111
AsyncCompactMapSequence.swift
122112
AsyncDropFirstSequence.swift
@@ -134,10 +124,10 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
134124
AsyncThrowingFlatMapSequence.swift
135125
AsyncThrowingMapSequence.swift
136126
AsyncThrowingPrefixWhileSequence.swift
127+
ExecutorJob.swift
137128
GlobalActor.swift
138129
GlobalConcurrentExecutor.swift
139130
MainActor.swift
140-
PartialAsyncTask.swift
141131
SourceCompatibilityShims.swift
142132
Task.swift
143133
Task+PriorityEscalation.swift
@@ -174,12 +164,26 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
174164
ContinuousClock.swift
175165
SuspendingClock.swift
176166
TaskSleepDuration.swift
167+
DispatchExecutor.swift
168+
CFExecutor.swift
169+
PlatformExecutorDarwin.swift
170+
PlatformExecutorLinux.swift
171+
PlatformExecutorWindows.swift
177172
)
178173

174+
set(SWIFT_RUNTIME_CONCURRENCY_NONEMBEDDED_SWIFT_SOURCES
175+
ExecutorImpl.swift
176+
)
177+
178+
set(SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES
179+
PlatformExecutorEmbedded.swift
180+
)
181+
179182
add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} IS_STDLIB
180183
${SWIFT_RUNTIME_CONCURRENCY_C_SOURCES}
181184
${SWIFT_RUNTIME_CONCURRENCY_EXECUTOR_SOURCES}
182185
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES}
186+
${SWIFT_RUNTIME_CONCURRENCY_NONEMBEDDED_SWIFT_SOURCES}
183187

184188
SWIFT_MODULE_DEPENDS_ANDROID Android
185189
SWIFT_MODULE_DEPENDS_LINUX Glibc
@@ -273,6 +277,7 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
273277

274278
${SWIFT_RUNTIME_CONCURRENCY_C_SOURCES}
275279
${SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES}
280+
${SWIFT_RUNTIME_CONCURRENCY_EMBEDDED_SWIFT_SOURCES}
276281

277282
SWIFT_COMPILE_FLAGS
278283
${extra_swift_compile_flags} -enable-experimental-feature Embedded
@@ -352,4 +357,3 @@ if(SWIFT_SHOULD_BUILD_EMBEDDED_STDLIB AND SWIFT_SHOULD_BUILD_EMBEDDED_CONCURRENC
352357

353358
add_dependencies(embedded-concurrency "copy_executor_impl_header")
354359
endif()
355-

Diff for: stdlib/public/Concurrency/Clock.swift

+100-8
Original file line numberDiff line numberDiff line change
@@ -11,24 +11,24 @@
1111
//===----------------------------------------------------------------------===//
1212
import Swift
1313

14-
/// A mechanism in which to measure time, and delay work until a given point
14+
/// A mechanism in which to measure time, and delay work until a given point
1515
/// in time.
1616
///
17-
/// Types that conform to the `Clock` protocol define a concept of "now" which
17+
/// Types that conform to the `Clock` protocol define a concept of "now" which
1818
/// is the specific instant in time that property is accessed. Any pair of calls
1919
/// to the `now` property may have a minimum duration between them - this
2020
/// minimum resolution is exposed by the `minimumResolution` property to inform
21-
/// any user of the type the expected granularity of accuracy.
21+
/// any user of the type the expected granularity of accuracy.
2222
///
2323
/// One of the primary uses for clocks is to schedule task sleeping. This method
2424
/// resumes the calling task after a given deadline has been met or passed with
25-
/// a given tolerance value. The tolerance is expected as a leeway around the
26-
/// deadline. The clock may reschedule tasks within the tolerance to ensure
25+
/// a given tolerance value. The tolerance is expected as a leeway around the
26+
/// deadline. The clock may reschedule tasks within the tolerance to ensure
2727
/// efficient execution of resumptions by reducing potential operating system
2828
/// wake-ups. If no tolerance is specified (i.e. nil is passed in) the sleep
29-
/// function is expected to schedule with a default tolerance strategy.
29+
/// function is expected to schedule with a default tolerance strategy.
3030
///
31-
/// For more information about specific clocks see `ContinuousClock` and
31+
/// For more information about specific clocks see `ContinuousClock` and
3232
/// `SuspendingClock`.
3333
@available(SwiftStdlib 5.7, *)
3434
public protocol Clock<Duration>: Sendable {
@@ -41,8 +41,56 @@ public protocol Clock<Duration>: Sendable {
4141
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
4242
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
4343
#endif
44-
}
4544

45+
#if !$Embedded
46+
/// Choose which Dispatch clock to use with DispatchExecutor
47+
///
48+
/// This controls which Dispatch clock is used to enqueue delayed jobs
49+
/// when using this Clock.
50+
@available(SwiftStdlib 6.2, *)
51+
var dispatchClockID: DispatchClockID { get }
52+
#endif
53+
54+
/// Convert a Clock-specific Duration to a Swift Duration
55+
///
56+
/// Some clocks may define `C.Duration` to be something other than a
57+
/// `Swift.Duration`, but that makes it tricky to convert timestamps
58+
/// between clocks, which is something we want to be able to support.
59+
/// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
60+
///
61+
/// Parameters:
62+
///
63+
/// - from duration: The `Duration` to convert
64+
///
65+
/// Returns: A `Swift.Duration` representing the equivalent duration, or
66+
/// `nil` if this function is not supported.
67+
@available(SwiftStdlib 6.2, *)
68+
func convert(from duration: Duration) -> Swift.Duration?
69+
70+
/// Convert a Swift Duration to a Clock-specific Duration
71+
///
72+
/// Parameters:
73+
///
74+
/// - from duration: The `Swift.Duration` to convert.
75+
///
76+
/// Returns: A `Duration` representing the equivalent duration, or
77+
/// `nil` if this function is not supported.
78+
@available(SwiftStdlib 6.2, *)
79+
func convert(from duration: Swift.Duration) -> Duration?
80+
81+
/// Convert an `Instant` from some other clock's `Instant`
82+
///
83+
/// Parameters:
84+
///
85+
/// - instant: The instant to convert.
86+
// - from clock: The clock to convert from.
87+
///
88+
/// Returns: An `Instant` representing the equivalent instant, or
89+
/// `nil` if this function is not supported.
90+
@available(SwiftStdlib 6.2, *)
91+
func convert<OtherClock: Clock>(instant: OtherClock.Instant,
92+
from clock: OtherClock) -> Instant?
93+
}
4694

4795
@available(SwiftStdlib 5.7, *)
4896
extension Clock {
@@ -88,6 +136,50 @@ extension Clock {
88136
}
89137
}
90138

139+
@available(SwiftStdlib 6.2, *)
140+
extension Clock {
141+
#if !$Embedded
142+
public var dispatchClockID: DispatchClockID {
143+
return .suspending
144+
}
145+
#endif
146+
147+
// For compatibility, return `nil` if this is not implemented
148+
public func convert(from duration: Duration) -> Swift.Duration? {
149+
return nil
150+
}
151+
152+
public func convert(from duration: Swift.Duration) -> Duration? {
153+
return nil
154+
}
155+
156+
public func convert<OtherClock: Clock>(instant: OtherClock.Instant,
157+
from clock: OtherClock) -> Instant? {
158+
let ourNow = now
159+
let otherNow = clock.now
160+
let otherDuration = otherNow.duration(to: instant)
161+
162+
// Convert to `Swift.Duration`
163+
guard let duration = clock.convert(from: otherDuration) else {
164+
return nil
165+
}
166+
167+
// Convert from `Swift.Duration`
168+
guard let ourDuration = convert(from: duration) else {
169+
return nil
170+
}
171+
172+
return ourNow.advanced(by: ourDuration)
173+
}
174+
}
175+
176+
@available(SwiftStdlib 6.2, *)
177+
extension Clock where Duration == Swift.Duration {
178+
public func convert(from duration: Duration) -> Duration? {
179+
return duration
180+
}
181+
}
182+
91183
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
92184
@available(SwiftStdlib 5.7, *)
93185
extension Clock {

0 commit comments

Comments
 (0)