fix(zset): pin every handle check, make handles invariant, test not-Sync, document the cost and the FFI thread state
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01VdfEGXA7aJ6ftMsCc9PJze
This commit is contained in:
committed by
Edward Langley
parent
5d7a3423c2
commit
ce6e36112d
@@ -20,7 +20,9 @@
|
||||
* - Not thread-safe, but movable. A ZsCircuit, a ZsRebuild and a ZsZSet may each be handed to
|
||||
* another thread; use each from one thread at a time (a zs_rebuild_step call uses both its
|
||||
* job and its circuit). Callbacks run on whichever thread calls zs_circuit_step or
|
||||
* zs_rebuild_step, so a ctx must be usable from every thread that may step. Streams, inputs and sinks are uint32_t ids local to their circuit; a sink id is
|
||||
* zs_rebuild_step, with THAT thread's thread-local state (Lisp dynamic bindings, errno,
|
||||
* zs_last_error), so a ctx must be usable from every thread that may step.
|
||||
* - Streams, inputs and sinks are uint32_t ids local to their circuit; a sink id is
|
||||
* never a stream id (they are refused as ZS_ERR_BAD_HANDLE), but an id from another circuit
|
||||
* is not detected. A ZsRebuild is stepped only on the circuit that made it (else
|
||||
* ZS_ERR_BAD_HANDLE), even if another circuit now lives at the old address.
|
||||
|
||||
+10
-18
@@ -86,7 +86,7 @@ impl<T> Slot<T> {
|
||||
}
|
||||
|
||||
/// The index of one value in a circuit's arena, typed so a read cannot name the wrong type.
|
||||
struct Id<T>(usize, PhantomData<fn() -> T>);
|
||||
struct Id<T>(usize, PhantomData<fn(T) -> T>);
|
||||
|
||||
impl<T> Clone for Id<T> {
|
||||
fn clone(&self) -> Self {
|
||||
@@ -114,26 +114,18 @@ impl Arena {
|
||||
self.0[id.0].downcast_mut().expect("zset: arena type")
|
||||
}
|
||||
|
||||
/// One value to read and another to write, which are never the same entry.
|
||||
/// One value to read and another, allocated after it, to write.
|
||||
fn read_write<R: 'static, W: 'static>(&mut self, r: Id<R>, w: Id<W>) -> (&R, &mut W) {
|
||||
assert_ne!(
|
||||
r.0, w.0,
|
||||
"zset: an entry cannot be read and written at once"
|
||||
let (lo, hi) = self.0.split_at_mut(w.0);
|
||||
assert!(
|
||||
r.0 < w.0,
|
||||
"zset: the read entry comes before the written one"
|
||||
);
|
||||
let ty = "zset: arena type";
|
||||
if r.0 < w.0 {
|
||||
let (lo, hi) = self.0.split_at_mut(w.0);
|
||||
(
|
||||
lo[r.0].downcast_ref().expect(ty),
|
||||
hi[0].downcast_mut().expect(ty),
|
||||
)
|
||||
} else {
|
||||
let (lo, hi) = self.0.split_at_mut(r.0);
|
||||
(
|
||||
hi[0].downcast_ref().expect(ty),
|
||||
lo[w.0].downcast_mut().expect(ty),
|
||||
)
|
||||
}
|
||||
(
|
||||
lo[r.0].downcast_ref().expect(ty),
|
||||
hi[0].downcast_mut().expect(ty),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -82,6 +82,22 @@ fn an_integrate_stream_holds_its_integral_twice_when_the_tick_is_whole() {
|
||||
assert_eq!(live(), base, "[leak] every key is dropped with the circuit");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn a_sink_adds_its_input_with_one_clone_per_key() {
|
||||
let mut c = Circuit::new();
|
||||
let (input, s) = c.input::<Counted>();
|
||||
let sink = c.output(&s);
|
||||
c.push(&input, keys(N));
|
||||
let before = clones();
|
||||
c.step();
|
||||
assert_eq!(
|
||||
clones() - before,
|
||||
N as usize,
|
||||
"[sink] one clone per key it sums"
|
||||
);
|
||||
assert_eq!(c.take(&sink).len(), N as usize);
|
||||
}
|
||||
|
||||
type E = (Counted, u8);
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
|
||||
@@ -95,10 +95,77 @@ fn a_transaction_begun_on_one_thread_is_rolled_back_on_another() {
|
||||
assert_eq!(c.ticks(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
#[should_panic(expected = "handle from another circuit")]
|
||||
fn a_handle_from_another_circuit_is_refused() {
|
||||
let (mut a, mut b) = (Circuit::new(), Circuit::new());
|
||||
let (input, _) = a.input::<u8>();
|
||||
b.push(&input, Z::singleton(1, 1));
|
||||
trait AmbiguousIfSync<A> {
|
||||
fn some_item() {}
|
||||
}
|
||||
impl<T: ?Sized> AmbiguousIfSync<()> for T {}
|
||||
struct IsSync;
|
||||
impl<T: ?Sized + Sync> AmbiguousIfSync<IsSync> for T {}
|
||||
|
||||
// Fails to build (E0283) if `Circuit` becomes `Sync`.
|
||||
#[test]
|
||||
fn a_circuit_is_not_sync() {
|
||||
let _ = <Circuit as AmbiguousIfSync<_>>::some_item;
|
||||
}
|
||||
|
||||
type Site = fn(&mut Circuit, &Input<u8>, &Stream<Z<u8>>, &Output<u8>);
|
||||
|
||||
/// Two circuits of one shape, so a foreign handle indexes a live slot of the same type.
|
||||
fn twin() -> (Circuit, Input<u8>, Stream<Z<u8>>, Output<u8>) {
|
||||
let mut c = Circuit::new();
|
||||
let (i, s) = c.input::<u8>();
|
||||
let o = c.output(&s);
|
||||
(c, i, s, o)
|
||||
}
|
||||
|
||||
fn panic_text(f: impl FnOnce() + std::panic::UnwindSafe) -> Option<String> {
|
||||
let e = std::panic::catch_unwind(f).err()?;
|
||||
e.downcast_ref::<String>()
|
||||
.cloned()
|
||||
.or_else(|| e.downcast_ref::<&str>().map(|s| s.to_string()))
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn every_call_that_takes_a_handle_refuses_a_foreign_one() {
|
||||
let sites: [(&str, Site); 10] = [
|
||||
("current", |c, _, s, _| {
|
||||
let _ = c.current(s);
|
||||
}),
|
||||
("take", |c, _, _, o| drop(c.take(o))),
|
||||
("push", |c, i, _, _| c.push(i, Z::new())),
|
||||
("unary (map)", |c, _, s, _| {
|
||||
let _ = c.map(s, |k| *k);
|
||||
}),
|
||||
("binary, first operand", |c, _, s, _| {
|
||||
let (_, own) = c.input::<u8>();
|
||||
let _ = c.plus(s, &own);
|
||||
}),
|
||||
("binary, second operand", |c, _, s, _| {
|
||||
let (_, own) = c.input::<u8>();
|
||||
let _ = c.plus(&own, s);
|
||||
}),
|
||||
("output", |c, _, s, _| {
|
||||
let _ = c.output(s);
|
||||
}),
|
||||
("delay", |c, _, s, _| {
|
||||
let _ = c.delay(s);
|
||||
}),
|
||||
("integrate", |c, _, s, _| {
|
||||
let _ = c.integrate(s);
|
||||
}),
|
||||
("distinct", |c, _, s, _| {
|
||||
let _ = c.distinct(s);
|
||||
}),
|
||||
];
|
||||
for (name, site) in sites {
|
||||
let (mut mine, ..) = twin();
|
||||
let (_, fi, fs, fo) = twin();
|
||||
let text = panic_text(std::panic::AssertUnwindSafe(|| {
|
||||
site(&mut mine, &fi, &fs, &fo)
|
||||
}));
|
||||
assert!(
|
||||
text.is_some_and(|t| t.contains("handle from another circuit")),
|
||||
"[foreign] {name} accepted a handle from another circuit"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user