tokio-rs/tokio · error · io::Error

other file operation is pending, call poll_complete before s

Error message

other file operation is pending, call poll_complete before start_seek

What it means

Runtime error from `File::start_seek` (file.rs:681). Tokio's `File` refuses to begin a seek while it is in `State::Busy` — a prior read/write operation has not yet completed. The API requires the previous operation to be drained via `poll_complete` before a new `start_seek`. This is an API-usage violation, not an I/O failure.

Source

Thrown at tokio/src/fs/file.rs:681

                            if let Ok(pos) = result {
                                inner.pos = pos;
                            }
                            continue;
                        }
                    }
                }
            }
        }
    }
}

impl AsyncSeek for File {
    fn start_seek(self: Pin<&mut Self>, mut pos: SeekFrom) -> io::Result<()> {
        let me = self.get_mut();
        let inner = me.inner.get_mut();

        match inner.state {
            State::Busy(_) => Err(io::Error::new(
                io::ErrorKind::Other,
                "other file operation is pending, call poll_complete before start_seek",
            )),
            State::Idle(ref mut buf_cell) => {
                let mut buf = buf_cell.take().unwrap();

                // Factor in any unread data from the buf
                if !buf.is_empty() {
                    let n = buf.discard_read();

                    if let SeekFrom::Current(ref mut offset) = pos {
                        *offset += n;
                    }
                }

                let std = me.std.clone();

                inner.state = State::Busy(spawn_blocking(move || {

View on GitHub (pinned to 625954f365)

Solutions

  1. Fully `await` the previous read/write operation before issuing a seek on the same `File`.
  2. Prefer the `AsyncSeekExt`/`AsyncReadExt`/`AsyncWriteExt` combinators which enforce the operation ordering.
  3. Do not share one `File` across concurrent tasks; serialize access through a single owner or a channel.
  4. If polling manually, call `poll_complete` to finish the pending op before `start_seek`.

Example fix

// before: seeking while a read future is still pending
let n = file.read(&mut buf).await?; // ensure this completes first
file.seek(SeekFrom::Start(0)).await?; // OK only after the read resolved

// after: always await prior ops before seeking (serialize on one owner)
let n = file.read(&mut buf).await?;
file.seek(SeekFrom::Start(0)).await?;
file.write(&data).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Enforce single-owner access to the File; never start a seek while a
// read/write future is still pending. Await each op to completion first.
// (Compile-time discipline; no runtime check exists inside File itself.)

Prevention

When it happens

Trigger: Calling `AsyncSeekExt::seek` (which calls `start_seek`) on a `File` while a previous `poll_read`/`poll_write` operation is still pending — e.g. interleaving seeks with an in-flight read/write future on the same `File`, or sharing one `File` across tasks without serialization.

Common situations: Mixing `AsyncReadExt`/`AsyncWriteExt` and `AsyncSeekExt` calls on the same `File` without fully awaiting each; manually polling the `File`; sharing a `File` across tasks concurrently (Tokio files are not `Sync`-shareable for concurrent ops).

Related errors


AI-assisted analysis of tokio-rs/tokio@625954f365 (2026-08-11). Data as JSON: /api/errors/47726d0ce53f05a5. Report an issue: GitHub.