Files
improvise/crates/zset-ffi/tests/threads_abi.rs
T

99 lines
2.9 KiB
Rust

//! A circuit moves between threads, one at a time, and its callbacks run on whichever thread
//! steps it (`context/code/zset-ffi.md`, decision 10). Evidence: `context/test-evidence/zset-ffi.md`.
use std::ffi::c_void;
use std::ptr::null_mut;
use std::sync::Mutex;
use std::thread::{self, ThreadId};
use zset_ffi::*;
const OK: i32 = 0;
fn is_send<T: Send>() {}
#[test]
fn the_opaque_handles_are_send() {
is_send::<ZsCircuit>();
is_send::<ZsRebuild>();
}
/// The caller's context: which threads ran the callback.
struct Seen(Mutex<Vec<ThreadId>>);
extern "C" fn keep_all(ctx: *mut c_void, _: *const u8, _: usize) -> i32 {
// SAFETY: `ctx` is the `Seen` the test keeps alive across every step.
unsafe { &*(ctx as *const Seen) }
.0
.lock()
.unwrap()
.push(thread::current().id());
1
}
/// A raw handle moved to another thread; the test is the one-at-a-time owner.
struct Moved(*mut ZsCircuit);
unsafe impl Send for Moved {}
fn push_key(c: *mut ZsCircuit, input: u32, key: &[u8]) {
let mut z = null_mut();
assert_eq!(unsafe { zs_zset_new(&mut z) }, OK);
assert_eq!(unsafe { zs_zset_insert(z, key.as_ptr(), key.len(), 1) }, OK);
assert_eq!(unsafe { zs_circuit_push(c, input, z) }, OK);
unsafe { zs_zset_free(z) };
}
fn step_and_count(c: *mut ZsCircuit, sink: u32) -> usize {
assert_eq!(unsafe { zs_circuit_step(c) }, OK);
let mut out = null_mut();
assert_eq!(unsafe { zs_circuit_take(c, sink, &mut out) }, OK);
let mut n = 0;
let mut buf = [0u8; 64];
assert_eq!(
unsafe { zs_zset_encode(out, buf.as_mut_ptr(), buf.len(), &mut n) },
OK
);
unsafe { zs_zset_free(out) };
n / 13
}
#[test]
fn a_circuit_stepped_on_two_threads_runs_its_callbacks_on_the_thread_that_steps() {
let seen = Seen(Mutex::new(Vec::new()));
let ctx = &seen as *const Seen as *mut c_void;
let (mut c, mut input, mut kept, mut sink) = (null_mut(), 0, 0, 0);
assert_eq!(unsafe { zs_circuit_new(&mut c) }, OK);
assert_eq!(unsafe { zs_circuit_input(c, &mut input) }, OK);
assert_eq!(
unsafe { zs_circuit_filter(c, input, Some(keep_all), ctx, &mut kept) },
OK
);
assert_eq!(unsafe { zs_circuit_output(c, kept, &mut sink) }, OK);
push_key(c, input, b"a");
assert_eq!(step_and_count(c, sink), 1);
let here = thread::current().id();
let moved = Moved(c);
let there = thread::spawn(move || {
let moved = moved;
push_key(moved.0, input, b"b");
let n = step_and_count(moved.0, sink);
(n, thread::current().id(), moved)
})
.join()
.unwrap();
assert_eq!(
there.0, 1,
"[threads] the second thread's step saw its own push"
);
assert_ne!(there.1, here);
let ids = seen.0.lock().unwrap().clone();
assert_eq!(
ids,
vec![here, there.1],
"[threads] each callback ran where it was stepped"
);
unsafe { zs_circuit_free(there.2.0) };
}