{"record":{"id":"8818606a7fe78244","repo":"charmbracelet/crush","slug":"acquire-lock-w","errorCode":null,"errorMessage":"acquire lock: %w","messagePattern":"acquire lock: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"internal/lock/lock_unix.go","lineNumber":31,"sourceCode":")\n\n// retrySleep is the interval between non-blocking flock retries in the\n// blocking File path. Small enough that contention resolution feels\n// snappy; large enough that we don't burn a CPU spinning.\nconst retrySleep = 100 * time.Millisecond\n\nfunc lockFile(ctx context.Context, f *os.File) (func(), error) {\n\tfor {\n\t\terr := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)\n\t\tif err == nil {\n\t\t\treturn func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN) }, nil\n\t\t}\n\t\tif !errors.Is(err, unix.EWOULDBLOCK) {\n\t\t\treturn nil, fmt.Errorf(\"flock: %w\", err)\n\t\t}\n\t\tselect {\n\t\tcase <-ctx.Done():\n\t\t\treturn nil, fmt.Errorf(\"acquire lock: %w\", ctx.Err())\n\t\tcase <-time.After(retrySleep):\n\t\t}\n\t}\n}\n\nfunc tryLockFile(f *os.File) (func(), error) {\n\tif err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {\n\t\tif errors.Is(err, unix.EWOULDBLOCK) {\n\t\t\treturn nil, ErrContended\n\t\t}\n\t\treturn nil, fmt.Errorf(\"flock: %w\", err)\n\t}\n\treturn func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN) }, nil\n}\n","sourceCodeStart":13,"sourceCodeEnd":46,"githubUrl":"https://github.com/charmbracelet/crush/blob/7944b8e52225d8805e31eacbf7ef24856b0dfb7a/internal/lock/lock_unix.go#L13-L46","documentation":"In lockFile's retry loop, when Flock returns EWOULDBLOCK (lock held by another process), the function waits and retries until the passed context is done. When the context is cancelled or its deadline expires before the lock becomes free, it returns this error wrapping ctx.Err().","triggerScenarios":"Calling lock.File with a context whose deadline expires while another process still holds the exclusive flock on the file.","commonSituations":"Two instances of the app started against the same data directory; the second one's bounded wait (context.WithTimeout) expires before the first releases the lock; debugging a hung session with an artificially short timeout.","solutions":["Close or kill the other process holding the lock (check for running instances with the same data dir).","Increase or remove the context deadline if a longer wait is acceptable (context.Background() blocks indefinitely).","Delete a truly stale lock only after confirming the holder is dead (flock is released automatically on process exit, so a live flock implies a live holder).","Fall back to lock.TryFile to surface contention immediately with ErrContended instead of waiting."],"exampleFix":"// before\nctx, cancel := context.WithTimeout(context.Background(), 2*time.Second)\nrelease, err := lock.File(ctx, lockPath) // expires under contention\n// after\nctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\nrelease, err := lock.File(ctx, lockPath)\nif errors.Is(err, context.DeadlineExceeded) {\n    return fmt.Errorf(\"another instance still holds %s\", lockPath)\n}","handlingStrategy":"retry","validationCode":"// detect an active holder before waiting\ntr, err := lock.TryFile(path)\nif errors.Is(err, lock.ErrContended) {\n    return fmt.Errorf(\"lock %s is held; resolve before waiting\", path)\n}","typeGuard":null,"tryCatchPattern":"ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)\ndefer cancel()\nrelease, err := lock.File(ctx, path)\nif errors.Is(err, context.DeadlineExceeded) {\n    return fmt.Errorf(\"timed out waiting for lock held by another process\")\n}","preventionTips":["Choose realistic timeouts based on how long contenders typically hold the lock.","Surface a clear 'another instance is running' message on DeadlineExceeded.","Remember flock is released automatically when a process exits; investigate live holders."],"tags":["locking","context","timeout","contention"],"backgroundTag":"lock-acquisition-timeout","analyzedSha":"7944b8e52225d8805e31eacbf7ef24856b0dfb7a","analyzedAt":"2026-08-29T12:48:59.079Z","schemaVersion":2},"datasetVersion":"2026-08-29T17:17:51.833Z"}