denoland/deno · error
file busy
Error message
file busy
What it means
Each stdio/FsFile resource is guarded by a RefCell<Option<StdFile>> permitting one operation at a time. Synchronous ops go through with_sync(), which uses try_borrow_mut(); when the cell is held — typically because an async read/write/seek on the same rid is still in flight, or the file was temporarily taken out for a blocking task — the op returns FsError::FileBusy, surfaced in JS as a 'Busy' error (io::ErrorKind::Other) with message 'file busy' (ext/io/fs.rs:71).
Source
Thrown at ext/io/fs.rs:71
}
impl std::error::Error for FsError {}
impl FsError {
pub fn kind(&self) -> io::ErrorKind {
match self {
Self::Io(err) => err.kind(),
Self::FileBusy => io::ErrorKind::Other,
Self::NotSupported => io::ErrorKind::Other,
Self::PermissionCheck(e) => e.kind(),
Self::JoinError(_) => io::ErrorKind::Other,
}
}
pub fn into_io_error(self) -> io::Error {
match self {
FsError::Io(err) => err,
FsError::FileBusy => io::Error::new(self.kind(), "file busy"),
FsError::NotSupported => io::Error::new(self.kind(), "not supported"),
FsError::PermissionCheck(err) => err.into_io_error(),
FsError::JoinError(ref err) => {
io::Error::new(self.kind(), format!("join error: {err}"))
}
}
}
}
impl From<io::Error> for FsError {
fn from(err: io::Error) -> Self {
Self::Io(err)
}
}
impl From<io::ErrorKind> for FsError {
fn from(err: io::ErrorKind) -> Self {
Self::Io(err.into())View on GitHub (pinned to 9ad36f7a2c)
Solutions
- Await all in-flight async ops on the handle before calling its sync methods
- Open a second handle (Deno.open) for genuinely concurrent access
- Pick one style — all sync or all async — per handle
- Catch 'Busy' and retry once after the pending async work drains
Example fix
// before const p = file.read(buf); // async op in flight const n = file.readSync(buf); // Error: file busy // after const p = file.read(buf); const n = await p; const m = file.readSync(buf); // safe: no pending async op
Defensive patterns
Strategy: retry
Validate before calling
// Serialize all access through one queue so sync ops never race async ops
const pending: Promise<unknown>[] = [];
function serialized<T>(op: () => Promise<T>): Promise<T> {
const p = (async () => {
await Promise.all(pending.splice(0));
return op();
})();
pending.push(p);
return p;
}
const n = await serialized(() => file.read(buf)); Try / catch
function isBusy(e: unknown): boolean {
return e instanceof Error && e.name === 'Busy' && /file busy/.test(e.message);
}
let n: number;
for (let i = 0; ; i++) {
try {
n = file.readSync(buf);
break;
} catch (e) {
if (i < 2 && isBusy(e)) {
await new Promise((r) => setTimeout(r, 10)); // let the async op finish
continue;
}
throw e;
}
} Prevention
- Await every async op on a handle before calling its sync methods
- Choose all-sync or all-async per file handle, never both
- Open a second handle for genuinely concurrent access
When it happens
Trigger: file.readSync()/writeSync()/seekSync() on a Deno.FsFile while an async file.read()/write()/... on the same handle has not resolved; issuing a sync op between starting an async op and awaiting it; cloning the resource while a blocking task owns it.
Common situations: Piping a file to stdout while concurrently polling it; libraries mixing sync and async fs APIs on one handle; wrapping the same rid in two abstractions that both act on it.
Related errors
- ERR_INVALID_RETURN_VALUE
- ERR_FS_EISDIR
- too many temp dirs exist
- too many temp files exist
- sockets cannot be copied
AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20).
Data as JSON: /api/errors/084b6019010c79ff.
Report an issue: GitHub.