fix: harden Phase 2 async/await per adversarial review (io.sx)

- await: add the one-awaiter-per-future guard `sched.Task.wait` has — a second
  concurrent `await` on the same pending future would overwrite the single
  `park` handle and orphan the first awaiter (silent deadlock). Now aborts
  loudly. (Fan-in over SEPARATE futures — `race` — registers one awaiter each,
  so it stays fine.)
- Document the Future/ThunkBox ALLOCATOR-LIFETIME contract: both are allocated
  from the `context.allocator` in force at `async`, which must outlive the
  future (the long-lived-container rule). Calling `async` inside a transient
  arena torn down before `run()` is a use-after-free; the common case (program
  GPA) is safe. A deeper own-allocator capture is deferred to convergence.
- Document that `cancel` does NOT stop an already-spawned worker (model (a) —
  the worker still runs; the sticky `canceled` atomic is the source of truth).
  True work-cancellation is Phase 3.
- Drop the dead `f.task = null` (immediately overwritten by spawn_raw).

The new `io_abort` extern shifts the prelude type table — 40 `.ir` snapshots
regenerated (behavior-preserving; no `.exit`/`.stdout`/`.stderr` changed).
Suite 829/0.
This commit is contained in:
agra
2026-06-27 08:13:57 +03:00
parent 967aed67d4
commit ada8d16256
41 changed files with 159 additions and 10 deletions

View File

@@ -4470,6 +4470,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4392,6 +4392,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4308,6 +4308,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4317,6 +4317,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4469,6 +4469,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4445,6 +4445,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4444,6 +4444,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4321,6 +4321,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4326,6 +4326,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4323,6 +4323,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4277,6 +4277,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4288,6 +4288,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4319,6 +4319,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4283,6 +4283,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4318,6 +4318,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4395,6 +4395,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4443,6 +4443,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4275,6 +4275,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4371,6 +4371,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4376,6 +4376,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4302,6 +4302,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4312,6 +4312,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4616,6 +4616,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4506,6 +4506,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4357,6 +4357,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4323,6 +4323,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4296,6 +4296,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4318,6 +4318,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4472,6 +4472,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4425,6 +4425,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4522,6 +4522,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4538,6 +4538,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4631,6 +4631,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -4311,6 +4311,9 @@ declare i64 @now_secs(ptr) #0
; Function Attrs: nounwind ; Function Attrs: nounwind
declare i64 @mono_ms(ptr) #0 declare i64 @mono_ms(ptr) #0
; Function Attrs: nounwind
declare void @abort() #0
; Function Attrs: nounwind ; Function Attrs: nounwind
define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 { define internal ptr @CBlockingIo.spawn_raw(ptr %0, ptr %1, ptr %2, ptr %3, { i64 } %4) #0 {
entry: entry:

View File

@@ -26,6 +26,9 @@
#import "modules/std/atomic.sx"; #import "modules/std/atomic.sx";
time :: #import "modules/std/time.sx"; time :: #import "modules/std/time.sx";
// Loud-bail for the one-awaiter-per-future invariant (mirrors sched.sx).
io_abort :: () -> noreturn extern libc "abort";
// --- IoErr: the error channel async rides (cancellation = model (a)) --- // --- IoErr: the error channel async rides (cancellation = model (a)) ---
// //
// A canceled future raises `.Canceled` out of `await`; a failed task // A canceled future raises `.Canceled` out of `await`; a failed task
@@ -125,17 +128,28 @@ sx_run_boxed_closure :: (arg: *void) {
// (issue 0156 Part 2) — so any inputs are captured at the CALL SITE in the lambda // (issue 0156 Part 2) — so any inputs are captured at the CALL SITE in the lambda
// (`context.io.async(() -> i64 => compute(a, b))`), exactly like `sched.go`. // (`context.io.async(() -> i64 => compute(a, b))`), exactly like `sched.go`.
// //
// The Future is HEAP-allocated (not returned by value): under the fiber impl the // The Future (and the completion-closure `ThunkBox`) are HEAP-allocated (not
// worker fills it AFTER `async` returns, so the awaiter and the worker must share // returned by value): under the fiber impl the worker fills the Future AFTER
// one stable object. Like `sched.go`'s Task, it currently leaks (bounded by the // `async` returns, so the awaiter and the worker must share one stable object.
// async count; invisible under the default GPA). Freeing it needs join-point // Like `sched.go`'s Task, they currently leak (bounded by the async count;
// ownership — deferred. // invisible under the default GPA). Freeing them needs join-point ownership —
// deferred.
//
// ALLOCATOR-LIFETIME CONTRACT: both are allocated from the `context.allocator`
// in force at the `async` CALL, and that allocator MUST outlive the future —
// i.e. survive until the worker has run and the result is consumed. This is the
// long-lived-container rule (CLAUDE.md): calling `async` inside a transient
// `push Context { allocator = arena }` that is torn down before `run()`/`await`
// drives the worker frees the Future while it is still live (use-after-free).
// The common case (the program-stable default GPA, or a scheduler set up under a
// long-lived allocator) is safe. A deeper fix — `async` capturing the scheduler's
// own long-lived allocator the way `sched.go` does — needs a protocol affordance
// to reach it and is deferred to the convergence phase.
async :: ufcs (io: Io, worker: Closure() -> $R) -> *Future($R) { async :: ufcs (io: Io, worker: Closure() -> $R) -> *Future($R) {
raw := context.allocator.alloc_bytes(size_of(Future($R))); raw := context.allocator.alloc_bytes(size_of(Future($R)));
f : *Future($R) = xx raw; f : *Future($R) = xx raw;
f.state = .pending; f.state = .pending;
f.park = .{ handle = null }; f.park = .{ handle = null };
f.task = null;
f.canceled = Atomic(bool).init(false); f.canceled = Atomic(bool).init(false);
// The completion closure: run the worker, publish the result, wake any parked // The completion closure: run the worker, publish the result, wake any parked
// awaiter. Heap-boxed so it survives until the worker actually runs (deferred // awaiter. Heap-boxed so it survives until the worker actually runs (deferred
@@ -161,6 +175,17 @@ async :: ufcs (io: Io, worker: Closure() -> $R) -> *Future($R) {
await :: ufcs (f: *Future($R)) -> $R !IoErr { await :: ufcs (f: *Future($R)) -> $R !IoErr {
if f.canceled.load(.acquire) { raise error.Canceled; } if f.canceled.load(.acquire) { raise error.Canceled; }
if f.state == .pending { if f.state == .pending {
// ONE awaiter per future (M:1): the single `park` slot records one parked
// fiber, so a second concurrent `await` on the same pending future would
// OVERWRITE the first awaiter's handle and orphan it forever (the worker's
// single `ready(f.park)` wakes only the last). Enforce loudly here, exactly
// as `sched.Task.wait` does — a non-null handle on a still-pending future
// means another fiber is already parked on it. (Fan-in over many futures —
// `race` — registers ONE awaiter across SEPARATE futures, so it is fine.)
if f.park.handle != null {
out("io: await — future already has an awaiter (one awaiter per future in the M:1 model)\n");
io_abort();
}
context.io.suspend_raw(@f.park) catch {}; // Phase 3 propagates Canceled context.io.suspend_raw(@f.park) catch {}; // Phase 3 propagates Canceled
} }
if f.canceled.load(.acquire) { raise error.Canceled; } if f.canceled.load(.acquire) { raise error.Canceled; }
@@ -169,10 +194,14 @@ await :: ufcs (f: *Future($R)) -> $R !IoErr {
return f.value; return f.value;
} }
// `cancel(f)` — request cancellation. Sets the per-future cancel flag + // `cancel(f)` — request cancellation. Sets the per-future cancel flag + marks the
// marks the state so a subsequent `await` raises `.Canceled`. (In the // state so a subsequent `await` raises `.Canceled` (model (a) — cancel rides the
// blocking model the task already ran; cancel still rides the `!` // `!` channel). DOES NOT STOP AN ALREADY-SPAWNED WORKER: under the fiber impl the
// channel — model (a).) // worker fiber is already queued, so `run()` still executes it to completion (its
// side effects happen; it flips `.canceled -> .ready`). The sticky `canceled`
// atomic is the source of truth — subsequent awaits keep raising regardless of
// the state field. True work-cancellation (the worker's next suspend raising
// `Canceled` so it abandons its body) is Phase 3.
cancel :: ufcs (f: *Future($R)) { cancel :: ufcs (f: *Future($R)) {
f.canceled.store(true, .release); f.canceled.store(true, .release);
f.state = .canceled; f.state = .canceled;