Fix #5152: placed-document bitmap test no longer races its worker

a_ready_bitmap_draws_again_where_its_file_shows assumed the first frame shows placeholders,
but the background worker can make the tiny test bitmaps between scheduling and the frame's
second lookup, so the frame already showed them ("the bitmaps show" failed on FreeBSD CI).
The test's loader now waits until that frame is drawn.
This commit is contained in:
Don Omondi
2026-11-02 14:36:14 +07:00
committed by bflatastic
parent 87979644cd
commit f4979c7e54
+32 -1
View File
@@ -53,6 +53,34 @@ fn placed_node(id: u64, key: &str, width: f64, height: f64, xf: Affine) -> Node
/// Held by the tests that draw placed documents from bitmaps, which share the bitmaps, their
/// generation and the jobs being made: one test's bitmaps don't push out another's, and
/// [`settle`] waits for its own.
/// Held shut, [`gated_blue_page`] makes the background worker wait before reading a file, so a
/// frame drawn meanwhile shows the placeholders whatever the scheduler does (#5152: a fast worker
/// could finish between scheduling a bitmap and the frame looking it up again).
static GATE_SHUT: std::sync::atomic::AtomicBool = std::sync::atomic::AtomicBool::new(false);
/// The thread drawing while the gate is shut: it never waits (a file read there is drawn exactly).
static GATE_HOLDER: std::sync::Mutex<Option<std::thread::ThreadId>> = std::sync::Mutex::new(None);
/// [`blue_page`] once the gate opens (at most 10 s), except on the thread that shut it.
fn gated_blue_page(p: &PlacedDocument, bytes: &[u8]) -> Option<(Document, Node, Rect)> {
let here = std::thread::current().id();
let holder = *GATE_HOLDER.lock().unwrap_or_else(std::sync::PoisonError::into_inner);
let t = std::time::Instant::now();
while holder != Some(here) && GATE_SHUT.load(std::sync::atomic::Ordering::SeqCst) && t.elapsed().as_secs() < 10 {
std::thread::sleep(std::time::Duration::from_millis(1));
}
blue_page(p, bytes)
}
/// Shut the gate for the calling thread's frames.
fn shut_gate() {
*GATE_HOLDER.lock().unwrap_or_else(std::sync::PoisonError::into_inner) = Some(std::thread::current().id());
GATE_SHUT.store(true, std::sync::atomic::Ordering::SeqCst);
}
fn open_gate() {
GATE_SHUT.store(false, std::sync::atomic::Ordering::SeqCst);
}
fn serial() -> std::sync::MutexGuard<'static, ()> {
static SERIAL: std::sync::Mutex<()> = std::sync::Mutex::new(());
SERIAL.lock().unwrap_or_else(std::sync::PoisonError::into_inner)
@@ -231,7 +259,7 @@ fn a_bitmap_that_cant_be_made_is_not_asked_for_again() {
#[test]
fn a_ready_bitmap_draws_again_where_its_file_shows() {
let _serial = serial();
vectorcraft_doc::placed_document::set_loader(blue_page);
vectorcraft_doc::placed_document::set_loader(gated_blue_page);
let mut d = Document::new(100.0, 100.0);
for key in ["area-a", "area-b"] {
d.images.insert(key.into(), ImageBlob::new("application/json", format!("{{\"doc\": \"{key}\"}}").into_bytes()));
@@ -251,7 +279,10 @@ fn a_ready_bitmap_draws_again_where_its_file_shows() {
let opts = RenderOptions { background: Some([255, 255, 255, 255]), progressive_placed: true, ..Default::default() };
let mut r = Renderer::new();
let generation = placed_document::generation();
// The first frame shows the placeholders: the worker waits until it is drawn.
shut_gate();
let before = r.render(&d, 100, 100, Affine::IDENTITY, &opts);
open_gate();
settle();
let made = placed_document::made_between(generation, placed_document::generation()).expect("known");
let files = |keys: &[&str]| keys.iter().map(|k| k.to_string()).collect::<std::collections::HashSet<_>>();