511db25230
El SDK CI - dev / build-and-test (pull_request) Failing after 14m31s
The async/future measurements were produced by a C stub in /tmp, and that artifact was destroyed when the session worktrees were removed. The log then asserted results with nothing behind them -- a claim inside an evidence record, which is exactly what turns a chain of custody into a pile. Rerun, not reconstructed. Rebuilding the missing file would have been a fabrication with a fresh timestamp; rerunning produces new evidence with its own. lang/tests/integration/fixtures/future.c the future, as a tagged heap object lang/tests/integration/async_future.sh the harness, 6/6 ok unbound: synchronous, correct result ok unbound: el_await on a non-future passes through, no crash ok bound: does not crash ok bound: the awaited result is correct ok bound: the caller continues BEFORE the body finishes ok bound: wrap returns in <10ms while the body takes 50ms LABELLED AS A REPLICATION. The outcomes were already known when this harness was written, so its expectations are NOT predictions committed in advance. Its evidentiary value is that a third party can reproduce it, not that it was called ahead of time. Recording it as anything stronger would corrupt the record it is meant to repair. The fixture also carries the P5 defect and its fix in a comment: the first el_await dereferenced ->magic off an unvalidated slot and SIGSEGV'd on the unbound path, sixty seconds after the same defect was diagnosed elsewhere in the runtime.
89 lines
3.6 KiB
Markdown
89 lines
3.6 KiB
Markdown
# async — half expressible, and the cycle that was dogma
|
|
|
|
**Status: replicated and corroborated. Three runs — the first was invalid.**
|
|
|
|
> **Chain of custody note, 2026-08-17.** The original measurements were produced
|
|
> by a C stub written in `/tmp`, and that artifact was destroyed when the session
|
|
> worktrees were removed. For a period this file asserted results with nothing
|
|
> behind them — a claim inside an evidence record, which is the defect that turns
|
|
> a chain into a pile. It was **rerun**, not reconstructed: reconstructing the
|
|
> missing file would have been a fabrication with a fresh timestamp.
|
|
>
|
|
> The fixture now lives at `lang/tests/integration/fixtures/future.c` and the
|
|
> harness at `lang/tests/integration/async_future.sh`, so a third party can
|
|
> reproduce this without taking my word for it. **6/6.**
|
|
>
|
|
> The replication is labelled as such: the outcomes were already known when the
|
|
> harness was written, so its expectations are not predictions committed in
|
|
> advance. Its value is reproducibility, not foresight.
|
|
|
|
## The first attempt was DOGMA, not science
|
|
|
|
I had just finished arguing that `@async` was expressible, then ran something to
|
|
confirm it. **No prediction was committed.** The test was rigged in a way that
|
|
should have been visible while writing it:
|
|
|
|
```c
|
|
pthread_create(&t,NULL,runner,NULL); pthread_join(t,NULL);
|
|
```
|
|
|
|
`join` immediately after `create` — the caller blocks until the body finishes.
|
|
That is a thread round-trip, not deferral. And the test printed the word
|
|
`DEFERRED` itself: I wrote the conclusion into the output and read it back.
|
|
|
|
```
|
|
Ishikawa on the rigged test
|
|
method ran after concluding, not to decide
|
|
machine nothing forces a prediction before execution
|
|
material the assertion was written into the output string
|
|
measurement no falsification criterion existed, so nothing could fail
|
|
root cause the test was authored by the party holding the conclusion,
|
|
with no commitment made before it ran
|
|
```
|
|
|
|
Discarded and re-run properly.
|
|
|
|
## Second run — predictions committed first
|
|
|
|
```
|
|
P1 the caller proceeds while the body runs expect TRUE
|
|
P2 interleaving is observable in timestamps expect TRUE
|
|
P3 the result cannot be retrieved — one 64-bit slot, no
|
|
future type, so the wrap either blocks or returns
|
|
something that is not the result expect TRUE
|
|
P4 therefore HALF expressible: fire-and-forget yes, await no expect TRUE
|
|
```
|
|
|
|
## Results — 4/4
|
|
|
|
```
|
|
[ 18 us] wrap RETURNS to caller
|
|
[ 29 us] body START
|
|
caller continues, got 0
|
|
[ 50176 us] body END (computed 42)
|
|
caller done
|
|
```
|
|
|
|
The caller got **0, not 42**. Both of my earlier claims were wrong in opposite
|
|
directions: "not expressible" was too strong — fire-and-forget works today,
|
|
bound after the build, no compiler change. "Expressible" was too strong the
|
|
other way.
|
|
|
|
## Follow-on cycle — a future is one more tagged object
|
|
|
|
```
|
|
P1 el_val_t already carries tagged heap objects TRUE 5 magic tags exist
|
|
P2 a future is one more TRUE
|
|
P3 the caller awaits and gets 42 TRUE
|
|
P4 ZERO compiler changes TRUE runtime C + one binding
|
|
P5 the unbound path still works FALSE SIGSEGV
|
|
```
|
|
|
|
**P4 is the result.** `@async` — called unexpressible for hours — needs no
|
|
compiler change. A future is one more magic-tagged heap object; `defer` returns
|
|
the handle, `el_await` blocks.
|
|
|
|
**P5 is the failure that mattered.** Sixty seconds after diagnosing
|
|
`let s: String = 42` as an arbitrary read, I wrote the identical defect into
|
|
`el_await`: reading `->magic` off an unvalidated slot. That opened cycle 19.
|