atuinsh/atuin · error
master lock
Error message
master lock
What it means
`Subshell::into_parts` locks the PTY master's internal `Mutex` to split it into reader/writer/resizer parts, and `.expect("master lock")` panics when the lock is poisoned. A std `Mutex` is poisoned when some thread panicked while holding it, so this panic is always a downstream symptom of an earlier panic elsewhere; the function's own doc comment names fd exhaustion in `try_clone_reader` as the realistic first failure.
Source
Thrown at crates/atuin-lab-share/src/subshell.rs:119
/// boxed PTY master survives inside the resizer closure; the reader and
/// writer carry their own dups of the master fd.
///
/// The subshell owns its child outright, so `stop` kills it, and `wait`
/// applies the exit-code mapping the session has always used: the child's
/// own code when the wait succeeds (non-`i32` codes clamp to 1), 0 when
/// it fails. Everything else is the subshell's defaults: no bootstrap (a
/// fresh shell starts blank), synthetic query answers (the compositor
/// swallows its output, so nothing else would reply), and hub resizes
/// applied to the child PTY.
///
/// # Panics
///
/// Panics if the reader cannot be cloned (the process is out of file
/// descriptors) or the writer was already taken — impossible on a freshly
/// spawned subshell, which is the only caller.
fn into_parts(self) -> crate::Result<SourceParts> {
let (reader, writer) = {
let master = self.master.lock().expect("master lock");
(
master.try_clone_reader().expect("clone pty reader"),
master.take_writer().expect("take pty writer"),
)
};
let resizer = PtyResizer(self.master);
// Terminates the child without owning it, so the session can stop the
// child while `wait` runs on the blocking pool.
let mut killer = self.child.clone_killer();
let mut child = self.child;
Ok(SourceParts {
reader: Box::new(ByteReader(reader)),
writer,
resizer: Box::new(move |size| resizer.resize(size)),
stop: Box::new(move || {
// Best-effort, exactly as the session's kill switch always
// treated it: a failed kill still reaches `wait`'s mapping.
let _ = killer.kill();View on GitHub (pinned to 15fe1318f1)
Solutions
- Fix the first panic: resolve the fd exhaustion or error that poisoned the lock (check `ulimit -n`, count entries in /proc/self/fd)
- Recover instead of panicking: `self.master.lock().unwrap_or_else(|e| e.into_inner())` - the PTY state is still usable
- Audit every code path that locks the master so no `.expect`/panic can fire while it is held
Example fix
// before
let master = self.master.lock().expect("master lock");
// after
let master = self.master
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()); Defensive patterns
Strategy: fallback
Validate before calling
// Detect poisoning before it panics, and log the root cause
if let Ok(master) = self.master.try_lock() {
// healthy path
drop(master);
} else if self.master.is_poisoned() {
log::warn!("master lock poisoned by an earlier panic; recovering");
} Type guard
fn master_usable(master: &Mutex<Master>) -> bool {
!master.is_poisoned()
} Try / catch
// Recover the guarded data instead of propagating the panic:
// the PTY state protected by the mutex is still valid
let master = self
.master
.lock()
.unwrap_or_else(|poisoned| poisoned.into_inner()); Prevention
- Never let a panic escape while holding a shared Mutex - convert expects into Results inside the critical section
- Log the FIRST panic; a poisoned lock is always a symptom, not the disease
- Keep critical sections around the PTY master minimal so fewer panics can fire inside them
When it happens
Trigger: Any thread panicking while holding the master's lock - most plausibly `try_clone_reader` failing with EMFILE inside a prior `into_parts` call, or a panic inside a resize/write path that locks the same master - followed by another attempt to lock it.
Common situations: Long-lived lab-share sessions leaking file descriptors until `try_clone_reader` panics; a session torn down mid-setup; user code that locks the same `Master` and panics while holding it.
Related errors
- clone pty reader
- take pty writer
- issue in stats average query
- issue in stats exits query
- issue in stats day of week query
AI-assisted analysis of atuinsh/atuin@15fe1318f1 (2026-08-19).
Data as JSON: /api/errors/26e438a0536832b8.
Report an issue: GitHub.