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

  1. Await all in-flight async ops on the handle before calling its sync methods
  2. Open a second handle (Deno.open) for genuinely concurrent access
  3. Pick one style — all sync or all async — per handle
  4. 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

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


AI-assisted analysis of denoland/deno@9ad36f7a2c (2026-08-20). Data as JSON: /api/errors/084b6019010c79ff. Report an issue: GitHub.