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, err

View on GitHub (pinned to 843d9dc814)

Solutions

  1. Ensure single ownership: only the goroutine that finishes reading calls Close (use sync.Once or defer in the same scope as the read loop).
  2. Check the closed state (or track it in your wrapper) before issuing Read.
  3. Coordinate cancellation: cancel the context, wait for the reader goroutine to observe EOF/err, then Close.
  4. 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

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.