nsqio/nsq · critical

cannot flock directory %s - %s (possibly in use by another i

Error message

cannot flock directory %s - %s (possibly in use by another instance of nsqd)

What it means

internal/dirlock wraps an exclusive, non-blocking flock (syscall.Flock with LOCK_EX|LOCK_NB) on the directory fd of nsqd's --data-path; Lock returns 'cannot flock directory %s - %s (possibly in use by another instance of nsqd)' when the flock syscall itself errors. Its purpose is to stop two nsqd processes from sharing one data directory and corrupting disk-backed queue state. The wrapped syscall error distinguishes the causes: EWOULDBLOCK means another holder exists; other errnos mean the filesystem does not support flock at all.

Source

Thrown at internal/dirlock/dirlock.go:31

	dir string
	f   *os.File
}

func New(dir string) *DirLock {
	return &DirLock{
		dir: dir,
	}
}

func (l *DirLock) Lock() error {
	f, err := os.Open(l.dir)
	if err != nil {
		return err
	}
	l.f = f
	err = syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB)
	if err != nil {
		return fmt.Errorf("cannot flock directory %s - %s (possibly in use by another instance of nsqd)", l.dir, err)
	}
	return nil
}

func (l *DirLock) Unlock() error {
	err := syscall.Flock(int(l.f.Fd()), syscall.LOCK_UN)
	closeErr := l.f.Close()
	if err != nil {
		return err
	}
	return closeErr
}

View on GitHub (pinned to 85cf10c09c)

Solutions

  1. Find and stop the other process: 'fuser -v <data-path>' / 'ps aux | grep nsqd' / 'lsof +D <data-path>', then start nsqd again.
  2. Give each nsqd instance its own --data-path (the standard fix for fleets and local parallel runs).
  3. Move the data path off network/FUSE filesystems onto local disk (ext4/xfs) — flock on a directory must be supported.
  4. If you truly need shared storage, do not share one dir between live nsqd processes; architect replication with nsqd nodes instead.

Example fix

# before: two instances, same dir
nsqd --data-path=/var/lib/nsq ...   # instance A running
nsqd --data-path=/var/lib/nsq ...   # instance B -> cannot flock directory ...

# after: separate data paths (or stop the old instance first)
nsqd --data-path=/var/lib/nsq-a ...
nsqd --data-path=/var/lib/nsq-b ...
Defensive patterns

Strategy: validation

Validate before calling

// pre-start: assert no other nsqd holds the data dir and the fs supports flock
func canLockDataDir(dir string) error {
    f, err := os.Open(dir)
    if err != nil {
        return err
    }
    defer f.Close()
    if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
        return fmt.Errorf("data dir %s busy or unsupported fs: %w", dir, err)
    }
    return syscall.Flock(int(f.Fd()), syscall.LOCK_UN)
}

Try / catch

// orchestrators: catch startup failure and name the two causes
if err := startNsqd(cfg); err != nil && strings.Contains(err.Error(), "cannot flock directory") {
    if errors.Is(err, syscall.EWOULDBLOCK) {
    	return errors.New("another nsqd owns this data-path; stop it or assign a new --data-path")
    }
    return errors.New("data-path filesystem does not support flock; use local disk")
}

Prevention

When it happens

Trigger: Starting a second nsqd with the same --data-path (EWOULDBLOCK/EAGAIN); or running the data path on a filesystem that cannot flock a directory: NFS without a lock manager, some network/FUSE mounts, older CIFS setups, or certain container volumes (EROFS/EOPNOTSUPP-style errors). The directory must already exist — a missing dir fails earlier at os.Open.

Common situations: Accidentally launching a duplicate systemd/docker instance after a config edit; running nsqd on NFS-mounted storage 'for shared persistence'; k8s hostPath/RWO volume attached twice; test scripts that forgot to kill the previous nsqd and reuse /tmp dirs.

Related errors


AI-assisted analysis of nsqio/nsq@85cf10c09c (2026-08-16). Data as JSON: /api/errors/ea0f43d1e099b58f. Report an issue: GitHub.