Skip to content

Commit

Permalink
Use isolated(any) instead of Sendable for withMainSerialExecutor asyn…
Browse files Browse the repository at this point in the history
…c operation (#46)

* Use isolated(any) instead of Sendable.

* bump ci

* bump CI

* doc fixes

---------

Co-authored-by: Stephen Celis <[email protected]>
  • Loading branch information
jonduenas and stephencelis authored Nov 11, 2024
1 parent 7a16fa5 commit 163409e
Show file tree
Hide file tree
Showing 2 changed files with 76 additions and 51 deletions.
41 changes: 27 additions & 14 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -43,18 +43,31 @@ jobs:
run: swift test -c ${{ matrix.config }}

wasm:
name: Wasm
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: bytecodealliance/actions/wasmtime/setup@v1
- uses: swiftwasm/setup-swiftwasm@v1
with:
swift-version: "wasm-5.9.2-RELEASE"
- name: Build tests
run: swift build --triple wasm32-unknown-wasi --build-tests
- name: Run tests
run: wasmtime --dir . .build/debug/swift-concurrency-extrasPackageTests.wasm
name: SwiftWasm
runs-on: ubuntu-latest
strategy:
matrix:
include:
- toolchain: swift-DEVELOPMENT-SNAPSHOT-2024-09-12-a
swift-sdk: swift-wasm-DEVELOPMENT-SNAPSHOT-2024-09-12-a
checksum: 630ce23114580dfae029f832d8ccc8b1ba5136b7f915e82f8e405650e326b562
steps:
- uses: actions/checkout@v4
- uses: bytecodealliance/actions/wasmtime/setup@v1
- name: Install Swift and Swift SDK for WebAssembly
run: |
PREFIX=/opt/swift
SWIFT_TOOLCHAIN_TAG="${{ matrix.toolchain }}"
SWIFT_SDK_TAG="${{ matrix.swift-sdk }}"
set -ex
curl -f -o /tmp/swift.tar.gz "https://download.swift.org/development/ubuntu2204/$SWIFT_TOOLCHAIN_TAG/$SWIFT_TOOLCHAIN_TAG-ubuntu22.04.tar.gz"
sudo mkdir -p $PREFIX; sudo tar -xzf /tmp/swift.tar.gz -C $PREFIX --strip-component 1
$PREFIX/usr/bin/swift sdk install "https://github.com/swiftwasm/swift/releases/download/$SWIFT_SDK_TAG/$SWIFT_SDK_TAG-wasm32-unknown-wasi.artifactbundle.zip" --checksum ${{ matrix.checksum }}
echo "$PREFIX/usr/bin" >> $GITHUB_PATH
- name: Build tests
run: swift build --swift-sdk wasm32-unknown-wasi --build-tests -Xlinker -z -Xlinker stack-size=$((1024 * 1024))
- name: Run tests
run: wasmtime --dir . .build/debug/swift-concurrency-extrasPackageTests.wasm

windows:
name: Windows
Expand All @@ -66,8 +79,8 @@ jobs:
steps:
- uses: compnerd/gha-setup-swift@main
with:
branch: swift-5.10-release
tag: 5.10-RELEASE
branch: swift-6.0-release
tag: 6.0-RELEASE

- uses: actions/checkout@v4
- name: Run tests
Expand Down
86 changes: 49 additions & 37 deletions Sources/ConcurrencyExtras/MainSerialExecutor.swift
Original file line number Diff line number Diff line change
@@ -1,45 +1,57 @@
#if !os(WASI) && !os(Windows) && !os(Android)
import Foundation

/// Perform an operation on the main serial executor.
///
/// Some asynchronous code is [notoriously
/// difficult](https://forums.swift.org/t/reliably-testing-code-that-adopts-swift-concurrency/57304)
/// to test in Swift due to how suspension points are processed by the runtime. This function
/// attempts to run all tasks spawned in the given operation serially and deterministically. It
/// makes asynchronous tests faster and less flakey.
///
/// ```swift
/// await withMainSerialExecutor {
/// // Everything performed in this scope is performed serially...
/// }
/// ```
///
/// See <doc:ReliablyTestingAsync> for more information on why this tool is needed to test
/// async code and how to use it.
///
/// > Warning: This API is only intended to be used from tests to make them more reliable. Please do
/// > not use it from application code.
/// >
/// > We say that it "_attempts_ to run all tasks spawned in an operation serially and
/// > deterministically" because under the hood it relies on a global, mutable variable in the Swift
/// > runtime to do its job, and there are no scoping _guarantees_ should this mutable variable change
/// > during the operation.
///
/// - Parameter operation: An operation to be performed on the main serial executor.
@MainActor
public func withMainSerialExecutor(
@_implicitSelfCapture operation: @Sendable () async throws -> Void
) async rethrows {
let didUseMainSerialExecutor = uncheckedUseMainSerialExecutor
defer { uncheckedUseMainSerialExecutor = didUseMainSerialExecutor }
uncheckedUseMainSerialExecutor = true
try await operation()
}
#if compiler(>=6)
/// Perform an operation on the main serial executor.
///
/// Some asynchronous code is [notoriously
/// difficult](https://forums.swift.org/t/reliably-testing-code-that-adopts-swift-concurrency/57304)
/// to test in Swift due to how suspension points are processed by the runtime. This function
/// attempts to run all tasks spawned in the given operation serially and deterministically. It
/// makes asynchronous tests faster and less flakey.
///
/// ```swift
/// await withMainSerialExecutor {
/// // Everything performed in this scope is performed serially...
/// }
/// ```
///
/// See <doc:ReliablyTestingAsync> for more information on why this tool is needed to test
/// async code and how to use it.
///
/// > Warning: This API is only intended to be used from tests to make them more reliable. Please do
/// > not use it from application code.
/// >
/// > We say that it "_attempts_ to run all tasks spawned in an operation serially and
/// > deterministically" because under the hood it relies on a global, mutable variable in the Swift
/// > runtime to do its job, and there are no scoping _guarantees_ should this mutable variable change
/// > during the operation.
///
/// - Parameter operation: An operation to be performed on the main serial executor.
@MainActor
public func withMainSerialExecutor(
@_implicitSelfCapture operation: @isolated(any) () async throws -> Void
) async rethrows {
let didUseMainSerialExecutor = uncheckedUseMainSerialExecutor
defer { uncheckedUseMainSerialExecutor = didUseMainSerialExecutor }
uncheckedUseMainSerialExecutor = true
try await operation()
}
#else
@MainActor
public func withMainSerialExecutor(
@_implicitSelfCapture operation: @Sendable () async throws -> Void
) async rethrows {
let didUseMainSerialExecutor = uncheckedUseMainSerialExecutor
defer { uncheckedUseMainSerialExecutor = didUseMainSerialExecutor }
uncheckedUseMainSerialExecutor = true
try await operation()
}
#endif

/// Perform an operation on the main serial executor.
///
/// A synchronous version of ``withMainSerialExecutor(operation:)-79jpc`` that can be used in
/// A synchronous version of ``withMainSerialExecutor(operation:)-7fqt1`` that can be used in
/// `XCTestCase.invokeTest` to ensure all async tests are performed serially:
///
/// ```swift
Expand All @@ -65,7 +77,7 @@
/// Overrides Swift's global executor with the main serial executor in an unchecked fashion.
///
/// > Warning: When set to `true`, all tasks will be enqueued on the main serial executor till set
/// > back to `false`. Consider using ``withMainSerialExecutor(operation:)-79jpc``, instead, which
/// > back to `false`. Consider using ``withMainSerialExecutor(operation:)-7fqt1``, instead, which
/// > scopes this work to the duration of a given operation.
public var uncheckedUseMainSerialExecutor: Bool {
get { swift_task_enqueueGlobal_hook != nil }
Expand Down

0 comments on commit 163409e

Please sign in to comment.