Web: a failed render worker start no longer shifts the other workers' slots (#229)

This commit is contained in:
MarekSpodBiedry 2026-10-10 13:57:14 +02:00
parent b70ae1741e
commit d35d0a3dd2
2 changed files with 48 additions and 5 deletions

View File

@ -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");

View File

@ -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
}