Skip to content

feat(tasks): must_not_yield — assert a critical region is atomic (#488)#528

Merged
InauguralPhysicist merged 1 commit into
mainfrom
feat/must-not-yield-488
Jul 9, 2026
Merged

feat(tasks): must_not_yield — assert a critical region is atomic (#488)#528
InauguralPhysicist merged 1 commit into
mainfrom
feat/must-not-yield-488

Conversation

@InauguralPhysicist

Copy link
Copy Markdown
Collaborator

What

The other half of #488 (the lib/sync locks landed in #489). A region under cooperative scheduling is atomic — mutual exclusion for free — only while it issues no yield, but nothing marked such a region. So a task_yield (or a builtin that yields internally) introduced later broke atomicity silently: the program still ran and usually looked fine, failing only as a rare, seed-dependent anomaly.

must_not_yield of fn runs fn of null as an atomic critical section and asserts it.

How

A new g_no_yield_depth is incremented for the region; while nonzero, every task builtin that would actually suspend raises value instead:

  • task_yield (when a scheduler is active),
  • blocking task_recv / blocking task_join (a live target),
  • task_sleep.

Non-suspending ops stay legal (task_try_recv, task_send, joining an already-finished task) — the guard sits on the actual-suspend path, so it never false-fires. The depth is only ever nonzero for the running task (a yield can't happen inside), so a plain counter is safe, and it's balanced even when the body raises (call_eigs_fn returns control either way). Nestable.

Surfaced by the eddy consumer: its snapshot-isolation commit loop is correct only because it contains no yield.

Verification

  • tests/test_tasks.eigs: a yield inside is caught (value) and the depth is restored (a later yield works); a non-yielding region returns its body result; non-suspending ops are allowed; a blocking task_recv inside raises.
  • Full suite green release + ASan (detect_leaks=1) + jit-smoke: 2703/2703, 0 failed, no leak-tally increase.

Docs

Closes #488

🤖 Generated with Claude Code

Comment thread src/lint.c
obj->data.ident.name, (op[0] == '=' ? "==" : op), lit, sugg);
}

static void w018_scan(ASTNode *n, LintContext *ctx, W018Scope *sc) {
The other half of #488 (the lib/sync locks landed in #489). A region under
cooperative scheduling is atomic — mutual exclusion for free — ONLY while
it issues no yield, but nothing marked such a region, so a task_yield (or a
builtin that yields internally) introduced later broke atomicity SILENTLY:
the program still ran and usually looked fine, failing only as a rare,
seed-dependent anomaly.

`must_not_yield of fn` runs `fn of null` as an atomic critical section and
asserts it. A new g_no_yield_depth is incremented for the region; while
nonzero, every task builtin that would actually SUSPEND raises `value`
instead:
  - task_yield (when a scheduler is active),
  - blocking task_recv / blocking task_join (a live target),
  - task_sleep.
Non-suspending ops stay legal in a region (task_try_recv, task_send,
joining an already-finished task) — the guard sits on the actual-suspend
path, so it never false-fires. The depth is only ever nonzero for the
running task (a yield can't happen inside), so a plain counter is safe, and
it is balanced even when the body raises (call_eigs_fn returns control
either way). Nestable.

Surfaced by the eddy consumer: its snapshot-isolation commit loop is
correct only because it contains no yield.

Regression (tests/test_tasks.eigs): a yield inside is caught (`value`) and
the region depth is restored (a later yield works); a non-yielding region
returns its body result; non-suspending ops are allowed; a blocking
task_recv inside raises. Documented in docs/BUILTINS.md; discoverability
gate satisfied.

Full suite green release + ASan (leaks on) + jit-smoke, 2703/2703, no
leak-tally increase.

Closes #488

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@InauguralPhysicist InauguralPhysicist force-pushed the feat/must-not-yield-488 branch from 80f075d to 5908412 Compare July 9, 2026 22:47
@InauguralPhysicist InauguralPhysicist merged commit cd734cb into main Jul 9, 2026
@InauguralPhysicist InauguralPhysicist deleted the feat/must-not-yield-488 branch July 9, 2026 22:48
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

lib/sync: a way to mark a cooperative-task region as must-not-yield (atomic/critical)

2 participants