juicedata/juicefs · error
jfsObjects.NewNSLock: the length of the objects parameter mu
Error message
jfsObjects.NewNSLock: the length of the objects parameter must be 1, current %s
What it means
NewNSLock implements minio's RWLocker interface and requires the namespace lock to be scoped to exactly one object path. The JuiceFS gateway cannot express a multi-object or zero-object lock in its single-lockfile flock implementation, so it panics when the caller violates that contract.
Source
Thrown at pkg/gateway/gateway.go:1523
return j.getFlockWithTimeOut(ctx, meta.F_RDLCK, timeout)
}
func (j *jfsFLock) RUnlock() {
if j.inode == 0 || j.readonly {
return
}
if errno := j.meta.Flock(mctx, j.inode, j.owner, meta.F_UNLCK, true); errno != 0 {
logger.Errorf("failed to release lock for inode %d by owner %d, error : %s", j.inode, j.owner, errno)
}
j.localLock.RUnlock()
}
func (n *jfsObjects) NewNSLock(bucket string, objects ...string) minio.RWLocker {
if n.gConf.ReadOnly {
return &jfsFLock{readonly: true}
}
if len(objects) != 1 {
panic(fmt.Errorf("jfsObjects.NewNSLock: the length of the objects parameter must be 1, current %s", objects))
}
lockfile := path.Join(minio.MinioMetaBucket, minio.MinioMetaLockFile)
var file *fs.File
var errno syscall.Errno
file, errno = n.fs.Open(mctx, lockfile, vfs.MODE_MASK_W)
if errno != 0 && !errors.Is(errno, syscall.ENOENT) {
logger.Errorf("failed to open the file to be locked: %s error %s", lockfile, errno)
return &jfsFLock{}
}
if errors.Is(errno, syscall.ENOENT) {
if file, errno = n.fs.Create(mctx, lockfile, 0666, n.gConf.Umask); errno != 0 {
if errors.Is(errno, syscall.EEXIST) {
if file, errno = n.fs.Open(mctx, lockfile, vfs.MODE_MASK_W); errno != 0 {
logger.Errorf("failed to open the file to be locked: %s error %s", lockfile, errno)
return &jfsFLock{}
}
} else {View on GitHub (pinned to c9a67b23e8)
Solutions
- Pass exactly one object path when acquiring a namespace lock
- If multiple objects need locking, call NewNSLock once per object and acquire each lock separately
- Verify the vendored minio version matches what the JuiceFS gateway expects (go.mod) so internal lock calls pass 1 object
Example fix
// before locker := jfs.NewNSLock(bucket, obj1, obj2) // after l1 := jfs.NewNSLock(bucket, obj1) l2 := jfs.NewNSLock(bucket, obj2)
Defensive patterns
Strategy: validation
Validate before calling
if len(objects) != 1 {
return fmt.Errorf("NewNSLock requires exactly 1 object, got %d", len(objects))
}
locker := jfs.NewNSLock(bucket, objects[0]) Type guard
func validNSLockArgs(objects []string) bool { return len(objects) == 1 } Try / catch
// panic-based: wrap call site
defer func() {
if r := recover(); r != nil {
log.Printf("NewNSLock misuse: %v", r)
}
}() Prevention
- Always lock one object per NewNSLock call
- Add an assertion/unit test on custom gateway code that calls NewNSLock
- Keep the vendored minio version in sync with JuiceFS expectations
When it happens
Trigger: Calling jfsObjects.NewNSLock with zero objects (NewNSLock(bucket)) or more than one object (NewNSLock(bucket, obj1, obj2)); internally this happens only if a minio Multipart/erasure-lock caller passes multiple lock targets.
Common situations: Embedding jfsObjects into a custom S3 gateway layer that calls NewNSLock directly with multiple object names; version drift between minio/pkg and JuiceFS changing how many lock targets are passed; readonly mode masking the bug until a writable mount is used.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- Failed to put: %s
- Failed to create bucket %s: %s, previous error: %s Please cr
- Failed to create bucket %s: %s, previous error: %s
- Failed to get: %s
- read wrong data: expected %x, got %x
AI-assisted analysis of juicedata/juicefs@c9a67b23e8 (2026-09-06).
Data as JSON: /api/errors/be1a38759dc7aa1e.
Report an issue: GitHub.