AlistGo/alist · error
read on closed file
Error message
read on closed file
What it means
Returned by openObject.Read when the caller reads from a stream whose Close was already called. The mutex-guarded closed flag makes this deterministic: any Read after Close yields this error rather than undefined behavior. It mirrors io.ErrClosed semantics but as a distinct sentinel-style message.
Source
Thrown at drivers/halalcloud/util.go:305
var chunk []byte
err = utils.Retry(3, time.Second, func() (err error) {
chunk, err = getRawFiles(oo.d[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 := 1024 * 1024
_, size, err := oo.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, errView on GitHub (pinned to 843d9dc814)
Solutions
- Ensure single ownership: only the goroutine that finishes reading calls Close (use sync.Once or defer in the same scope as the read loop).
- Check the closed state (or track it in your wrapper) before issuing Read.
- Coordinate cancellation: cancel the context, wait for the reader goroutine to observe EOF/err, then Close.
- If multiplexing, wrap openObject in your own refcounted ReadCloser that closes the underlying one last.
Example fix
// before
go func() { io.Copy(dst, r) }()
r.Close() // races the copy
// after
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); io.Copy(dst, r) }()
wg.Wait()
r.Close() Defensive patterns
Strategy: validation
Validate before calling
// ensure reads finish before close in the owning goroutine
var wg sync.WaitGroup
wg.Add(1)
go func() { defer wg.Done(); io.Copy(dst, stream) }()
// ...later
wg.Wait()
stream.Close() Try / catch
n, err := stream.Read(buf)
if err != nil && strings.Contains(err.Error(), "read on closed file") {
// ownership bug in caller: reopen the stream, do not retry the read
stream, _ = reopen(ctx)
n, err = stream.Read(buf)
} Prevention
- One owner goroutine for read+close lifecycle
- Close only after EOF or error
- Use errgroup to join reader goroutines before cleanup
When it happens
Trigger: A goroutine still draining the reader while another calls Close (e.g. ranged readers closed early on cancel); retry logic reusing a closed stream; http.Response bodies read after the request context was cancelled and cleanup ran.
Common situations: io.Copy with a cancel mid-flight where cleanup races the final Read; wrapping the stream in a pool/buffer that recycles on Close while a consumer still holds it; video seeking implementations that close the old range reader before a pending Read finishes.
Related errors
AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15).
Data as JSON: /api/errors/240b11e4b43fa971.
Report an issue: GitHub.