rerun cycle 18 rather than reconstruct it
El SDK CI - dev / build-and-test (pull_request) Failing after 14m31s
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.
This commit is contained in:
@@ -1,6 +1,21 @@
|
||||
# async — half expressible, and the cycle that was dogma
|
||||
|
||||
**Status: measured on a branch, not merged. Two runs — the first was invalid.**
|
||||
**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
|
||||
|
||||
|
||||
Executable
+62
@@ -0,0 +1,62 @@
|
||||
#!/usr/bin/env bash
|
||||
# async_future.sh — REPLICATION of cycle 18.
|
||||
#
|
||||
# STATUS: replication, not a blind test. The outcomes were already observed on
|
||||
# 2026-08-17 before this harness existed, so the expectations below are not
|
||||
# predictions committed in advance. Its evidentiary value is that the artifact
|
||||
# lives in the repository and a third party can run it — not that it was called
|
||||
# ahead of time. The original run's artifact was written in /tmp and lost when
|
||||
# the worktrees were removed, which broke the chain; this replaces the claim
|
||||
# with something reproducible rather than reconstructing the missing file.
|
||||
#
|
||||
# CLAIM UNDER TEST: @async requires no compiler change. A future is one more
|
||||
# magic-tagged heap object, and el_seam_wrap lets a construct bound AFTER the
|
||||
# build decide whether and when to invoke the body.
|
||||
set -uo pipefail
|
||||
ELC="${1:?usage: async_future.sh <elc>}"
|
||||
LANG_DIR="${2:-$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)/..}"
|
||||
LANG_DIR="$(cd "$LANG_DIR" && pwd)"
|
||||
FIX="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/fixtures/future.c"
|
||||
W=$(mktemp -d); trap 'rm -rf "$W"' EXIT; F=0
|
||||
chk(){ [ "$2" = "$3" ] && printf ' ok %s\n' "$1" || { printf ' FAIL %s\n expected %s got %s\n' "$1" "$2" "$3"; F=$((F+1)); }; }
|
||||
cd "$LANG_DIR"
|
||||
SRCS=$(../scripts/el-runtime-sources.sh runtime)
|
||||
CF="-std=c11 -O2 -rdynamic -I runtime"; LF=""
|
||||
for d in /opt/homebrew/opt/openssl@3 /usr/local/opt/openssl@3; do
|
||||
[ -d "$d" ] && CF="$CF -I $d/include" && LF="-L $d/lib"
|
||||
done
|
||||
LF="$LF -lcurl -lssl -lcrypto -lpthread -lm"
|
||||
|
||||
cat > "$W/p.el" <<'EOF'
|
||||
extern fn el_await(h: Int) -> Int
|
||||
|
||||
fn work(k: Int) -> Int {
|
||||
return k * 2
|
||||
}
|
||||
|
||||
fn main() {
|
||||
let h: Int = work(21)
|
||||
println("CALLER_CONTINUED")
|
||||
let r: Int = el_await(h)
|
||||
println("RESULT " + int_to_str(r))
|
||||
}
|
||||
EOF
|
||||
"$ELC" "$W/p.el" > "$W/p.c" 2>/dev/null
|
||||
cc $CF -o "$W/p" "$W/p.c" "$FIX" $SRCS $LF 2>/dev/null || { echo " FAIL probe did not build"; exit 1; }
|
||||
|
||||
out=$(cd "$W" && ./p 2>&1); rc=$?
|
||||
chk "unbound: no construct, synchronous, correct result" "0" "$rc"
|
||||
chk "unbound: el_await on a non-future passes through, no crash" "1" "$(echo "$out" | grep -c '^RESULT 42$')"
|
||||
|
||||
printf 'work async wrap defer\n' > "$W/c.txt"
|
||||
out=$(cd "$W" && EL_CONSTRUCTS=c.txt ./p 2>&1); rc=$?
|
||||
chk "bound: does not crash" "0" "$rc"
|
||||
chk "bound: the awaited result is correct" "1" "$(echo "$out" | grep -c '^RESULT 42$')"
|
||||
|
||||
wrap=$(echo "$out" | awk '/^WRAP_RETURNED/{print $2}')
|
||||
bend=$(echo "$out" | awk '/^BODY_END/{print $2}')
|
||||
caller_before_body_end=$(echo "$out" | awk '/CALLER_CONTINUED/{c=NR} /^BODY_END/{b=NR} END{print (c<b)?1:0}')
|
||||
chk "bound: the caller continues BEFORE the body finishes" "1" "$caller_before_body_end"
|
||||
chk "bound: the wrap returns in under 10ms while the body takes 50ms" "1" "$([ "${wrap:-999999}" -lt 10000 ] && [ "${bend:-0}" -gt 40000 ] && echo 1 || echo 0)"
|
||||
|
||||
echo; echo " 6 assertions, $((6-F)) passed, $F failed"; exit $F
|
||||
@@ -0,0 +1,66 @@
|
||||
/* future.c — a FUTURE as one more magic-tagged heap object.
|
||||
*
|
||||
* Fixture for tests/integration/async_future.sh. Linked into the probe but
|
||||
* never referenced from El source: everything here is reached only by binding
|
||||
* a construct AFTER the binary exists.
|
||||
*
|
||||
* The claim under test: @async needs no compiler change. el_val_t already
|
||||
* carries List, Map, Geometry, Manifold and Bin as magic-tagged heap pointers;
|
||||
* a future is one more, and el_seam_wrap hands the target the body so it can
|
||||
* decide whether and when to invoke it.
|
||||
*/
|
||||
#include <stdio.h>
|
||||
#include <stdint.h>
|
||||
#include <stdlib.h>
|
||||
#include <pthread.h>
|
||||
#include <unistd.h>
|
||||
#include <time.h>
|
||||
|
||||
typedef int64_t el_val_t;
|
||||
|
||||
#define EL_MAGIC_FUT 0xE1F07000u
|
||||
typedef struct { uint32_t magic; pthread_t th; el_val_t result; int done;
|
||||
el_val_t (*body)(void*); void* env; } ElFuture;
|
||||
|
||||
static long t0_us;
|
||||
static long now_us(void){ struct timespec ts; clock_gettime(CLOCK_MONOTONIC,&ts);
|
||||
return ts.tv_sec*1000000L + ts.tv_nsec/1000; }
|
||||
|
||||
static void* fut_runner(void* v){
|
||||
ElFuture* f = (ElFuture*)v;
|
||||
printf("BODY_START %ld\n", now_us()-t0_us);
|
||||
usleep(50000); /* 50ms, so interleaving is visible */
|
||||
f->result = f->body(f->env);
|
||||
f->done = 1;
|
||||
printf("BODY_END %ld\n", now_us()-t0_us);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* wraps_body target: returns the HANDLE immediately, never the result */
|
||||
el_val_t defer(el_val_t fn, el_val_t con, el_val_t (*b)(void*), void* e){
|
||||
(void)fn; (void)con;
|
||||
t0_us = now_us();
|
||||
ElFuture* f = calloc(1,sizeof(ElFuture));
|
||||
f->magic = EL_MAGIC_FUT; f->body = b; f->env = e;
|
||||
pthread_create(&f->th, NULL, fut_runner, f);
|
||||
printf("WRAP_RETURNED %ld\n", now_us()-t0_us);
|
||||
return (el_val_t)(intptr_t)f;
|
||||
}
|
||||
|
||||
/* el_await — block on the handle and yield the real result.
|
||||
*
|
||||
* NEVER dereference to decide whether a slot is a pointer. el_val_t carries
|
||||
* integers too, so reading ->magic off an integer dereferences that integer AS
|
||||
* AN ADDRESS. The first version of this function did exactly that and
|
||||
* SIGSEGV'd on the unbound path -- sixty seconds after the same defect was
|
||||
* diagnosed elsewhere in the runtime. Check the floor and alignment first. */
|
||||
el_val_t el_await(el_val_t h){
|
||||
if (h < 0x10000) return h; /* small ints / low addresses */
|
||||
if (h & 0x7) return h; /* malloc returns 8-aligned */
|
||||
ElFuture* f = (ElFuture*)(intptr_t)h;
|
||||
if (f->magic != EL_MAGIC_FUT) return h; /* safe to read now */
|
||||
pthread_join(f->th, NULL);
|
||||
el_val_t r = f->result;
|
||||
free(f);
|
||||
return r;
|
||||
}
|
||||
Reference in New Issue
Block a user