juicedata/juicefs · error
ceph: can't put empty file
Error message
ceph: can't put empty file
What it means
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.
Source
Thrown at pkg/object/ceph.go:180
// If the data exceeds 90M, ceph will report an error: 'rados: ret=-90, Message too long'
if len(data) < 85<<20 {
return ctx.WriteFull(key, data)
}
}
buf := cephPool.Get().([]byte)
defer cephPool.Put(buf)
var off uint64
for {
n, err := in.Read(buf)
if n > 0 {
if err = ctx.Write(key, buf[:n], off); err != nil {
return err
}
off += uint64(n)
} else {
if err == io.EOF {
if off == 0 {
return errors.New("ceph: can't put empty file")
}
return nil
}
return err
}
}
})
}
func (c *ceph) Delete(_ context.Context, key string, getters ...AttrGetter) error {
err := c.do(func(ctx *rados.IOContext) error {
return ctx.Delete(key)
})
if err == rados.ErrNotFound {
err = nil
}
return err
}View on GitHub (pinned to c9a67b23e8)
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.
Example fix
// before: blindly sync everything juicefs sync /mnt/src ceph://pool/mnt // after: exclude empty files find /mnt/src -type f -empty > exclude.list juicefs sync --exclude=- /mnt/src ceph://pool/mnt < exclude.list
Defensive patterns
Strategy: validation
Validate before calling
fi, err := os.Stat(srcPath)
if err == nil && fi.Size() == 0 {
return fmt.Errorf("skip %s: ceph backend cannot store empty files", srcPath)
} Try / catch
if err := obj.Put(ctx, key, r); err != nil {
if strings.Contains(err.Error(), "can't put empty file") {
return nil // skip empty files intentionally
}
return err
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
- (max tries) upload block %s: %s (after %d tries)
- Invalid endpoint %s: %s
- Can't create connection to cluster %s for user %s: %s
- Can't read default config file: %s
- Can't connect to cluster %s: %s
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/da4e8acddd1ac8c5.
Report an issue: GitHub.