Hmbown/CodeWhale · error

Frame lock failed

Error message

Frame lock failed

What it means

run_world, the pet-render world simulation loop, keeps a bounded ring of the last 16 rendered frames behind a std::sync::Mutex. This error is thrown when locking that mutex returns a poisoning error, meaning another thread panicked while holding the frame-lock and left the shared state inconsistent. The world loop aborts rather than continue rendering from possibly-corrupt frame data.

Solutions

  1. Find and fix the panic in the thread that poisoned the frames mutex (the original panic message is printed before this error appears).
  2. Restart the world/serve process to clear the poisoned lock.
  3. If intentional recovery is desired, use frames.lock().unwrap_or_else(|p| p.into_inner()) instead of failing, accepting the possibly-partial state.
  4. Guard the frame-producing code against panics (catch_unwind or fix indexing/bounds errors) so the lock never poisons.

Example fix

// before
let mut output = frames
    .lock()
    .map_err(|_| anyhow::anyhow!("Frame lock failed"))?;
// after — recover from a poisoned lock instead of aborting the world loop
let mut output = frames.lock().unwrap_or_else(|poisoned| poisoned.into_inner());
Defensive patterns

Strategy: try-catch

Type guard

fn frames_healthy(frames: &Mutex<VecDeque<Value>>) -> bool { !frames.is_poisoned() }

Try / catch

match frames.lock() {
    Ok(guard) => { /* push frame */ }
    Err(poisoned) => {
        let guard = poisoned.into_inner(); // or log + abort world loop
    }
}

Prevention

When it happens

Trigger: Calling run_world (from serve) while another thread holding the frames Mutex panics; the subsequent frames.lock() call in the tick loop returns Err(PoisonError), which is mapped to this anyhow error.

Common situations: A panic inside a rendering/consumer thread that also touches the frame buffer (e.g. a bug in frame serialization or a JSON build panic); most common during development with panicking frame consumers, or under a genuinely concurrent world server with a buggy frame reader.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15). Data as JSON: /api/errors/bdee008ddca16dbe. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/tui/pet_watch/owner.rs:603

                }
                pose["style"]["alpha"] = json!(
                    (pose["style"]["alpha"].as_f64().unwrap_or(0.5) * saved.appearance.brightness)
                        .clamp(0.0, 1.0)
                );
            }
            frame["appearance"] = json!(saved.appearance);
            frame["producerConnected"] = json!(producer.is_some());
            frame["storageAvailable"] = json!(!storage_error);
            frame["audioOwner"] = json!(audio.as_ref().map(|(id, _)| id));
            frame["audioUnavailable"] = json!(audio_error);
            measurements.push_back(started.elapsed().as_secs_f64() * 1000.0);
            if measurements.len() > 300 {
                measurements.pop_front();
            }
            frame["performance"] = json!({"worldHz":30,"frames":ticks,"uptimeSeconds":origin.elapsed().as_secs_f64(),"workMs":measurements.back()});
            let mut output = frames
                .lock()
                .map_err(|_| anyhow::anyhow!("Frame lock failed"))?;
            output.push_back(frame);
            if output.len() > 16 {
                output.pop_front();
            }
        }
        if last_save.elapsed() >= Duration::from_secs(1) {
            storage_error = save(&context, &mut saved, &mut store).is_err();
            last_save = Instant::now();
        }
        let work = match rx.recv_timeout(Duration::from_millis(2)) {
            Ok(work) => work,
            Err(mpsc::RecvTimeoutError::Timeout) => continue,
            Err(mpsc::RecvTimeoutError::Disconnected) => {
                save(&context, &mut saved, &mut store)?;
                return Ok(());
            }
        };
        match work {

View on GitHub (pinned to 433685b202)