[bug]: added lock to remove race conditions.#20
[bug]: added lock to remove race conditions.#20Thedrogon wants to merge 1 commit intoanikchand461:mainfrom
Conversation
|
@Thedrogon is attempting to deploy a commit to the Anik Chand's projects Team on Vercel. A member of the Team first needs to authorize it. |
Reviewer's GuideIntroduces a file-based locking mechanism around task CLI operations to avoid concurrent access race conditions, and adjusts .gitignore so the built binary is ignored without hiding source files. Sequence diagram for task CLI execution with file locksequenceDiagram
actor User
participant Shell
participant TaskCLI as cmd_task_main
participant TaskPkg as internal_task
participant OS
User->>Shell: Run task command
Shell->>TaskCLI: Execute main
TaskCLI->>TaskPkg: AcquireLock()
TaskPkg->>TaskPkg: Compute dataFilePath
TaskPkg->>OS: Try create tasks.lock (O_CREATE | O_EXCL) up to 10 times
alt Lock acquired
OS-->>TaskPkg: File created
TaskPkg-->>TaskCLI: unlockFunc, nil
TaskCLI->>TaskCLI: defer unlockFunc()
TaskCLI->>TaskCLI: Parse os.Args[1]
TaskCLI->>TaskPkg: Execute selected command
TaskCLI->>TaskPkg: unlockFunc()
TaskPkg->>OS: Remove tasks.lock
else Lock not acquired
OS-->>TaskPkg: tasks.lock exists on all attempts
TaskPkg-->>TaskCLI: nil, error
TaskCLI-->>Shell: Print error and exit
end
Class diagram for main and new lock function in task packageclassDiagram
class cmd_task_main {
+main()
}
class internal_task {
+AcquireLock() (func(), error)
+dataFilePath() (string, error)
}
cmd_task_main ..> internal_task : calls AcquireLock
internal_task ..> internal_task : calls dataFilePath
Flow diagram for AcquireLock retry and error handlingflowchart TD
A[Start AcquireLock] --> B[Call dataFilePath]
B -->|error| Z[Return nil and error]
B -->|ok| C[Compute lockPath tasks.lock]
C --> D[Set attempt i = 0]
D --> E{Attempt i < 10?}
E -->|no| Y[Return nil and could not acquire lock error]
E -->|yes| F[os.OpenFile lockPath with O_CREATE and O_EXCL]
F --> G{OpenFile error is nil?}
G -->|yes| H[Close lock file]
H --> I[Return unlock func that removes lockPath]
G -->|no| J{os.IsExist error?}
J -->|no| K[Return nil and error]
J -->|yes| L[Sleep 100ms]
L --> M[Increment i]
M --> E
File-Level Changes
Assessment against linked issues
Possibly linked issues
Tips and commandsInteracting with Sourcery
Customizing Your ExperienceAccess your dashboard to:
Getting Help
|
📝 WalkthroughWalkthroughThe pull request implements a file locking mechanism to serialize access to the task database. A lock file is acquired in the main routine before command processing, with automatic retry logic and deterministic cleanup to prevent concurrent data races. Changes
Sequence DiagramsequenceDiagram
participant Main as Main Routine
participant Lock as Lock Manager
participant FS as File System
participant Cmd as Command Handler
Main->>Lock: AcquireLock()
Lock->>FS: Attempt atomic lock file creation (O_CREATE|O_EXCL)
alt Lock acquired
FS-->>Lock: Lock file created
Lock-->>Main: Return cleanup function
Main->>Cmd: Process commands
Cmd->>Cmd: Read/modify/write task data
Cmd-->>Main: Commands complete
Main->>Lock: defer cleanup()
Lock->>FS: Remove lock file
FS-->>Lock: Success
else Lock exists (retry)
FS-->>Lock: File exists error
Lock->>Lock: Wait 100ms
Lock->>FS: Retry (up to 10 times)
else Lock acquisition failed
Lock-->>Main: Return error
Main->>Main: Exit early
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 2❌ Failed checks (2 warnings)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing touches
🧪 Generate unit tests (beta)
⚔️ Resolve merge conflicts (beta)
Tip Issue Planner is now in beta. Read the docs and try it out! Share your feedback on Discord. Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Hey - I've left some high level feedback:
- The lock file is treated as a simple presence flag; consider writing minimal metadata (e.g., PID and timestamp) and handling stale locks so a crashed process doesn’t leave the CLI permanently blocked on future runs.
- The unlock function currently ignores any error from
os.Remove(lockPath); if a remove failure would matter for subsequent runs, consider checking/logging that error so unexpected filesystem issues with the lock don’t go unnoticed.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The lock file is treated as a simple presence flag; consider writing minimal metadata (e.g., PID and timestamp) and handling stale locks so a crashed process doesn’t leave the CLI permanently blocked on future runs.
- The unlock function currently ignores any error from `os.Remove(lockPath)`; if a remove failure would matter for subsequent runs, consider checking/logging that error so unexpected filesystem issues with the lock don’t go unnoticed.Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
There was a problem hiding this comment.
🧹 Nitpick comments (3)
internal/task/lock.go (3)
22-25: Lock file is created then immediately closed — OS-level lock is never held.The file is opened with
O_CREATE|O_EXCL, then immediately closed on Line 25. Betweenf.Close()and the eventualos.Remove(lockPath), the lock is purely advisory based on file existence. This is the root cause of the stale-lock vulnerability described above.If you switch to
syscall.Flock(orgolang.org/x/sys/windowsequivalent), you'd keep the file descriptor open and the OS would release the lock on process exit:♻️ Sketch using syscall.Flock (Unix)
- f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_EXCL, 0644) + f, err := os.OpenFile(lockPath, os.O_CREATE|os.O_RDWR, 0644) if err == nil { - f.Close() - return func() { - os.Remove(lockPath) - }, nil + if err := syscall.Flock(int(f.Fd()), syscall.LOCK_EX|syscall.LOCK_NB); err == nil { + return func() { + syscall.Flock(int(f.Fd()), syscall.LOCK_UN) + f.Close() + os.Remove(lockPath) + }, nil + } + f.Close() }
20-39: 1-second timeout may be too aggressive for legitimate contention.Ten retries × 100 ms = 1 second. For a CLI that might be invoked by a script in a tight loop (or a user running concurrent
task addcommands), this could be too short, producing spurious failures. Consider making the retry count/interval configurable or at least bumping the defaults (e.g., 30 retries × 100 ms = 3 s).
11-42: Stale lock file is only a risk from SIGKILL or system crash, not from normal signal handling.The
defer unlock()at line 56 ofcmd/task/main.gois properly in place. In Go, deferred functions execute when the program exits normally or on SIGINT/SIGTERM, so Ctrl+C will not leave a stale lock. However, a hard kill (kill -9/ SIGKILL) or system crash will bypass cleanup and leavetasks.lockindefinitely, blocking all future invocations.If robustness against forceful termination is desired, consider:
- Write the PID to the lock file, then check on acquisition failure if that PID is still alive and remove stale locks automatically.
- Use
syscall.Flock(Unix) /LockFileEx(Windows), which are automatically released by the OS when the process exits, regardless of termination method.Option 2 is the most robust cross-platform solution and eliminates the entire stale-lock risk.
closes #19 ,
This PR introduces a file locking mechanism to ensure data integrity when multiple instances of the task CLI are run concurrently . It also fixes a .gitignore issue that was incorrectly masking source files.
Added lock.go: Implements a cross-platform lock file mechanism (tasks.lock) alongside the data file. It attempts to acquire a lock for up to 1 second before timing out.
Updated main.go: Wraps the command execution in AcquireLock() and ensures the lock is released via defer.
Updated .gitignore: Changed task to /task to ensure the binary is ignored without hiding the task/ source directory or new files like lock.go.
Summary by Sourcery
Add a file-based locking mechanism to prevent concurrent task CLI instances from corrupting data and adjust ignore rules for the compiled binary.
Bug Fixes:
Enhancements:
Build:
Summary by CodeRabbit