From f20431876b890d4c80ef554771d30afc6fb35b01 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:05:36 -0800 Subject: [PATCH 1/3] fix(ts): log only the waker identities Rust specifies in the task fixture Co-Authored-By: Claude Opus 5.5 --- ts/fixtures/task/expected/futures.ts | 61 ++++++++++++---------------- ts/fixtures/task/src/futures.rs | 7 ++-- 2 files changed, 29 insertions(+), 39 deletions(-) diff --git a/ts/fixtures/task/expected/futures.ts b/ts/fixtures/task/expected/futures.ts index 82f50556b..246a5afa6 100644 --- a/ts/fixtures/task/expected/futures.ts +++ b/ts/fixtures/task/expected/futures.ts @@ -208,6 +208,10 @@ export function polling(waker: rt.Waker): boolean { * `Polling` does. First, wakers made from one `Arc` and from another, of two `Wake` * impls, `Waker::noop`, and pointers to `()`; then, each poll, the task's waker against * its last poll's, a clone, a `Context` of a clone, and a `run` of `Waker::noop` inside. + * Not a clone of an `Arc`'s waker or of `Waker::noop` against its original: the clone's + * vtable is another function's constant, whose address is the same only when the build + * puts both in one codegen unit, as a non-incremental build does and an incremental one + * may not. The executor's vtable is a `static`, so its clones compare reliably. */ export function wakers(rx: Consumer.Consumer, guard: Guard.Guard): rt.Future { return new wakersFuture(rx, guard); @@ -264,84 +268,69 @@ export class wakersFuture { this.#other = new rt.ArcWaker(new rt.Arc(this.#relay(200)), Relay.wake$task_Wake as (a0: rt.Arc) => undefined, (arc3: rt.Arc) => arc3[Symbol.dispose](), Relay.wakeByRef$task_Wake as (a0: rt.Arc) => undefined); this.#plain = new rt.ArcWaker(new rt.Arc(new Plain.Plain(this.#relay(300))), Plain.wake$task_Wake as (a0: rt.Arc) => undefined, (arc4: rt.Arc) => arc4[Symbol.dispose]()); this.#noop = rt.NOOP_WAKER; - const unit: rt.Place = rt.DANGLING_UNIT; - const _29: rt.Waker = this.#a.clone(); - const _26: boolean = rt.willWake(this.#a, _29); - const _31: boolean = rt.willWake(this.#a, this.#b); - const _34: boolean = ((this.#a.data as rt.Place)) === this.#b.data as rt.Place; - const _39: boolean = rt.willWake(this.#a, this.#other); - const _42: boolean = ((this.#a.data as rt.Place)) === this.#other.data as rt.Place; - const _47: boolean = this.#a.vtable === this.#other.vtable; - const _54: boolean = this.#a.vtable === this.#plain.vtable; - const _61: boolean = rt.willWake(this.#a, this.#noop); - const _63: boolean = rt.willWake(this.#noop, rt.NOOP_WAKER); - const _143: rt.Waker = this.#noop; - const _67: rt.Waker = this.#noop.clone(); - this.#checks = [_26, _31, _34, _39, _42, _47, _54, _61, _63, rt.willWake(_143, _67), ((this.#noop.data as rt.Place)) === rt.NULL, rt.willWake(this.#one.value.task, this.#noop), rt.NULL === rt.DANGLING_UNIT, rt.DANGLING_UNIT === rt.DANGLING_UNIT, unit === rt.DANGLING_UNIT, rt.DANGLING_UNIT === rt.DANGLING_UNIT]; - _67[Symbol.dispose](); - _29[Symbol.dispose](); + this.#checks = [rt.willWake(this.#a, this.#b), ((this.#a.data as rt.Place)) === this.#b.data as rt.Place, rt.willWake(this.#a, this.#other), ((this.#a.data as rt.Place)) === this.#other.data as rt.Place, this.#a.vtable === this.#other.vtable, this.#a.vtable === this.#plain.vtable, rt.willWake(this.#a, this.#noop), rt.willWake(this.#noop, rt.NOOP_WAKER), ((this.#noop.data as rt.Place)) === rt.NULL, rt.NULL === rt.DANGLING_UNIT, rt.DANGLING_UNIT === rt.DANGLING_UNIT, rt.DANGLING_UNIT === rt.DANGLING_UNIT, rt.DANGLING_UNIT === rt.DANGLING_UNIT]; put$unit(rt.DANGLING_UNIT, undefined); put$unit(rt.DANGLING_UNIT, undefined); const arg = this.#checks; const args2: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtList(arg, f, rt.fmtBool)]; - const _100: string = rt.fmtFormat(["wakers ", args2[0]]); - Guard.note(this.#guard, _100); + const _88: string = rt.fmtFormat(["wakers ", args2[0]]); + Guard.note(this.#guard, _88); lib_drop$tuple4_task_Waker_task_Waker_task_Waker_task_Waker([this.#a, this.#b, this.#other, this.#plain] as [rt.Waker, rt.Waker, rt.Waker, rt.Waker]); this.#last = undefined as [rt.Place, rt.WakerVTable] | undefined; this.#total = 0; - bb0 = 65; + bb0 = 57; continue; } if (this.#state === 1) { rt.asyncFnResumed(); } else { - bb0 = 70; + bb0 = 62; continue; } - case 65: + case 57: this.#pinned = new recvFuture$kio_Consumer(this.#rx); this.#next = this.#pinned; - const _147: rt.Waker = this.#noop; + const _132: rt.Waker = this.#noop; this.#awaitee = new rt.PollFn((cx: rt.Context): rt.Poll => { const waker: rt.Waker = cx; - const _89: [rt.Place, rt.WakerVTable] | undefined = [waker.data as rt.Place, waker.vtable]; - const same: boolean = rt.eqOption(rt.eqTuple([rt.same, rt.same]))(this.#last, _89); + const _83: [rt.Place, rt.WakerVTable] | undefined = [waker.data as rt.Place, waker.vtable]; + const same: boolean = rt.eqOption(rt.eqTuple([rt.same, rt.same]))(this.#last, _83); this.#last = [waker.data as rt.Place, waker.vtable] as [rt.Place, rt.WakerVTable]; return run$task_Poll_Option_u64_futures_wakers_closure0_closure1_closure0(waker, (): rt.Poll => { const clone: rt.Waker = waker.clone(); const lent: rt.Context = clone; - const inner: [boolean, boolean] = run$tuple2_bool_bool_futures_wakers_closure0_closure1_closure0_closure0(_147, (): [boolean, boolean] => { - return [polling(_147), polling(waker)] as [boolean, boolean]; + const inner: [boolean, boolean] = run$tuple2_bool_bool_futures_wakers_closure0_closure1_closure0_closure0(_132, (): [boolean, boolean] => { + return [polling(_132), polling(waker)] as [boolean, boolean]; }); const _153: boolean = polling(clone); const _183: boolean = rt.willWake(lent, waker); const _222: boolean = polling(waker); const args4: rt.Fmt[] = [(f: rt.Formatter) => rt.fmtBool(same, f), (f: rt.Formatter) => rt.fmtBool(_153, f), (f: rt.Formatter) => rt.fmtBool(_183, f), (f: rt.Formatter) => rt.fmtTuple([(f: rt.Formatter) => rt.fmtBool(inner[0], f), (f: rt.Formatter) => rt.fmtBool(inner[1], f)], f), (f: rt.Formatter) => rt.fmtBool(_222, f)]; - const _810: string = rt.fmtFormat(["same ", args4[0], " clone ", args4[1], " lent ", args4[2], " inner ", args4[3], " after ", args4[4]]); - Guard.note(this.#guard, _810); + const _85: string = rt.fmtFormat(["same ", args4[0], " clone ", args4[1], " lent ", args4[2], " inner ", args4[3], " after ", args4[4]]); + Guard.note(this.#guard, _85); const _04: rt.Poll = this.#next.poll(cx); clone[Symbol.dispose](); return _04; }); }); - bb0 = 70; + bb0 = 62; continue; - case 70: - const _126: rt.Poll = this.#awaitee.poll(_2); - if (_126 !== rt.Pending) { - const value: rt.u64 | undefined = _126; + case 62: + const _114: rt.Poll = this.#awaitee.poll(_2); + if (_114 !== rt.Pending) { + const value: rt.u64 | undefined = _114; if (value === undefined) { - const _138: rt.u64 = this.#total; + const _126: rt.u64 = this.#total; rt.drop(this.#pinned); this.#one[Symbol.dispose](); this.#guard[Symbol.dispose](); this.#rx[Symbol.dispose](); this.#state = 1; - return _138; + return _126; } this.#total = rt.u64Add(this.#total, value); rt.drop(this.#pinned); - bb0 = 65; + bb0 = 57; continue; } this.#state = 3; diff --git a/ts/fixtures/task/src/futures.rs b/ts/fixtures/task/src/futures.rs index 7d560ec69..58a417330 100644 --- a/ts/fixtures/task/src/futures.rs +++ b/ts/fixtures/task/src/futures.rs @@ -132,6 +132,10 @@ fn polling(waker: &Waker) -> bool { /// `Polling` does. First, wakers made from one `Arc` and from another, of two `Wake` /// impls, `Waker::noop`, and pointers to `()`; then, each poll, the task's waker against /// its last poll's, a clone, a `Context` of a clone, and a `run` of `Waker::noop` inside. +/// Not a clone of an `Arc`'s waker or of `Waker::noop` against its original: the clone's +/// vtable is another function's constant, whose address is the same only when the build +/// puts both in one codegen unit, as a non-incremental build does and an incremental one +/// may not. The executor's vtable is a `static`, so its clones compare reliably. pub async fn wakers(mut rx: Consumer, guard: Guard) -> u64 { let relay = |id| Relay { task: Waker::noop().clone(), @@ -144,7 +148,6 @@ pub async fn wakers(mut rx: Consumer, guard: Guard) -> u64 { let noop = Waker::noop(); let unit = Box::into_raw(Box::new(())); let checks = [ - a.will_wake(&a.clone()), a.will_wake(&b), a.data() == b.data(), a.will_wake(&other), @@ -153,9 +156,7 @@ pub async fn wakers(mut rx: Consumer, guard: Guard) -> u64 { ptr::eq(a.vtable(), plain.vtable()), a.will_wake(noop), noop.will_wake(Waker::noop()), - noop.will_wake(&noop.clone()), noop.data().is_null(), - one.task.will_wake(noop), ptr::null::<()>() == ptr::dangling(), ptr::dangling::<()>() == ptr::dangling(), unit.cast_const() == ptr::dangling(), From 1c038f33e19474a55b81d2878b388b0e973b68a9 Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:45:36 -0800 Subject: [PATCH 2/3] chore(quest): claim quest/m0/checks/task-fixture.md Quest-Claim: 7c11b10bb080cbb0 From ba726a332af7ba7b0b3f9f93328c00449e09b62c Mon Sep 17 00:00:00 2001 From: Luke Curley <432854+kixelated@users.noreply.github.com> Date: Sun, 1 Nov 2026 18:45:59 -0800 Subject: [PATCH 3/3] chore(quest): finish the task fixture outside the gate Co-Authored-By: Claude Opus 5.5 --- quest/m0/checks/README.md | 1 - quest/m0/checks/task-fixture.md | 16 ---------------- 2 files changed, 17 deletions(-) delete mode 100644 quest/m0/checks/task-fixture.md diff --git a/quest/m0/checks/README.md b/quest/m0/checks/README.md index c8b288649..67be7a090 100644 --- a/quest/m0/checks/README.md +++ b/quest/m0/checks/README.md @@ -18,7 +18,6 @@ Each child reports its CPU saved, cold and warm, against `origin/main`. ## Required -- [The task fixture outside the gate](/quest/m0/checks/task-fixture.md) - the `task` differential passes on every host, not only in the gate's container - [A cheaper differential](/quest/m0/checks/differential.md) - fixtures' wasm builds at opt-level 1 under single-threaded Node, and `stable_names` reuses `lang`'s translation - [Checks by the diff](/quest/m0/checks/dispatch.md) - `just ci` runs only the projects a change touches, as moq's `sh/dispatch.sh` does, with a nightly `just gate` - [A warm gate](/quest/m0/checks/cache.md) - the land gate builds through mbx, as moq's does diff --git a/quest/m0/checks/task-fixture.md b/quest/m0/checks/task-fixture.md deleted file mode 100644 index 10ed982e7..000000000 --- a/quest/m0/checks/task-fixture.md +++ /dev/null @@ -1,16 +0,0 @@ -# [S] The task fixture outside the gate - -## Goal - -The ts `task` differential passes on every host `just ci` runs on, as it does in the land -gate's container, so an agent's local `just ci` on `main` is green. - -## Plan - -Found by the 2026-11-01 checks audit: on `main` at `8e6a7219`, `task` fails on nixos2 and in -the fixer's container, alone or in parallel, with "35417 cases, 3059 panics, 628 -differences", and passes in the gate. It made the fixer hand back #387, #393, #394, and #397. -Reproduce it, find what differs between the hosts (Node's version, threads, timers, the -environment), and fix the cause, never by loosening the comparison. - -Public API: none.