juicedata/juicefs · warning

closed

Error message

closed

What it means

parallelDownloader's reader sets its persistent error to "closed" when Close is called; subsequent reads after close return this error. doCopySingle triggers it when it closes the downloader while (or before) a read is still in flight.

Source

Thrown at pkg/sync/download.go:151

		delete(r.buffers, off)
		r.Unlock()
		<-r.concurrent
	}
	if copiedBytes != nil {
		copiedBytes.IncrInt64(int64(n))
	}
	return n, nil
}

func (r *parallelDownloader) Close() {
	r.Lock()
	defer r.Unlock()
	for _, p := range r.buffers {
		downloadBufPool.Put(p)
	}
	r.buffers = nil
	if r.err == nil {
		r.err = errors.New("closed")
	}
}

func newParallelDownloader(store object.ObjectStorage, key string, size int64, bSize int64, concurrent chan int) *parallelDownloader {
	if bSize < 1 {
		panic("concurrent and blockSize must be positive integer")
	}
	down := &parallelDownloader{
		src:        store,
		key:        key,
		fsize:      size,
		blockSize:  bSize,
		concurrent: concurrent,
		buffers:    make(map[int64]*[]byte),
	}
	down.notify = sync.NewCond(down)
	go down.download()
	return down

View on GitHub (pinned to c9a67b23e8)

Solutions

  1. Ensure all reads complete before calling Close, or stop reading after Close returns
  2. Use io.Copy semantics that stop reading once EOF/err is seen and only close once
  3. Add synchronization so Close happens only after the reader goroutine finishes
  4. If intentional shutdown, ignore/compare the "closed" error instead of treating it as failure

Example fix

// before
io.Copy(dst, reader)
reader.Close()
// after
_, err := io.Copy(dst, reader)
if err != nil && !strings.Contains(err.Error(), "closed") {
	return err
}
reader.Close()
Defensive patterns

Strategy: try-catch

Type guard

func isClosedErr(err error) bool { return err != nil && err.Error() == "closed" }

Try / catch

n, err := io.Copy(dst, reader)
if isClosedErr(err) {
	return nil // expected after shutdown
} else if err != nil {
	return err
}

Prevention

When it happens

Trigger: Reading from a downloader reader after Close was invoked; concurrent copy logic closing the reader while another goroutine still reads from it.

Common situations: Sync copy cancellation or early exit where one path closes the reader but a deferred/buffered read follows; races between the copy loop and error handling.

Understand the failure class

Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.

Related errors


AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06). Data as JSON: /api/errors/278fe203a5ecccbd. Report an issue: GitHub.