CooperativeExecutor.runUntil sleeps until the next timer while runnable jobs are queued
还没有人认领这个 Issue。
评估
- 难度
- 2/5
- 预计耗时
- 1-3 小时
- 新手友好度
- 76/100
- Issue 类型
- 缺陷
- 描述清晰度
- 描述清楚
- 活跃度
- 活跃
- 技术栈
- swift
调研方向
从 stdlib/public/Concurrency/CooperativeExecutor.swift 中第 286-331 行附近的 CooperativeExecutor.runUntil(_:) 开始,然后在受影响的 runtime 上运行提供的 WASI 复现程序。当可运行的作业在 executor 等待计时器之前执行,并且两个场景都在几毫秒内完成且 watchdog 已取消时,修复才算完成;如果周围的测试结构支持,请添加回归测试。
由索引模型根据 Issue 内容生成。
描述
Description
On wasm32-unknown-wasip1, a task that becomes runnable while another task is inside Task.sleep does not run until that sleep's deadline. This applies to a task that called Task.yield() and to a task whose awaited child has finished.
CooperativeExecutor.runUntil(_:) takes the ready jobs with runQueue.take(), runs them, and then sleeps for the time to the next timer. Jobs enqueued while those ran are in runQueue at that point; runQueue.isEmpty is checked only when no timer is pending.
Reproduction
// Package.swift: swift-tools-version 6.3, one executableTarget named repro.
// Sources/repro/main.swift
let sleepSeconds: UInt64 = 5
let clock = ContinuousClock()
let start = clock.now
func stamp(_ what: String) { print("[\(clock.now - start)] \(what)") }
func scenario(_ name: String, yields: Int) async {
stamp("\(name): start")
let watchdog = Task {
try? await Task.sleep(nanoseconds: sleepSeconds * 1_000_000_000)
stamp("\(name): watchdog woke, cancelled=\(Task.isCancelled)")
}
let worker = Task {
for _ in 0 ..< yields { await Task.yield() }
stamp("\(name): worker done")
}
await worker.value
watchdog.cancel()
stamp("\(name): end")
}
await scenario("no-yield", yields: 0)
await scenario("yield", yields: 50)
stamp("all done")
$ swift sdk install https://download.swift.org/swift-6.4.0-release/wasm-sdk/swift-6.4.0-RELEASE/swift-6.4.0-RELEASE_wasm.artifactbundle.tar.gz --checksum f07b7be3c586d92d7a07051fc6d303b87ebea67eadc40640ba59d5a8b79aa86d
$ swift build -c release --swift-sdk swift-6.4.0-RELEASE_wasm
$ wasmkit run .build/out/Products/Release-webassembly-wasm32/repro.wasm
[0.003 seconds] no-yield: start
[0.006 seconds] no-yield: worker done
[5.023 seconds] no-yield: end
[5.024 seconds] yield: start
[5.026 seconds] no-yield: watchdog woke, cancelled=true
[10.036 seconds] yield: watchdog woke, cancelled=false
[10.039 seconds] yield: worker done
[10.039 seconds] yield: end
With no yields the worker is done at 6 ms, yet the task awaiting it resumes at 5 s. With yields the worker does not advance at all until the watchdog's sleep has run its full length, by which time nobody has cancelled it.
Expected behavior
Jobs that became runnable during a pass run before the executor sleeps; it should sleep only when the run queue is empty. Each scenario above should end within milliseconds, with the watchdog cancelled before it wakes.
A possible fix is to guard the sleep: if let toWait, runQueue.isEmpty { _sleep(…) } (or continue when runQueue is not empty). Untested.
Environment
Swift version 6.4 (swift-6.4-RELEASE), Ubuntu 24.04 aarch64 host, Swift SDK swift-6.4.0-RELEASE_wasm (wasm32-unknown-wasip1), run with the WasmKit 0.3.1 the toolchain ships.
Also reproduced with a binary built by Swift 6.3.3 and its Wasm SDK. The same code is present in release/6.3, release/6.4.x, release/6.4.1, swift-6.4.0-RELEASE, and main (as of 2026-09-19).
Additional information
The Swift version makes no difference; whether the WASI runtime's poll_oneoff actually sleeps does. Time for a four-scenario variant of the program (5 s sleep each) to finish, same two binaries on three runtimes:
| Binary | WasmKit 0.1.6 (ships with 6.3.3) | WasmKit 0.3.1 (ships with 6.4.0) | wasmtime 30.0.2 |
|---|---|---|---|
| built with Swift 6.3.3 + its Wasm SDK | 0.01 s | 20 s | 20 s |
| built with Swift 6.4.0 + its Wasm SDK | 0.01 s | 20 s | 20 s |
Impact: any program on these targets that keeps a timer pending while other tasks make progress — a timeout or watchdog around a body is the common case — runs at the pace of its timers. It surfaced as a test suite whose every deadline-bounded test took exactly its deadline (300 s) under the 6.4.0 toolchain while passing in seconds under 6.3.3: https://github.com/sinoru/swift-synchronization-kit/issues/4
#90096 (enabling Task.sleep in Embedded Swift) removes the #if !$Embedded guards around the same timer queues, so the same stall would presumably appear there once it lands.
- 主要语言
- Swift
- 星标
- 70.4k
- 派生
- 10.8k
- 平均合并
- 2 天 2 小时
- 30 天内合并 PR
- 461
贡献指南
从这里开始
- 先读完整个 Issue,再读项目的贡献指南。
- 在 Issue 下留言说明你要接手 —— 这能避免两个人做同样的事。
- Fork 仓库,在一个分支上完成修改。
- 提交 Pull Request,并在描述里引用这个 Issue 编号。
swiftlang/swift 的其他 Issue
-
triage needed
难度 2/5 1-3 小时 新手友好度 88/100
-
Foundation Windows
难度 2/5 1-3 小时 新手友好度 70/100
-
triage needed
难度 2/5 1-3 小时 新手友好度 76/100
-
triage needed
难度 2/5 1-3 小时 新手友好度 78/100
-
难度 2/5 1-3 小时 新手友好度 68/100
相似的 Issue
-
type: docs
难度 1/5 1 小时以内 新手友好度 95/100
googleapis/google-cloud-swift#971 ·
-
难度 2/5 1-3 小时 新手友好度 76/100
bitcoindevkit/bdk-ffi#1125 ·
-
难度 2/5 1-3 小时 新手友好度 72/100
mozilla-mobile/firefox-ios#35743 ·
-
难度 2/5 1-3 小时 新手友好度 84/100
manaflow-ai/cmux#13417 ·
-
triage
难度 2/5 1-3 小时 新手友好度 84/100
ionic-team/capacitor#8616 ·