gastownhall/beads · error

acquire %s: %w

Error message

acquire %s: %w

What it means

ListenAndServe first acquires proxy.lock under rootDir; failure to create or lock the file is wrapped as 'acquire %s: %w'. The lock serializes the whole proxy lifetime, so without it the proxy must not start. A doomed start may release the lock early, but only the main goroutine touches it.

Source

Thrown at internal/storage/dbproxy/proxy/server.go:115

		port:        opts.Port,
		idleTimeout: opts.IdleTimeout,
		server:      opts.Server,
		stats:       opts.Stats,
		stopEpoch:   opts.StopEpoch,
	}
}

func (p *proxyServer) tracef(format string, args ...any) {
	p.logger.Printf(format, args...)
}

func (p *proxyServer) ListenAndServe(parentCtx context.Context) error {
	lock, err := util.TryLock(filepath.Join(p.rootDir, LockFileName))
	if err != nil {
		if lockfile.IsLocked(err) {
			return ErrLockHeld
		}
		return fmt.Errorf("acquire %s: %w", LockFileName, err)
	}
	// proxy.lock is held for the proxy's whole lifetime, but a doomed start
	// must be able to release it early (before its backend teardown) without
	// the deferred release double-unlocking. Only the main goroutine touches
	// this.
	lockHeld := true
	releaseLock := func() {
		if lockHeld {
			lockHeld = false
			lock.Unlock()
		}
	}
	defer releaseLock()
	if err := clearSpawnMarkerAfterLock(p.rootDir); err != nil {
		return fmt.Errorf("clear proxy spawn marker: %w", err)
	}

	// Fast-abort: a concurrent `bd dolt stop` advances the stop epoch before

View on GitHub (pinned to 71377f2769)

Solutions

  1. Stop the other proxy process using the same rootDir (bd dolt stop or kill it)
  2. Remove a stale lock only after confirming no live holder
  3. Fix rootDir permissions so the lock file can be created/locked

Example fix

// before: second daemon against same root
bd serve --root /data   // holds proxy.lock
bd serve --root /data   // acquire error
// after
bd dolt stop --root /data && bd serve --root /data
Defensive patterns

Strategy: fallback

Validate before calling

// before starting, check for a live holder
if f, err := os.Open(filepath.Join(rootDir, "proxy.lock")); err == nil {
  defer f.Close()
  if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err != nil {
    return fmt.Errorf("proxy already running for %s", rootDir)
  }
}

Try / catch

if err := p.ListenAndServe(ctx); err != nil {
  if strings.Contains(err.Error(), "acquire") { /* another proxy holds the lock */ }
  return err
}

Prevention

When it happens

Trigger: Calling proxyServer.ListenAndServe when proxy.lock cannot be created/locked in rootDir — another proxy holds the lock, or the lock file can't be opened/created.

Common situations: Two `bd` daemons started against the same rootDir; leftover lock after a crashed start (usually auto-released by flock semantics); read-only or permission-restricted rootDir; NFS mounts without flock support.

Related errors


AI-assisted analysis of gastownhall/beads@71377f2769 (2026-08-30). Data as JSON: /api/errors/a2f39c75ef7843b5. Report an issue: GitHub.