diff --git a/apps/lightcraft-web/src/wire.rs b/apps/lightcraft-web/src/wire.rs index 4796740e..a06d3ab2 100644 --- a/apps/lightcraft-web/src/wire.rs +++ b/apps/lightcraft-web/src/wire.rs @@ -332,11 +332,46 @@ impl CacheWatch { } } +/// Hands out the positions that render workers will have in the main thread's worker list. +/// A worker that fails to start gets no position, so the position a callback captures stays the +/// index of its own worker in that list (#229). +#[derive(Debug, Default)] +pub struct WorkerSlots { + next: usize, +} + +impl WorkerSlots { + /// The position the worker being started will have, if it comes up. + pub fn peek(&self) -> usize { + self.next + } + + /// The worker at [`Self::peek`] came up and was added to the list. + pub fn commit(&mut self) { + self.next += 1; + } +} + #[cfg(test)] mod tests { use super::*; use lightcraft_engine::Session; + // Issue #229: a failed worker attempt must not shift the positions of the workers after it + #[test] + fn a_failed_worker_attempt_leaves_no_gap_in_the_slots() { + let mut slots = WorkerSlots::default(); + let mut taken = Vec::new(); + for attempt_ok in [false, true, true, true] { + let i = slots.peek(); + if attempt_ok { + taken.push(i); + slots.commit(); + } + } + assert_eq!(taken, vec![0, 1, 2]); + } + #[test] fn clearing_index_changes_disk_namespace_and_survives_restart() { let key = lightcraft_preview::hash_bytes(b"thumbnail"); diff --git a/apps/lightcraft-web/src/workers.rs b/apps/lightcraft-web/src/workers.rs index f321bdb9..3ba58d37 100644 --- a/apps/lightcraft-web/src/workers.rs +++ b/apps/lightcraft-web/src/workers.rs @@ -27,7 +27,7 @@ use wasm_bindgen::JsCast; use wasm_bindgen::prelude::*; use crate::backend::Backend; -use crate::wire::{CacheWatch, ThumbIndex, WireJob, WorkerCore, thumb_storage_key}; +use crate::wire::{CacheWatch, ThumbIndex, WireJob, WorkerCore, WorkerSlots, thumb_storage_key}; /// Storage key of the thumbnail index. pub const THUMB_INDEX: &str = "thumbs/index.json"; @@ -245,14 +245,17 @@ impl Workers { log::warn!("render workers: no compiled module on the page (window.lightcraftModule); rendering on the main thread"); return w; } - for i in 0..n { + let mut slots = WorkerSlots::default(); + for attempt in 0..n { + // The callbacks name their worker by its place in `workers`; a failed attempt takes none. + let i = slots.peek(); let opts = web_sys::WorkerOptions::new(); opts.set_type(web_sys::WorkerType::Module); - opts.set_name(&format!("lightcraft-render-{i}")); + opts.set_name(&format!("lightcraft-render-{attempt}")); let worker = match web_sys::Worker::new_with_options("./worker.js", &opts) { Ok(x) => x, Err(e) => { - log::warn!("render worker {i}: {e:?}"); + log::warn!("render worker {attempt}: {e:?}"); continue; } }; @@ -268,10 +271,15 @@ impl Workers { set(&init, "module", &module); set(&init, "store", &store.into()); if let Err(e) = worker.post_message(&init) { - log::warn!("render worker {i}: {e:?}"); + log::warn!("render worker {attempt}: {e:?}"); + // It never joins the list, so it must not answer to the index of the next worker. + worker.set_onmessage(None); + worker.set_onerror(None); + worker.terminate(); continue; } w.0.borrow_mut().workers.push(W { worker, ready: false, dead: false, busy: None }); + slots.commit(); } w }