{"record":{"id":"da4e8acddd1ac8c5","repo":"juicedata/juicefs","slug":"ceph-can-t-put-empty-file","errorCode":null,"errorMessage":"ceph: can't put empty file","messagePattern":"ceph: can't put empty file","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/object/ceph.go","lineNumber":180,"sourceCode":"\t\t\t// If the data exceeds 90M, ceph will report an error: 'rados: ret=-90, Message too long'\n\t\t\tif len(data) < 85<<20 {\n\t\t\t\treturn ctx.WriteFull(key, data)\n\t\t\t}\n\t\t}\n\t\tbuf := cephPool.Get().([]byte)\n\t\tdefer cephPool.Put(buf)\n\t\tvar off uint64\n\t\tfor {\n\t\t\tn, err := in.Read(buf)\n\t\t\tif n > 0 {\n\t\t\t\tif err = ctx.Write(key, buf[:n], off); err != nil {\n\t\t\t\t\treturn err\n\t\t\t\t}\n\t\t\t\toff += uint64(n)\n\t\t\t} else {\n\t\t\t\tif err == io.EOF {\n\t\t\t\t\tif off == 0 {\n\t\t\t\t\t\treturn errors.New(\"ceph: can't put empty file\")\n\t\t\t\t\t}\n\t\t\t\t\treturn nil\n\t\t\t\t}\n\t\t\t\treturn err\n\t\t\t}\n\t\t}\n\t})\n}\n\nfunc (c *ceph) Delete(_ context.Context, key string, getters ...AttrGetter) error {\n\terr := c.do(func(ctx *rados.IOContext) error {\n\t\treturn ctx.Delete(key)\n\t})\n\tif err == rados.ErrNotFound {\n\t\terr = nil\n\t}\n\treturn err\n}","sourceCodeStart":162,"sourceCodeEnd":198,"githubUrl":"https://github.com/juicedata/juicefs/blob/c9a67b23e8e08ec23ec331aa6f1675e2319e921c/pkg/object/ceph.go#L162-L198","documentation":"The Ceph RADOS object storage backend cannot upload a zero-length file; when Put reads the source and immediately hits io.EOF at offset 0, it returns this error instead of creating an empty RADOS object. It reflects a limitation of how the backend writes (via rados WriteOp on read chunks), not a corruption.","triggerScenarios":"Uploading a 0-byte local file (or a reader that returns io.EOF with no data) through Put on a ceph:// object store, e.g. during juicefs sync or gc when the source tree contains empty files.","commonSituations":"Syncing directory trees that include empty placeholder files to Ceph; tools that create empty marker files; copying objects where the source stream is already drained.","solutions":["Skip zero-byte files in the sync/copy job (e.g. juicefs sync --exclude or filter empty files) since Ceph backend cannot store them.","Upgrade JuiceFS — check if a newer version supports empty-object puts for the ceph backend.","Write a placeholder byte or use a different backend (e.g. local or S3-compatible) for trees that must contain empty files.","Confirm the source file is genuinely empty and not a reader failure; retry non-empty sources to rule out premature EOF."],"exampleFix":"// before: blindly sync everything\njuicefs sync /mnt/src ceph://pool/mnt\n// after: exclude empty files\nfind /mnt/src -type f -empty > exclude.list\njuicefs sync --exclude=- /mnt/src ceph://pool/mnt < exclude.list","handlingStrategy":"validation","validationCode":"fi, err := os.Stat(srcPath)\nif err == nil && fi.Size() == 0 {\n    return fmt.Errorf(\"skip %s: ceph backend cannot store empty files\", srcPath)\n}","typeGuard":null,"tryCatchPattern":"if err := obj.Put(ctx, key, r); err != nil {\n    if strings.Contains(err.Error(), \"can't put empty file\") {\n        return nil // skip empty files intentionally\n    }\n    return err\n}","preventionTips":["Filter zero-byte files out of sync jobs targeting Ceph.","Remember empty marker files cannot be represented on this backend.","Test round-trips with edge-case files (0 bytes) before large migrations."],"tags":["ceph","object-storage","upload","empty-file"],"backgroundTag":"empty-required-field","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"}