AlistGo/alist · error
read on closed file
Error message
read on closed file
What it means
Returned by openObject.Read (the io.Reader the Mega driver hands out for downloads) when Read is called after Close. The openObject tracks a closed flag under a mutex; once Close has run, any further Read is a usage error, equivalent to reading a closed os.File.
Source
Thrown at drivers/mega/util.go:48
var chunk []byte
err = utils.Retry(3, time.Second, func() (err error) {
chunk, err = oo.d.DownloadChunk(oo.id)
return err
})
if err != nil {
return err
}
oo.id++
oo.chunk = chunk
return nil
}
// Read reads up to len(p) bytes into p.
func (oo *openObject) Read(p []byte) (n int, err error) {
oo.mu.Lock()
defer oo.mu.Unlock()
if oo.closed {
return 0, fmt.Errorf("read on closed file")
}
// Skip data at the start if requested
for oo.skip > 0 {
_, size, err := oo.d.ChunkLocation(oo.id)
if err != nil {
return 0, err
}
if oo.skip < int64(size) {
break
}
oo.id++
oo.skip -= int64(size)
}
if len(oo.chunk) == 0 {
err = oo.getChunk(oo.ctx)
if err != nil {
return 0, err
}View on GitHub (pinned to 843d9dc814)
Solutions
- Audit the consumer of the download stream: never Read after Close; treat Close as terminal.
- On retries, obtain a fresh reader from the driver instead of reusing the closed one.
- Where multiple goroutines touch the reader, funnel all Read/Close calls through one owner (e.g. io.Copy in a single goroutine) or synchronize with a WaitGroup before closing.
- Optionally use errors.Is/strings matching on 'read on closed file' to detect misuse in logs.
Example fix
// before
n, err := body.Read(buf) // panics logic later, body already closed by timeout handler
// after
// single owner pattern
go func() {
defer body.Close()
_, copyErr := io.Copy(dst, body)
if copyErr != nil { log.Warn("mega download aborted: ", copyErr) }
}() Defensive patterns
Strategy: validation
Validate before calling
// Before reading in each consumer goroutine, route through a single owner: // creator guarantees Read and Close happen in one goroutine (io.Copy)
Try / catch
if n, err := r.Read(buf); err != nil {
if strings.Contains(err.Error(), "read on closed file") {
// misuse: reader was closed; open a new one instead of retrying
fresh, oerr := driver.Get(ctx, path) // re-open
if oerr != nil { return oerr }
r = fresh
}
} Prevention
- Treat Close as terminal: never Read afterwards.
- Use defer body.Close() in the same function/goroutine that reads.
- On any read error, close and re-open a fresh reader rather than reusing.
- Keep one owner goroutine per reader; coordinate close with WaitGroups if sharing.
When it happens
Trigger: Calling Read on a Mega download stream after Close — typically a caller that closes the reader on error/timeout and then retries or logs by draining the body, or concurrent goroutines where one closes while another still reads (the mutex serializes them but cannot prevent close-then-read ordering).
Common situations: HTTP handlers that copy the reader with a context timeout, hit the timeout, close the body, then an error path or a retry wrapper attempts another Read; preview/streaming features that double-consume the same reader; defer-ordered code where Close executes before a late Read in another goroutine.
Related errors
- missing cookie or qrcode account
- not get file info
- read on closed file
- failed to read response body: %v
- generate totp code failed: %w
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/4220ccda3a6dd710.
Report an issue: GitHub.