Skip to content

[Concurrency] Updates after second SE pitch. #82456

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

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
31 changes: 30 additions & 1 deletion lib/SILGen/SILGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -527,7 +527,36 @@ FuncDecl *SILGenModule::getExit() {
Type SILGenModule::getConfiguredExecutorFactory() {
auto &ctx = getASTContext();

// Look in the main module for a typealias
// First look in the @main struct, if any
NominalTypeDecl *mainType = ctx.MainModule->getMainTypeDecl();
if (mainType) {
SmallVector<ValueDecl *, 1> decls;
auto identifier = ctx.getIdentifier("DefaultExecutorFactory");
mainType->lookupQualified(mainType,
DeclNameRef(identifier),
SourceLoc(),
NL_RemoveNonVisible | NL_RemoveOverridden
| NL_OnlyTypes | NL_ProtocolMembers,
decls);
for (auto decl : decls) {
TypeDecl *typeDecl = dyn_cast<TypeDecl>(decl);
if (typeDecl) {
if (auto *nominalDecl = dyn_cast<NominalTypeDecl>(typeDecl)) {
return nominalDecl->getDeclaredType();
}

if (isa<AssociatedTypeDecl>(typeDecl)) {
// We ignore associatedtype declarations; those with a default will
// turn into a `typealias` instead.
continue;
}

return typeDecl->getDeclaredInterfaceType();
}
}
}

// Failing that, look at the top level
Type factory = ctx.getNamedSwiftType(ctx.MainModule, "DefaultExecutorFactory");

// If we don't find it, fall back to _Concurrency.PlatformExecutorFactory
Expand Down
8 changes: 5 additions & 3 deletions stdlib/public/Concurrency/CFExecutor.swift
Original file line number Diff line number Diff line change
Expand Up @@ -43,8 +43,9 @@ enum CoreFoundation {

// .. Main Executor ............................................................

/// A CFRunLoop-based main executor (Apple platforms only)
@available(StdlibDeploymentTarget 6.2, *)
public final class CFMainExecutor: DispatchMainExecutor, @unchecked Sendable {
final class CFMainExecutor: DispatchMainExecutor, @unchecked Sendable {

override public func run() throws {
CoreFoundation.CFRunLoopRun()
Expand All @@ -58,9 +59,10 @@ public final class CFMainExecutor: DispatchMainExecutor, @unchecked Sendable {

// .. Task Executor ............................................................

/// A `TaskExecutor` to match `CFMainExecutor` (Apple platforms only)
@available(StdlibDeploymentTarget 6.2, *)
public final class CFTaskExecutor: DispatchGlobalTaskExecutor,
@unchecked Sendable {
final class CFTaskExecutor: DispatchGlobalTaskExecutor,
@unchecked Sendable {

}

Expand Down
153 changes: 44 additions & 109 deletions stdlib/public/Concurrency/Clock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -40,51 +40,61 @@ public protocol Clock<Duration>: Sendable {

#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
func sleep(until deadline: Instant, tolerance: Instant.Duration?) async throws
#endif

/// The traits associated with this clock instance.
@available(StdlibDeploymentTarget 6.2, *)
var traits: ClockTraits { get }

/// Convert a Clock-specific Duration to a Swift Duration
/// Run the given job on an unspecified executor at some point
/// after the given instant.
///
/// Some clocks may define `C.Duration` to be something other than a
/// `Swift.Duration`, but that makes it tricky to convert timestamps
/// between clocks, which is something we want to be able to support.
/// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
/// - Parameters:
///
/// Parameters:
/// - job: The job we wish to run
/// - instant: The time at which we would like it to run.
/// - tolerance: The ideal maximum delay we are willing to tolerate.
///
/// - from duration: The `Duration` to convert
///
/// Returns: A `Swift.Duration` representing the equivalent duration, or
/// `nil` if this function is not supported.
@available(StdlibDeploymentTarget 6.2, *)
func convert(from duration: Duration) -> Swift.Duration?
func run(_ job: consuming ExecutorJob,
at instant: Instant, tolerance: Duration?)

/// Convert a Swift Duration to a Clock-specific Duration
/// Enqueue the given job on the specified executor at some point after the
/// given instant.
///
/// The default implementation uses the `run` method to trigger a job that
/// does `executor.enqueue(job)`. If a particular `Clock` knows that the
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minor

Suggested change
/// does `executor.enqueue(job)`. If a particular `Clock` knows that the
/// does `executor.enqueue(job)`. If a particular ``Clock`` knows that the

maybe worth ref like that?

/// executor it has been asked to use is the same one that it will run jobs
/// on, it can short-circuit this behaviour and directly use `run` with
/// the original job.
///
/// Parameters:
/// - Parameters:
///
/// - from duration: The `Swift.Duration` to convert.
/// - job: The job we wish to run
/// - executor: The executor on which we would like it to run.
/// - instant: The time at which we would like it to run.
/// - tolerance: The ideal maximum delay we are willing to tolerate.
///
/// Returns: A `Duration` representing the equivalent duration, or
/// `nil` if this function is not supported.
@available(StdlibDeploymentTarget 6.2, *)
func convert(from duration: Swift.Duration) -> Duration?
func enqueue(_ job: consuming ExecutorJob,
on executor: some Executor,
at instant: Instant, tolerance: Duration?)
#endif
}

/// Convert an `Instant` from some other clock's `Instant`
///
/// Parameters:
///
/// - instant: The instant to convert.
// - from clock: The clock to convert from.
///
/// Returns: An `Instant` representing the equivalent instant, or
/// `nil` if this function is not supported.
extension Clock {
// The default implementation works by creating a trampoline and calling
// the run() method.
@available(StdlibDeploymentTarget 6.2, *)
public func enqueue(_ job: consuming ExecutorJob,
on executor: some Executor,
at instant: Instant, tolerance: Duration?) {
let trampoline = job.createTrampoline(to: executor)
run(trampoline, at: instant, tolerance: tolerance)
}

// Clocks that do not implement run will fatalError() if you try to use
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
// Clocks that do not implement run will fatalError() if you try to use
// Clocks that do not implement this method will fatalError() if you try to use

maybe? "implement run" sounds a bit weird mid-sentence

// them with an executor that does not understand them.
@available(StdlibDeploymentTarget 6.2, *)
func convert<OtherClock: Clock>(instant: OtherClock.Instant,
from clock: OtherClock) -> Instant?
public func run(_ job: consuming ExecutorJob,
at instant: Instant, tolerance: Duration?) {
fatalError("\(Self.self) does not implement run(_:at:tolerance:).")
}
}

@available(StdlibDeploymentTarget 5.7, *)
Expand Down Expand Up @@ -140,44 +150,6 @@ extension Clock {
}
}

@available(StdlibDeploymentTarget 6.2, *)
extension Clock {
// For compatibility, return `nil` if this is not implemented
public func convert(from duration: Duration) -> Swift.Duration? {
return nil
}

public func convert(from duration: Swift.Duration) -> Duration? {
return nil
}

public func convert<OtherClock: Clock>(instant: OtherClock.Instant,
from clock: OtherClock) -> Instant? {
let ourNow = now
let otherNow = clock.now
let otherDuration = otherNow.duration(to: instant)

// Convert to `Swift.Duration`
guard let duration = clock.convert(from: otherDuration) else {
return nil
}

// Convert from `Swift.Duration`
guard let ourDuration = convert(from: duration) else {
return nil
}

return ourNow.advanced(by: ourDuration)
}
}

@available(StdlibDeploymentTarget 6.2, *)
extension Clock where Duration == Swift.Duration {
public func convert(from duration: Duration) -> Duration? {
return duration
}
}

#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@available(StdlibDeploymentTarget 5.7, *)
extension Clock {
Expand All @@ -196,47 +168,10 @@ extension Clock {
}
#endif

/// Represents traits of a particular Clock implementation.
///
/// Clocks may be of a number of different varieties; executors will likely
/// have specific clocks that they can use to schedule jobs, and will
/// therefore need to be able to convert timestamps to an appropriate clock
/// when asked to enqueue a job with a delay or deadline.
///
/// Choosing a clock in general requires the ability to tell which of their
/// clocks best matches the clock that the user is trying to specify a
/// time or delay in. Executors are expected to do this on a best effort
/// basis.
@available(StdlibDeploymentTarget 6.2, *)
public struct ClockTraits: OptionSet {
public let rawValue: UInt32

public init(rawValue: UInt32) {
self.rawValue = rawValue
}

/// Clocks with this trait continue running while the machine is asleep.
public static let continuous = ClockTraits(rawValue: 1 << 0)

/// Indicates that a clock's time will only ever increase.
public static let monotonic = ClockTraits(rawValue: 1 << 1)

/// Clocks with this trait are tied to "wall time".
public static let wallTime = ClockTraits(rawValue: 1 << 2)
}

@available(StdlibDeploymentTarget 6.2, *)
extension Clock {
/// The traits associated with this clock instance.
@available(StdlibDeploymentTarget 6.2, *)
public var traits: ClockTraits {
return []
}
}

enum _ClockID: Int32 {
case continuous = 1
case suspending = 2
case walltime = 3
}

@available(StdlibDeploymentTarget 5.7, *)
Expand Down
39 changes: 33 additions & 6 deletions stdlib/public/Concurrency/ContinuousClock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -100,12 +100,6 @@ extension ContinuousClock: Clock {
)
}

/// The continuous clock is continuous and monotonic
@available(StdlibDeploymentTarget 6.2, *)
public var traits: ClockTraits {
return [.continuous, .monotonic]
}

#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
/// Suspend task execution until a given deadline within a tolerance.
/// If no tolerance is specified then the system may adjust the deadline
Expand Down Expand Up @@ -217,3 +211,36 @@ extension ContinuousClock.Instant: InstantProtocol {
rhs.duration(to: lhs)
}
}

#if !$Embedded
@available(StdlibDeploymentTarget 6.2, *)
extension ContinuousClock {

public func run(_ job: consuming ExecutorJob,
at instant: Instant,
tolerance: Duration?) {
guard let executor = Task<Never,Never>.currentSchedulingExecutor else {
fatalError("no scheduling executor is available")
}

executor.enqueue(job, at: instant,
tolerance: tolerance,
clock: self)
}

public func enqueue(_ job: consuming ExecutorJob,
on executor: some Executor,
at instant: Instant,
tolerance: Duration?) {
if let schedulingExecutor = executor.asSchedulingExecutor {
schedulingExecutor.enqueue(job, at: instant,
tolerance: tolerance,
clock: self)
} else {
let trampoline = job.createTrampoline(to: executor)
run(trampoline, at: instant, tolerance: tolerance)
}
}

}
#endif
Loading