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

  1. Audit the consumer of the download stream: never Read after Close; treat Close as terminal.
  2. On retries, obtain a fresh reader from the driver instead of reusing the closed one.
  3. 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.
  4. 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

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


AI-assisted analysis of AlistGo/alist@843d9dc814 (2026-08-15). Data as JSON: /api/errors/4220ccda3a6dd710. Report an issue: GitHub.