diff --git a/Runtimes/Core/Concurrency/CMakeLists.txt b/Runtimes/Core/Concurrency/CMakeLists.txt index 65f9ca7ea92ed..5fd2ec17e4138 100644 --- a/Runtimes/Core/Concurrency/CMakeLists.txt +++ b/Runtimes/Core/Concurrency/CMakeLists.txt @@ -1,7 +1,7 @@ add_subdirectory(InternalShims) gyb_expand(TaskGroup+addTask.swift.gyb TaskGroup+addTask.swift) -gyb_expand(Task+startSynchronously.swift.gyb Task+startSynchronously.swift) +gyb_expand(Task+immediate.swift.gyb Task+immediate.swift) add_library(swift_Concurrency Actor.cpp @@ -97,7 +97,7 @@ add_library(swift_Concurrency TaskSleep.swift TaskSleepDuration.swift "${CMAKE_CURRENT_BINARY_DIR}/TaskGroup+addTask.swift" - "${CMAKE_CURRENT_BINARY_DIR}/Task+startSynchronously.swift") + "${CMAKE_CURRENT_BINARY_DIR}/Task+immediate.swift") include(${SwiftCore_CONCURRENCY_GLOBAL_EXECUTOR}.cmake) target_compile_definitions(swift_Concurrency PRIVATE diff --git a/include/swift/ABI/Executor.h b/include/swift/ABI/Executor.h index 5523255c05076..4f4fcf8927253 100644 --- a/include/swift/ABI/Executor.h +++ b/include/swift/ABI/Executor.h @@ -77,10 +77,10 @@ class SerialExecutorRef { /// Executor that may need to participate in complex "same context" checks, /// by invoking `isSameExclusiveExecutionContext` when comparing execution contexts. ComplexEquality = 0b01, - /// Mark this executor as the one used by `Task.startSynchronously`, + /// Mark this executor as the one used by `Task.immediate`, /// It cannot participate in switching. // TODO: Perhaps make this a generic "cannot switch" rather than start synchronously specific. - StartSynchronously = 0b10, + Immediate = 0b10, }; static_assert(static_cast(ExecutorKind::Ordinary) == 0); @@ -107,12 +107,12 @@ class SerialExecutorRef { static SerialExecutorRef forSynchronousStart() { auto wtable = reinterpret_cast(nullptr) | - static_cast(ExecutorKind::StartSynchronously); + static_cast(ExecutorKind::Immediate); return SerialExecutorRef(nullptr, wtable); } bool isForSynchronousStart() const { return getIdentity() == nullptr && - getExecutorKind() == ExecutorKind::StartSynchronously; + getExecutorKind() == ExecutorKind::Immediate; } /// Given a pointer to a serial executor and its SerialExecutor diff --git a/include/swift/ABI/MetadataValues.h b/include/swift/ABI/MetadataValues.h index e2e1c45a9b43b..c34633649e5d0 100644 --- a/include/swift/ABI/MetadataValues.h +++ b/include/swift/ABI/MetadataValues.h @@ -2746,7 +2746,7 @@ class TaskCreateFlags : public FlagSet { /// /// Supported starting in Swift 6.1. Task_IsTaskFunctionConsumed = 15, - Task_IsStartSynchronouslyTask = 16, + Task_IsImmediateTask = 16, }; explicit constexpr TaskCreateFlags(size_t bits) : FlagSet(bits) {} @@ -2779,9 +2779,9 @@ class TaskCreateFlags : public FlagSet { FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsTaskFunctionConsumed, isTaskFunctionConsumed, setIsTaskFunctionConsumed) - FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsStartSynchronouslyTask, - isSynchronousStartTask, - setIsSYnchronousStartTask) + FLAGSET_DEFINE_FLAG_ACCESSORS(Task_IsImmediateTask, + isImmediateTask, + setIsImmediateTask) }; /// Flags for schedulable jobs. diff --git a/include/swift/Runtime/Concurrency.h b/include/swift/Runtime/Concurrency.h index 7135c2ae13185..02b932976aa88 100644 --- a/include/swift/Runtime/Concurrency.h +++ b/include/swift/Runtime/Concurrency.h @@ -1063,7 +1063,7 @@ SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) void swift_task_startOnMainActor(AsyncTask* job); SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(swift) -void swift_task_startSynchronously(AsyncTask* job); +void swift_task_immediate(AsyncTask* job, SerialExecutorRef targetExecutor); /// Donate this thread to the global executor until either the /// given condition returns true or we've run out of cooperative diff --git a/stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def b/stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def index 40cd713e4f629..7873272df2663 100644 --- a/stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def +++ b/stdlib/public/CompatibilityOverride/CompatibilityOverrideConcurrency.def @@ -439,9 +439,10 @@ OVERRIDE_TASK(task_startOnMainActor, void, swift::, (AsyncTask *task), (task)) // In ACTOR since we need ExecutorTracking info -OVERRIDE_ACTOR(task_startSynchronously, void, +OVERRIDE_ACTOR(task_immediate, void, SWIFT_EXPORT_FROM(swift_Concurrency), SWIFT_CC(swift), - swift::, (AsyncTask *task), (task)) + swift::, (AsyncTask *task, SerialExecutorRef targetExecutor), + (task, targetExecutor)) #undef OVERRIDE #undef OVERRIDE_ACTOR diff --git a/stdlib/public/Concurrency/Actor.cpp b/stdlib/public/Concurrency/Actor.cpp index 47da9f404dc75..00a0443175048 100644 --- a/stdlib/public/Concurrency/Actor.cpp +++ b/stdlib/public/Concurrency/Actor.cpp @@ -2510,22 +2510,31 @@ static void swift_task_switchImpl(SWIFT_ASYNC_CONTEXT AsyncContext *resumeContex } SWIFT_CC(swift) -static void swift_task_startSynchronouslyImpl(AsyncTask* task) { +static void +swift_task_immediateImpl(AsyncTask *task, + SerialExecutorRef targetExecutor) { swift_retain(task); - - auto currentTracking = ExecutorTrackingInfo::current(); - if (currentTracking) { - auto currentExecutor = currentTracking->getActiveExecutor(); - AsyncTask * originalTask = _swift_task_clearCurrent(); - - swift_job_run(task, currentExecutor); - _swift_task_setCurrent(originalTask); + if (targetExecutor.isGeneric()) { + // If the target is generic, it means that the closure did not specify + // an isolation explicitly. According to the "start synchronously" rules, + // we should therefore ignore the global and just start running on the + // caller immediately. + SerialExecutorRef executor = SerialExecutorRef::forSynchronousStart(); + + auto originalTask = ActiveTask::swap(task); + swift_job_run(task, executor); + _swift_task_setCurrent(originalTask); } else { - auto originalTask = ActiveTask::swap(task); - assert(!originalTask); + assert(swift_task_isCurrentExecutor(targetExecutor) && + "'immediate' must only be invoked when it is correctly in " + "the same isolation already, but wasn't!"); + + // We can run synchronously, we're on the expected executor so running in + // the caller context is going to be in the same context as the requested + // "caller" context. + AsyncTask *originalTask = _swift_task_clearCurrent(); - SerialExecutorRef executor = SerialExecutorRef::forSynchronousStart(); - swift_job_run(task, executor); + swift_job_run(task, targetExecutor); _swift_task_setCurrent(originalTask); } } diff --git a/stdlib/public/Concurrency/CMakeLists.txt b/stdlib/public/Concurrency/CMakeLists.txt index 611c525e6e666..3a85929288672 100644 --- a/stdlib/public/Concurrency/CMakeLists.txt +++ b/stdlib/public/Concurrency/CMakeLists.txt @@ -215,7 +215,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I GYB_SOURCES TaskGroup+addTask.swift.gyb - Task+startSynchronously.swift.gyb + Task+immediate.swift.gyb SWIFT_MODULE_DEPENDS_ANDROID Android SWIFT_MODULE_DEPENDS_LINUX Glibc diff --git a/stdlib/public/Concurrency/Task+startSynchronously.swift.gyb b/stdlib/public/Concurrency/Task+immediate.swift.gyb similarity index 74% rename from stdlib/public/Concurrency/Task+startSynchronously.swift.gyb rename to stdlib/public/Concurrency/Task+immediate.swift.gyb index 30c55da989f40..6381db9c968a5 100644 --- a/stdlib/public/Concurrency/Task+startSynchronously.swift.gyb +++ b/stdlib/public/Concurrency/Task+immediate.swift.gyb @@ -13,7 +13,7 @@ import Swift @_implementationOnly import SwiftConcurrencyInternalShims -// ==== Task.startSynchronously ------------------------------------------------ +// ==== Task.immediate --------------------------------------------------------- % METHOD_VARIANTS = [ % 'THROWING', @@ -32,6 +32,17 @@ import Swift @available(SwiftStdlib 6.2, *) extension Task where Failure == ${FAILURE_TYPE} { + @available(SwiftStdlib 6.2, *) + @available(*, deprecated, renamed: "immediate") + @discardableResult + public static func startSynchronously( + name: String? = nil, + priority: TaskPriority? = nil, + @_implicitSelfCapture _ operation: __owned @isolated(any) @escaping () async throws -> Success + ) -> Task { + immediate(name: name, priority: priority, operation) + } + /// Create and immediately start running a new task in the context of the calling thread/task. /// /// This function _starts_ the created task on the calling context. @@ -56,17 +67,33 @@ extension Task where Failure == ${FAILURE_TYPE} { /// - Returns: A reference to the unstructured task which may be awaited on. @available(SwiftStdlib 6.2, *) @discardableResult - public static func startSynchronously( + public static func immediate( name: String? = nil, priority: TaskPriority? = nil, - @_inheritActorContext @_implicitSelfCapture _ operation: __owned sending @escaping () async throws -> Success + % # NOTE: This closure cannot be 'sending' because we'll trigger ' pattern that the region based isolation checker does not understand how to check' + % # In this case: `func syncOnMyGlobalActor() { Task.immediate { @MyGlobalActor in } }` + @_implicitSelfCapture _ operation: __owned @isolated(any) @escaping () async throws -> Success ) -> Task { + + let builtinSerialExecutor = + unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor + + // Determine if we're switching isolation dynamically. + // If not, we can run the task synchronously and therefore MUST NOT "enqueue" it. + let flagsMustNotCrash: UInt64 = 0 + let canRunSynchronously: Bool = + if let builtinSerialExecutor { + _taskIsCurrentExecutor(executor: builtinSerialExecutor, flags: flagsMustNotCrash) + } else { + true // if there is not target executor, we can run synchronously + } + let flags = taskCreateFlags( priority: priority, isChildTask: false, copyTaskLocals: true, inheritContext: true, - enqueueJob: false, // don't enqueue, we'll run it manually + enqueueJob: !canRunSynchronously, addPendingGroupTaskUnconditionally: false, isDiscardingTask: false, isSynchronousStart: true @@ -79,6 +106,7 @@ extension Task where Failure == ${FAILURE_TYPE} { unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in Builtin.createTask( flags: flags, + initialSerialExecutor: builtinSerialExecutor, taskName: nameBytes.baseAddress!._rawValue, operation: operation).0 } @@ -91,7 +119,9 @@ extension Task where Failure == ${FAILURE_TYPE} { operation: operation).0 } - _startTaskSynchronously(task!) + if canRunSynchronously { + _startTaskImmediately(task!, targetExecutor: builtinSerialExecutor) + } return Task(task!) } } @@ -102,8 +132,8 @@ GROUP_AND_OP_INFO = [ ( 'TaskGroup', [ - 'startTaskSynchronously', - 'startTaskSynchronouslyUnlessCancelled' + 'addImmediateTask', + 'addImmediateTaskUnlessCancelled' ], '', 'ChildTaskResult' @@ -111,8 +141,8 @@ GROUP_AND_OP_INFO = [ ( 'ThrowingTaskGroup', [ - 'startTaskSynchronously', - 'startTaskSynchronouslyUnlessCancelled' + 'addImmediateTask', + 'addImmediateTaskUnlessCancelled' ], 'throws ', 'ChildTaskResult' @@ -120,8 +150,8 @@ GROUP_AND_OP_INFO = [ ( 'DiscardingTaskGroup', [ - 'startTaskSynchronously', - 'startTaskSynchronouslyUnlessCancelled' + 'addImmediateTask', + 'addImmediateTaskUnlessCancelled' ], '', 'Void' @@ -129,8 +159,8 @@ GROUP_AND_OP_INFO = [ ( 'ThrowingDiscardingTaskGroup', [ - 'startTaskSynchronously', - 'startTaskSynchronouslyUnlessCancelled' + 'addImmediateTask', + 'addImmediateTaskUnlessCancelled' ], 'throws ', 'Void' @@ -161,7 +191,7 @@ extension ${GROUP_TYPE} { public func ${METHOD_NAME}( // in ${GROUP_TYPE} name: String? = nil, priority: TaskPriority? = nil, - operation: sending @escaping () async ${THROWS}-> ${RESULT_TYPE} + @_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE} ) { let flags = taskCreateFlags( priority: priority, @@ -174,13 +204,18 @@ extension ${GROUP_TYPE} { isSynchronousStart: true ) + // Create the asynchronous task. + let builtinSerialExecutor = + unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor + // Create the task in this group. let (task, _) = Builtin.createTask( flags: flags, + initialSerialExecutor: builtinSerialExecutor, taskGroup: self._group, operation: operation ) - _startTaskSynchronously(task) + _startTaskImmediately(task, targetExecutor: builtinSerialExecutor) } } % end # METHOD_NAMES @@ -240,5 +275,5 @@ extension Task where Failure == ${FAILURE_TYPE} { @_silgen_name("swift_task_startOnMainActor") internal func _startTaskOnMainActor(_ task: Builtin.NativeObject) -@_silgen_name("swift_task_startSynchronously") -internal func _startTaskSynchronously(_ task: Builtin.NativeObject) +@_silgen_name("swift_task_immediate") +internal func _startTaskImmediately(_ task: Builtin.NativeObject, targetExecutor: Builtin.Executor?) diff --git a/stdlib/public/Concurrency/Task.swift b/stdlib/public/Concurrency/Task.swift index dfb31ffaba6cc..8db587e5863f8 100644 --- a/stdlib/public/Concurrency/Task.swift +++ b/stdlib/public/Concurrency/Task.swift @@ -807,8 +807,7 @@ extension Task where Failure == Error { unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor let (task, _) = Builtin.createTask(flags: flags, - initialSerialExecutor: - builtinSerialExecutor, + initialSerialExecutor: builtinSerialExecutor, operation: operation) self._task = task diff --git a/test/Concurrency/Runtime/startSynchronously.swift b/test/Concurrency/Runtime/startImmediately.swift similarity index 56% rename from test/Concurrency/Runtime/startSynchronously.swift rename to test/Concurrency/Runtime/startImmediately.swift index bbbb9418f9be8..629fa9f20a640 100644 --- a/test/Concurrency/Runtime/startSynchronously.swift +++ b/test/Concurrency/Runtime/startImmediately.swift @@ -1,4 +1,6 @@ -// REQUIRES: rdar145735542 +// FIXME: Marking this disabled since we're reworking the semantics and the test is a bit racy until we do +// REQUIRES: rdar149506152 + // RUN: %empty-directory(%t) // RUN: %target-build-swift -Xfrontend -disable-availability-checking %s %import-libdispatch -swift-version 6 -o %t/a.out // RUN: %target-codesign %t/a.out @@ -62,6 +64,9 @@ extension ThreadID: @unchecked Sendable {} @globalActor actor MyGlobalActor { static let shared: MyGlobalActor = MyGlobalActor() + + @MyGlobalActor + static func test() {} } final class NaiveQueueExecutor: SerialExecutor { @@ -73,14 +78,30 @@ final class NaiveQueueExecutor: SerialExecutor { public func enqueue(_ job: consuming ExecutorJob) { let unowned = UnownedJob(job) - print("NaiveQueueExecutor(\(self.queue.label)) enqueue... [thread:\(getCurrentThreadID())]") + print("NaiveQueueExecutor(\(self.queue.label)) enqueue [thread:\(getCurrentThreadID())]") queue.async { - print("NaiveQueueExecutor(\(self.queue.label)) enqueue: run [thread:\(getCurrentThreadID())]") unowned.runSynchronously(on: self.asUnownedSerialExecutor()) } } } +@globalActor +actor DifferentGlobalActor { + static let queue = DispatchQueue(label: "DifferentGlobalActor-queue") + let executor: NaiveQueueExecutor + nonisolated let unownedExecutor: UnownedSerialExecutor + + init() { + self.executor = NaiveQueueExecutor(queue: DifferentGlobalActor.queue) + self.unownedExecutor = executor.asUnownedSerialExecutor() + } + + static let shared: DifferentGlobalActor = DifferentGlobalActor() + + @DifferentGlobalActor + static func test() {} +} + // Test on all platforms func syncOnMyGlobalActor() -> [Task] { MyGlobalActor.shared.preconditionIsolated("Should be executing on the global actor here") @@ -88,24 +109,67 @@ func syncOnMyGlobalActor() -> [Task] { // This task must be guaranteed to happen AFTER 'tt' because we are already on this actor // so this enqueue must happen after we give up the actor. - print("schedule Task { @MyGlobalActor }, before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("schedule Task { @MyGlobalActor }, before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let t1 = Task { @MyGlobalActor in print("inside Task { @MyGlobalActor }, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") } - print("before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let outerTID = getCurrentThreadID() - let tt = Task.startSynchronously { @MyGlobalActor in + let tt = Task.immediate { @MyGlobalActor in let innerTID = getCurrentThreadID() - print("inside startSynchronously, outer thread = \(outerTID)") - print("inside startSynchronously, inner thread = \(innerTID)") + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") if (compareThreadIDs(outerTID, .notEqual, innerTID)) { print("ERROR! Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") } - print("inside startSynchronously, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") _ = try? await Task.sleep(for: .seconds(1)) - print("after sleep, inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after sleep, inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") + } + + return [t1, tt] +} + +func syncOnMyGlobalActorHopToDifferentActor() -> [Task] { + MyGlobalActor.shared.preconditionIsolated("Should be executing on the global actor here") + print("Confirmed to be on @MyGlobalActor") + + // This task must be guaranteed to happen AFTER 'tt' because we are already on this actor + // so this enqueue must happen after we give up the actor. + print("schedule Task { @DifferentGlobalActor }, before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") + let t1 = Task { @DifferentGlobalActor in + print("inside Task { @DifferentGlobalActor } [thread:\(getCurrentThreadID())] @ :\(#line)") + DifferentGlobalActor.shared.preconditionIsolated("Expected Task{} to be on DifferentGlobalActor") + } + + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") + let outerTID = getCurrentThreadID() + let tt = Task.immediate { @DifferentGlobalActor in + let innerTID = getCurrentThreadID() + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") + if (compareThreadIDs(outerTID, .equal, innerTID)) { + // This case specifically is NOT synchronously run because we specified a different isolation for the closure + // and FORCED a hop to the DifferentGlobalActor executor. + print("ERROR! Outer Thread ID must NOT equal Thread ID inside runSynchronously synchronous part!") + } + // We crucially need to see this task be enqueued on the different global actor, + // so it did not execute "synchronously" after all - it had to hop to the other actor. + dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) + DifferentGlobalActor.shared.preconditionIsolated("Expected Task.immediate { @DifferentGlobalActor in } to be on DifferentGlobalActor") + + print("inside immediate, sleep now [thread:\(getCurrentThreadID())] @ :\(#line)") + _ = try? await Task.sleep(for: .milliseconds(100)) + + print("inside immediate, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + dispatchPrecondition(condition: .onQueue(DifferentGlobalActor.queue)) + DifferentGlobalActor.shared.preconditionIsolated("Expected Task.immediate { @DifferentGlobalActor in } to be on DifferentGlobalActor") + + // do something here + await MyGlobalActor.test() + DifferentGlobalActor.test() } return [t1, tt] @@ -118,33 +182,33 @@ func syncOnNonTaskThread(synchronousTask behavior: SynchronousTaskBehavior) { queue.async { // This is in order so we don't have a "current task" nor any "current executor" - print("before startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("before immediate [thread:\(getCurrentThreadID())] @ :\(#line)") let outerTID = getCurrentThreadID() - let tt = Task.startSynchronously { + let tt = Task.immediate { dispatchPrecondition(condition: .onQueue(queue)) let innerTID = getCurrentThreadID() if compareThreadIDs(outerTID, .notEqual, innerTID) { - print("inside startSynchronously, outer thread = \(outerTID)") - print("inside startSynchronously, inner thread = \(innerTID)") + print("inside immediate, outer thread = \(outerTID)") + print("inside immediate, inner thread = \(innerTID)") print("ERROR! Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") } - print("inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") switch behavior { case .suspend: // sleep until woken up by outer task; i.e. actually suspend - print("inside startSynchronously, before sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, before sleep [thread:\(getCurrentThreadID())] @ :\(#line)") _ = try? await Task.sleep(for: .seconds(10)) - print("inside startSynchronously, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, after sleep [thread:\(getCurrentThreadID())] @ :\(#line)") case .dontSuspend: - print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, done [thread:\(getCurrentThreadID())] @ :\(#line)") () } sem1.signal() } - print("after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after immediate, outside; cancel (wakeup) the synchronous task! [thread:\(getCurrentThreadID())] @ :\(#line)") tt.cancel() // wake up the sleep sem1.wait() @@ -171,13 +235,40 @@ await Task { @MyGlobalActor in // CHECK-LABEL: syncOnMyGlobalActor() // CHECK: Confirmed to be on @MyGlobalActor -// CHECK: schedule Task { @MyGlobalActor }, before startSynchronously [thread:[[CALLING_THREAD:.*]]] -// CHECK: before startSynchronously [thread:[[CALLING_THREAD]]] +// CHECK: schedule Task { @MyGlobalActor }, before immediate [thread:[[CALLING_THREAD:.*]]] +// CHECK: before immediate [thread:[[CALLING_THREAD]]] // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, sleep now +// CHECK: inside immediate, sleep now // CHECK: inside Task { @MyGlobalActor }, after sleep // resume on some other thread -// CHECK: after sleep, inside startSynchronously +// CHECK: after sleep, inside immediate + +print("\n\n==== ------------------------------------------------------------------") +print("syncOnMyGlobalActorHopToDifferentActor()") + +await Task { @MyGlobalActor in + MyGlobalActor.shared.preconditionIsolated("Should be executing on the global actor here") + for t in syncOnMyGlobalActorHopToDifferentActor() { + await t.value + } +}.value + +// Assertion Notes: We expect the task to be on the specified queue as we force the Task.immediate +// task to enqueue on the DifferentGlobalActor, however we CANNOT use threads to verify this behavior, +// because dispatch may still pull tricks and reuse threads. We can only verify that we're on the right +// queue, and that the `enqueue` calls on the target executor happen when we expect them to. +// +// CHECK: syncOnMyGlobalActorHopToDifferentActor() +// CHECK: Confirmed to be on @MyGlobalActor +// CHECK: before immediate + +// This IS actually enqueueing on the target actor (not synchronous), as expected: +// CHECK: NaiveQueueExecutor(DifferentGlobalActor-queue) enqueue +// CHECK: inside immediate, sleep now + +// After the sleep we get back onto the specified executor as expected +// CHECK: NaiveQueueExecutor(DifferentGlobalActor-queue) enqueue +// CHECK: inside immediate, after sleep print("\n\n==== ------------------------------------------------------------------") var behavior: SynchronousTaskBehavior = .suspend @@ -186,12 +277,12 @@ syncOnNonTaskThread(synchronousTask: behavior) // CHECK-LABEL: syncOnNonTaskThread(synchronousTask: suspend) // No interleaving allowed between "before" and "inside": -// CHECK-NEXT: before startSynchronously [thread:[[CALLING_THREAD2:.*]]] +// CHECK-NEXT: before immediate [thread:[[CALLING_THREAD2:.*]]] // CHECK-NOT: ERROR! -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: inside startSynchronously, before sleep [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD2]]] -// CHECK-NEXT: inside startSynchronously, after sleep +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: inside immediate, before sleep [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: after immediate, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD2]]] +// CHECK-NEXT: inside immediate, after sleep print("\n\n==== ------------------------------------------------------------------") behavior = .dontSuspend @@ -199,11 +290,11 @@ print("syncOnNonTaskThread(synchronousTask: \(behavior))") syncOnNonTaskThread(synchronousTask: behavior) // CHECK-LABEL: syncOnNonTaskThread(synchronousTask: dontSuspend) -// CHECK-NEXT: before startSynchronously [thread:[[CALLING_THREAD3:.*]]] +// CHECK-NEXT: before immediate [thread:[[CALLING_THREAD3:.*]]] // CHECK-NOT: ERROR! -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD3]]] -// CHECK: inside startSynchronously, done [thread:[[CALLING_THREAD3]]] -// CHECK: after startSynchronously, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD3]]] +// CHECK: inside immediate, done [thread:[[CALLING_THREAD3]]] +// CHECK: after immediate, outside; cancel (wakeup) the synchronous task! [thread:[[CALLING_THREAD3]]] print("\n\n==== ------------------------------------------------------------------") print("callActorFromStartSynchronousTask() - not on specific queue") @@ -211,17 +302,17 @@ callActorFromStartSynchronousTask(recipient: .recipient(Recipient())) // CHECK: callActorFromStartSynchronousTask() // No interleaving allowed between "before" and "inside": -// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]] -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD4]]] +// CHECK: before immediate [thread:[[CALLING_THREAD4:.*]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD4]]] -// It is important that as we suspend on the actor call, the 'after' startSynchronously gets to run -// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]] -// CHECK: after startSynchronously +// It is important that as we suspend on the actor call, the 'after' immediate gets to run +// CHECK-NEXT: inside immediate, call rec.sync() [thread:[[CALLING_THREAD4]]] +// CHECK: after immediate // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, call rec.sync() done +// CHECK: inside immediate, call rec.sync() done // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, done +// CHECK: inside immediate, done /// Don't want to involve protocol calls to not confuse the test with additional details, /// so we use concrete types here. @@ -255,13 +346,13 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { queue.async { let outerTID = getCurrentThreadID() - print("before startSynchronously [thread:\(outerTID)] @ :\(#line)") - let tt = Task.startSynchronously { + print("before immediate [thread:\(outerTID)] @ :\(#line)") + let tt = Task.immediate { dispatchPrecondition(condition: .onQueue(queue)) let innerTID = getCurrentThreadID() precondition(compareThreadIDs(outerTID, .equal, innerTID), "Outer Thread ID must be equal Thread ID inside runSynchronously synchronous part!") - print("inside startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate [thread:\(getCurrentThreadID())] @ :\(#line)") for i in 1..<10 { queue.async { @@ -269,12 +360,12 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { } } - print("inside startSynchronously, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, call rec.sync() [thread:\(getCurrentThreadID())] @ :\(#line)") switch rec { case .recipient(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID) case .recipientOnQueue(let recipient): await recipient.callAndSuspend(syncTaskThreadID: innerTID) } - print("inside startSynchronously, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, call rec.sync() done [thread:\(getCurrentThreadID())] @ :\(#line)") // after suspension we are supposed to hop off to the global pool, // thus the thread IDs cannot be the same anymore @@ -287,11 +378,11 @@ func callActorFromStartSynchronousTask(recipient rec: TargetActorToCall) { print("NOTICE: Task resumed on same thread as it entered the synchronous task!") } - print("inside startSynchronously, done [thread:\(getCurrentThreadID())] @ :\(#line)") + print("inside immediate, done [thread:\(getCurrentThreadID())] @ :\(#line)") sem1.signal() } - print("after startSynchronously [thread:\(getCurrentThreadID())] @ :\(#line)") + print("after immediate [thread:\(getCurrentThreadID())] @ :\(#line)") sem2.signal() } @@ -306,14 +397,14 @@ callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue( // 50: callActorFromStartSynchronousTask() -// 51: before startSynchronously [thread:0x00007000054f5000] @ :366 -// 52: inside startSynchronously [thread:0x00007000054f5000] @ :372 -// 53: inside startSynchronously, call rec.sync() [thread:0x00007000054f5000] @ :380 +// 51: before immediate [thread:0x00007000054f5000] @ :366 +// 52: inside immediate [thread:0x00007000054f5000] @ :372 +// 53: inside immediate, call rec.sync() [thread:0x00007000054f5000] @ :380 // 54: Recipient/sync(syncTaskThreadID:) Current actor thread id = 0x000070000567e000 @ :336 -// 55: inside startSynchronously, call rec.sync() done [thread:0x000070000567e000] @ :385 +// 55: inside immediate, call rec.sync() done [thread:0x000070000567e000] @ :385 // 56: Inner thread id = 0x00007000054f5000 // 57: Current thread id = 0x000070000567e000 -// 60: after startSynchronously [thread:0x00007000054f5000] @ :418 +// 60: after immediate [thread:0x00007000054f5000] @ :418 // 61: - async work on queue // 62: - async work on queue // 63: - async work on queue @@ -324,24 +415,24 @@ callActorFromStartSynchronousTask(recipient: .recipientOnQueue(RecipientOnQueue( // 69: - async work on queue // 71: Inner thread id = 0x00007000054f5000 // 72: Current thread id = 0x000070000567e000 -// 73: inside startSynchronously, done [thread:0x000070000567e000] @ :414 +// 73: inside immediate, done [thread:0x000070000567e000] @ :414 // CHECK-LABEL: callActorFromStartSynchronousTask() - actor in custom executor with its own queue // No interleaving allowed between "before" and "inside": -// CHECK: before startSynchronously [thread:[[CALLING_THREAD4:.*]]] -// CHECK-NEXT: inside startSynchronously [thread:[[CALLING_THREAD4]]] +// CHECK: before immediate [thread:[[CALLING_THREAD4:.*]]] +// CHECK-NEXT: inside immediate [thread:[[CALLING_THREAD4]]] // As we call into an actor, we must enqueue to its custom executor; // Make sure the enqueue happens as expected and only then do we give up the calling thread -// allowing the 'after startSynchronously' to run. +// allowing the 'after immediate' to run. // -// CHECK-NEXT: inside startSynchronously, call rec.sync() [thread:[[CALLING_THREAD4]]] -// CHECK: after startSynchronously +// CHECK-NEXT: inside immediate, call rec.sync() [thread:[[CALLING_THREAD4]]] +// CHECK: after immediate // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, call rec.sync() done +// CHECK: inside immediate, call rec.sync() done // CHECK-NOT: ERROR! -// CHECK: inside startSynchronously, done +// CHECK: inside immediate, done actor RecipientOnQueue: RecipientProtocol { let executor: NaiveQueueExecutor diff --git a/test/Concurrency/Runtime/startImmediately_order.swift b/test/Concurrency/Runtime/startImmediately_order.swift new file mode 100644 index 0000000000000..7dbd579e37afa --- /dev/null +++ b/test/Concurrency/Runtime/startImmediately_order.swift @@ -0,0 +1,64 @@ +// RUN: %empty-directory(%t) +// RUN: %target-build-swift -Xfrontend -disable-availability-checking %s %import-libdispatch -swift-version 6 -o %t/a.out +// RUN: %target-codesign %t/a.out +// RUN: %target-run %t/a.out | %FileCheck %s + +// REQUIRES: executable_test +// REQUIRES: concurrency +// REQUIRES: concurrency_runtime + +// UNSUPPORTED: back_deployment_runtime +// UNSUPPORTED: back_deploy_concurrency +// UNSUPPORTED: use_os_stdlib +// UNSUPPORTED: freestanding + +import _Concurrency + +let max = 1000 + +func bar(x: Int, cc: CheckedContinuation) { + Task.immediate { + print("Task \(x) started") + try! await Task.sleep(nanoseconds: 10000) + if (x == max) { + cc.resume() + } + } +} + +await withCheckedContinuation { (cc: CheckedContinuation) in + for i in 1...max { + bar(x: i, cc: cc) + } +} + +// CHECK: Task 1 started +// CHECK: Task 2 started +// CHECK: Task 3 started +// CHECK: Task 4 started +// CHECK: Task 5 started +// CHECK: Task 6 started +// CHECK: Task 7 started +// CHECK: Task 8 started +// CHECK: Task 9 started +// CHECK: Task 10 started +// CHECK: Task 11 started +// CHECK: Task 12 started +// CHECK: Task 13 started +// CHECK: Task 14 started +// CHECK: Task 15 started +// CHECK: Task 16 started +// CHECK: Task 17 started +// CHECK: Task 18 started +// CHECK: Task 19 started +// CHECK: Task 20 started +// CHECK: Task 21 started +// CHECK: Task 22 started +// CHECK: Task 23 started +// CHECK: Task 24 started +// CHECK: Task 25 started +// CHECK: Task 26 started +// CHECK: Task 27 started +// CHECK: Task 28 started +// CHECK: Task 29 started +// CHECK: Task 30 started diff --git a/test/Concurrency/startSynchronouslyIsolation.swift b/test/Concurrency/startImmediatelyIsolation.swift similarity index 50% rename from test/Concurrency/startSynchronouslyIsolation.swift rename to test/Concurrency/startImmediatelyIsolation.swift index 8837fecdba3f1..d864285757a85 100644 --- a/test/Concurrency/startSynchronouslyIsolation.swift +++ b/test/Concurrency/startImmediatelyIsolation.swift @@ -4,37 +4,37 @@ @available(SwiftStdlib 6.2, *) func sync() -> Task { - Task.startSynchronously { + Task.immediate { return "" } } @available(SwiftStdlib 6.2, *) func async() async throws { - let t1 = Task.startSynchronously { + let t1 = Task.immediate { return "" } let _: String = await t1.value - let t2: Task = Task.startSynchronously { + let t2: Task = Task.immediate { throw CancellationError() } let _: String = try await t2.value await withTaskGroup(of: Int.self) { group in - group.startTaskSynchronously { 1 } - group.startTaskSynchronouslyUnlessCancelled { 2 } + group.addImmediateTask { 1 } + group.addImmediateTaskUnlessCancelled { 2 } } await withThrowingTaskGroup(of: Int.self) { group in - group.startTaskSynchronously { () async throws -> Int in 1 } - group.startTaskSynchronouslyUnlessCancelled { () async throws -> Int in 2 } + group.addImmediateTask { () async throws -> Int in 1 } + group.addImmediateTaskUnlessCancelled { () async throws -> Int in 2 } } await withDiscardingTaskGroup { group in - group.startTaskSynchronously { } - group.startTaskSynchronouslyUnlessCancelled { } + group.addImmediateTask { } + group.addImmediateTaskUnlessCancelled { } } try await withThrowingDiscardingTaskGroup { group in - group.startTaskSynchronously { () async throws -> Void in } - group.startTaskSynchronouslyUnlessCancelled { () async throws -> Void in } + group.addImmediateTask { () async throws -> Void in } + group.addImmediateTaskUnlessCancelled { () async throws -> Void in } } } diff --git a/test/abi/macOS/arm64/concurrency.swift b/test/abi/macOS/arm64/concurrency.swift index 634e96492dde8..85942a9747bf0 100644 --- a/test/abi/macOS/arm64/concurrency.swift +++ b/test/abi/macOS/arm64/concurrency.swift @@ -388,18 +388,21 @@ Added: _$ss33withTaskPriorityEscalationHandler9operation02onC9Escalated9isolatio Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvgZ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvpZMV Added: _swift_task_getCurrentTaskName -Added: _$sScG22startTaskSynchronously4name8priority9operationySSSg_ScPSgxyYacntF -Added: _$sScG37startTaskSynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgxyYacntF -Added: _$sScTss5Error_pRs_rlE18startSynchronously4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKcntFZ -Added: _$sScTss5NeverORs_rlE18startSynchronously4name8priority_ScTyxABGSSSg_ScPSgxyYaKcntFZ -Added: _$sScg22startTaskSynchronously4name8priority9operationySSSg_ScPSgxyYaKcntF -Added: _$sScg37startTaskSynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaKcntF -Added: _$ss19DiscardingTaskGroupV05startB13Synchronously4name8priority9operationySSSg_ScPSgyyYacntF -Added: _$ss19DiscardingTaskGroupV05startB28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYacntF -Added: _$ss27ThrowingDiscardingTaskGroupV05startC13Synchronously4name8priority9operationySSSg_ScPSgyyYaKcntF -Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKcntF - -Added: _swift_task_startSynchronously + +// startSynchronously, immediate, addImmediateTask{UnlessCancelled} +Added: _swift_task_immediate +Added: _$sScTss5Error_pRs_rlE18startSynchronously4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScTss5NeverORs_rlE18startSynchronously4name8priority_ScTyxABGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScG16addImmediateTask4name8priority9operationySSSg_ScPSgxyYaYAcntF +Added: _$sScg31addImmediateTaskUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaKYAcntF +Added: _$sScG31addImmediateTaskUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaYAcntF +Added: _$sScTss5NeverORs_rlE9immediate4name8priority_ScTyxABGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScTss5Error_pRs_rlE9immediate4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScg16addImmediateTask4name8priority9operationySSSg_ScPSgxyYaKYAcntF +Added: _$ss19DiscardingTaskGroupV012addImmediateB04name8priority9operationySSSg_ScPSgyyYaYAcntF +Added: _$ss19DiscardingTaskGroupV012addImmediateB15UnlessCancelled4name8priority9operationySSSg_ScPSgyyYaYAcntF +Added: _$ss27ThrowingDiscardingTaskGroupV012addImmediateC04name8priority9operationySSSg_ScPSgyyYaKYAcntF +Added: _$ss27ThrowingDiscardingTaskGroupV012addImmediateC15UnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKYAcntF // isIsolatingCurrentContext Added: _swift_task_invokeSwiftIsIsolatingCurrentContext diff --git a/test/abi/macOS/x86_64/concurrency.swift b/test/abi/macOS/x86_64/concurrency.swift index 07ec5e21e67ee..d960126b0b448 100644 --- a/test/abi/macOS/x86_64/concurrency.swift +++ b/test/abi/macOS/x86_64/concurrency.swift @@ -388,18 +388,21 @@ Added: _$ss33withTaskPriorityEscalationHandler9operation02onC9Escalated9isolatio Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvgZ Added: _$sScTss5NeverORszABRs_rlE4nameSSSgvpZMV Added: _swift_task_getCurrentTaskName -Added: _$sScG22startTaskSynchronously4name8priority9operationySSSg_ScPSgxyYacntF -Added: _$sScG37startTaskSynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgxyYacntF -Added: _$sScTss5Error_pRs_rlE18startSynchronously4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKcntFZ -Added: _$sScTss5NeverORs_rlE18startSynchronously4name8priority_ScTyxABGSSSg_ScPSgxyYaKcntFZ -Added: _$sScg22startTaskSynchronously4name8priority9operationySSSg_ScPSgxyYaKcntF -Added: _$sScg37startTaskSynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaKcntF -Added: _$ss19DiscardingTaskGroupV05startB13Synchronously4name8priority9operationySSSg_ScPSgyyYacntF -Added: _$ss19DiscardingTaskGroupV05startB28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYacntF -Added: _$ss27ThrowingDiscardingTaskGroupV05startC13Synchronously4name8priority9operationySSSg_ScPSgyyYaKcntF -Added: _$ss27ThrowingDiscardingTaskGroupV05startC28SynchronouslyUnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKcntF - -Added: _swift_task_startSynchronously + +// startSynchronously, immediate, addImmediateTask{UnlessCancelled} +Added: _swift_task_immediate +Added: _$sScTss5Error_pRs_rlE18startSynchronously4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScTss5NeverORs_rlE18startSynchronously4name8priority_ScTyxABGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScG16addImmediateTask4name8priority9operationySSSg_ScPSgxyYaYAcntF +Added: _$sScg31addImmediateTaskUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaKYAcntF +Added: _$sScG31addImmediateTaskUnlessCancelled4name8priority9operationySSSg_ScPSgxyYaYAcntF +Added: _$sScTss5NeverORs_rlE9immediate4name8priority_ScTyxABGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScTss5Error_pRs_rlE9immediate4name8priority_ScTyxsAA_pGSSSg_ScPSgxyYaKYAcntFZ +Added: _$sScg16addImmediateTask4name8priority9operationySSSg_ScPSgxyYaKYAcntF +Added: _$ss19DiscardingTaskGroupV012addImmediateB04name8priority9operationySSSg_ScPSgyyYaYAcntF +Added: _$ss19DiscardingTaskGroupV012addImmediateB15UnlessCancelled4name8priority9operationySSSg_ScPSgyyYaYAcntF +Added: _$ss27ThrowingDiscardingTaskGroupV012addImmediateC04name8priority9operationySSSg_ScPSgyyYaKYAcntF +Added: _$ss27ThrowingDiscardingTaskGroupV012addImmediateC15UnlessCancelled4name8priority9operationySSSg_ScPSgyyYaKYAcntF // isIsolatingCurrentContext Added: _swift_task_invokeSwiftIsIsolatingCurrentContext diff --git a/unittests/runtime/CompatibilityOverrideConcurrency.cpp b/unittests/runtime/CompatibilityOverrideConcurrency.cpp index 353c62011328d..bd146a11644db 100644 --- a/unittests/runtime/CompatibilityOverrideConcurrency.cpp +++ b/unittests/runtime/CompatibilityOverrideConcurrency.cpp @@ -118,7 +118,7 @@ static void swift_task_startOnMainActor_override(AsyncTask* task) { } SWIFT_CC(swift) -static void swift_task_startSynchronously_override(AsyncTask* task) { +static void swift_task_immediate_override(AsyncTask* task, SerialExecutorRef targetExecutor) { Ran = true; } @@ -350,8 +350,8 @@ TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_startOnMainActorImpl) { swift_task_startOnMainActor(nullptr); } -TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_startSynchronously) { - swift_task_startSynchronously(nullptr); +TEST_F(CompatibilityOverrideConcurrencyTest, test_swift_immediately) { + swift_task_immediate(nullptr, SerialExecutorRef::generic()); } TEST_F(CompatibilityOverrideConcurrencyTest,