{"record":{"id":"278fe203a5ecccbd","repo":"juicedata/juicefs","slug":"closed","errorCode":null,"errorMessage":"closed","messagePattern":"closed","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/sync/download.go","lineNumber":151,"sourceCode":"\t\tdelete(r.buffers, off)\n\t\tr.Unlock()\n\t\t<-r.concurrent\n\t}\n\tif copiedBytes != nil {\n\t\tcopiedBytes.IncrInt64(int64(n))\n\t}\n\treturn n, nil\n}\n\nfunc (r *parallelDownloader) Close() {\n\tr.Lock()\n\tdefer r.Unlock()\n\tfor _, p := range r.buffers {\n\t\tdownloadBufPool.Put(p)\n\t}\n\tr.buffers = nil\n\tif r.err == nil {\n\t\tr.err = errors.New(\"closed\")\n\t}\n}\n\nfunc newParallelDownloader(store object.ObjectStorage, key string, size int64, bSize int64, concurrent chan int) *parallelDownloader {\n\tif bSize < 1 {\n\t\tpanic(\"concurrent and blockSize must be positive integer\")\n\t}\n\tdown := &parallelDownloader{\n\t\tsrc:        store,\n\t\tkey:        key,\n\t\tfsize:      size,\n\t\tblockSize:  bSize,\n\t\tconcurrent: concurrent,\n\t\tbuffers:    make(map[int64]*[]byte),\n\t}\n\tdown.notify = sync.NewCond(down)\n\tgo down.download()\n\treturn down","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/sync/download.go#L133-L169","documentation":"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.","triggerScenarios":"Reading from a downloader reader after Close was invoked; concurrent copy logic closing the reader while another goroutine still reads from it.","commonSituations":"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.","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"],"exampleFix":"// before\nio.Copy(dst, reader)\nreader.Close()\n// after\n_, err := io.Copy(dst, reader)\nif err != nil && !strings.Contains(err.Error(), \"closed\") {\n\treturn err\n}\nreader.Close()","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isClosedErr(err error) bool { return err != nil && err.Error() == \"closed\" }","tryCatchPattern":"n, err := io.Copy(dst, reader)\nif isClosedErr(err) {\n\treturn nil // expected after shutdown\n} else if err != nil {\n\treturn err\n}","preventionTips":["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"],"tags":["sync","io","reader-lifecycle"],"backgroundTag":"invalid-state-transition","analyzedSha":"c9a67b23e8e08ec23ec331aa6f1675e2319e921c","analyzedAt":"2026-09-06T17:55:48.476Z","contentChangedAt":"2026-09-06T17:55:48.476Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}