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 := ¶llelDownloader{
src: store,
key: key,
fsize: size,
blockSize: bSize,
concurrent: concurrent,
buffers: make(map[int64]*[]byte),
}
down.notify = sync.NewCond(down)
go down.download()
return downView on GitHub (pinned to c9a67b23e8)
Solutions
- Ensure all reads complete before calling Close, or stop reading after Close returns
- Use io.Copy semantics that stop reading once EOF/err is seen and only close once
- Add synchronization so Close happens only after the reader goroutine finishes
- 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
- Close readers only after all reads finish (single owner goroutine)
- Use sync/errgroup to order read completion before Close
- Treat the "closed" sentinel as a normal shutdown signal, not data loss
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
- read worker config: %s
- dest read: %s
- Unable to skip %s bytes (position=%s, fileSize=%s): %s
- write
- errDirSuffix
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/278fe203a5ecccbd.
Report an issue: GitHub.