writeonce/tests/regress/lang-41/shard-settle-crash.wo
shoney.arickathil b9ce271b3d fix(lang41): an unadopted shard must not impersonate shard 0
- root cause: a worker's runtime is initialised lazily on first fiber
  adoption, and rt.shard_id is stamped only there — but INBOX_READY[i]
  is set at thread creation. A shard that never adopts is still settled
  at shutdown, carrying rt.shard_id 0 from the memset
- it then impersonated shard 0: wo_drop_obj saw 0 == 0 for anything the
  primary allocated, took the "we are home" branch instead of routing,
  and called class_free against rt->classes, which lazy init never
  filled. &rt->classes[class_id] off a NULL base is the faulting read
- fix: stamp the runtime's real identity at thread creation. An
  uninitialised shard owns nothing, so its true id makes every payload
  correctly foreign and routes it to an owner that can free it
- ASan could not name this: the arena is one hand-managed malloc block,
  so intra-arena reuse is invisible and it surfaces as a bare SEGV
- pinned by tests/regress/lang-41, driven from db-actor-accept. Needs
  multiple shards (the corpus runner pins WO_SHARDS=1) and the ASan
  build. SEGVs twice per run unfixed, clean fixed
- the HANG is a separate defect and is NOT fixed: with this in place the
  harness stops losing whole sections, but idempotent-stop-2 still
  fires ~1 run in 6. The story records where to look

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
(cherry picked from commit 9dca0b4b4727b976d326b29cb4c6522b62d48a73)
2026-09-15 01:15:30 +02:00

61 lines
1.4 KiB
Text

use json
@table(name: "rows", index: [k])
class Row {
k: Text
v: Text
}
class Ask { n: Int }
class Go { n: Int }
typedef Stored = {
status: Int,
body: Text
}
-- The shared actor: allocates inside receive, churns insert+delete on ONE key.
class Churn {
fn receive(msg: Ask) -> Int {
let i = 0;
while i < 5 {
let enc = json.encode(Stored { status: 200 + i, body: "payload-${msg.n}-${i}" });
insert Row { k: "one", v: enc };
let hits = from r in Row where r.k == "one" take 1 select r;
if len(hits) > 0 {
let back = json.decode(hits[0].v) as Stored;
if back != nil { i = i + back.status - back.status; }
delete hits[0];
}
i = i + 1;
}
return msg.n;
}
}
-- Clients: each parks on `call` into the SAME churn actor, concurrently.
class Client {
target: actor Ask
fn receive(msg: Go) -> Int {
let j = 0;
while j < 8 {
let r = call(self.target, Ask { n: msg.n * 100 + j });
j = j + 1;
}
return 0;
}
}
fn main() -> Int {
let churn: actor Ask = spawn Churn {};
let c1: actor Go = spawn Client { target: churn };
let c2: actor Go = spawn Client { target: churn };
let c3: actor Go = spawn Client { target: churn };
let c4: actor Go = spawn Client { target: churn };
send(c1, Go { n: 1 });
send(c2, Go { n: 2 });
send(c3, Go { n: 3 });
send(c4, Go { n: 4 });
print("dispatched");
return 0;
}