Skip to content

refactor: remove unused mutex locks for single-user CLI#12

Merged
eannchen merged 1 commit intomainfrom
refactor/remove-unused-locks
Jan 1, 2026
Merged

refactor: remove unused mutex locks for single-user CLI#12
eannchen merged 1 commit intomainfrom
refactor/remove-unused-locks

Conversation

@eannchen
Copy link
Copy Markdown
Owner

@eannchen eannchen commented Jan 1, 2026

Description

  1. The locks were added with future server-based architecture in mind, but there are no current plans for multi-user support.
  2. Update MOTIVATION.md

Type of Change

  • New feature
  • Bug fix
  • Refactor
  • Documentation update
  • Build/CI-related change
  • Other (specify):

Checklist

  • My code follows the project's style.
  • I have performed a self-review of my own code.
  • I have commented my code, particularly in hard-to-understand areas.
  • All new and existing unit tests pass locally with my changes.
  • I have manually tested the new functionality in the CLI.

Summary by CodeRabbit

  • Refactor

    • Streamlined internal storage caching system
    • Updated synchronization model for file operations
  • New Features

    • Added cache invalidation method for explicit cache control
  • Tests

    • Improved robustness testing for file and JSON handling

✏️ Tip: You can customize this high-level summary in your review settings.

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Jan 1, 2026

📝 Walkthrough

Walkthrough

The pull request removes concurrency control mechanisms (mutexes and lock/unlock calls) across the Trie, FileStorage, and usecase layers, making those components non-thread-safe. Concurrency-related tests are removed. A Delete method is added to file utility interfaces.

Changes

Cohort / File(s) Summary
File utilities: Delete operation support
config/test_utils.go, internal/fileutil/fileutil.go
Added Delete(filename string) error method to FileUtil interface with mock implementation; returns nil for non-existent files.
Search layer: Concurrency removal
internal/search/trie.go
Removed mu sync.RWMutex field and "sync" import; Hydrate, Insert, SearchPrefix, and Delete methods no longer acquire/release locks.
Storage layer: Concurrency and caching simplification
storage/file.go
Removed Lock() and Unlock() methods from Storage interface; removed three mutex fields (mu, questionStoreCacheMutex, deltasCacheMutex) from FileStorage; added questionStoreCache and deltasCache fields; added InvalidateCache() method; caching now uses simple nil checks without synchronization.
Storage tests: Concurrency test removal
storage/file_test.go
Removed concurrency tests (TestFileStorage_ConcurrentAccess, TestFileStorage_ConcurrentFileAccess, TestFileStorage_ConcurrentCacheAccess); retained tests for corrupted/empty JSON and file permission handling.
Usecase layer: Lock/unlock call removal
usecase/usecase.go
Removed Lock() and Unlock() calls from UpsertQuestion, DeleteQuestion, Undo, and MigrateToUTC methods.
Usecase tests: Race condition test removal
usecase/usecase_test.go
Removed TestQuestionUseCase_LostUpdateRaceCondition and TestQuestionUseCase_WriteSkewRaceCondition; removed unused "strconv" import.

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 "The mutexes are gone, the locks set free,
No more RWMutex, just let it be!
Delete operations shine so bright,
Threading care now left to night." 🔓

Pre-merge checks and finishing touches

✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main objective of the changeset: removing unused mutex locks from a single-user CLI application.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing touches
  • 📝 Generate docstrings

📜 Recent review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 94e0d12 and beeb285.

📒 Files selected for processing (7)
  • config/test_utils.go
  • internal/fileutil/fileutil.go
  • internal/search/trie.go
  • storage/file.go
  • storage/file_test.go
  • usecase/usecase.go
  • usecase/usecase_test.go
💤 Files with no reviewable changes (4)
  • usecase/usecase.go
  • internal/search/trie.go
  • usecase/usecase_test.go
  • storage/file_test.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/fileutil/fileutil.go
  • config/test_utils.go
🧰 Additional context used
🧬 Code graph analysis (1)
storage/file.go (3)
internal/fileutil/fileutil.go (1)
  • FileUtil (10-19)
config/config.go (1)
  • Delta (181-184)
core/model.go (1)
  • Delta (110-116)
🔇 Additional comments (3)
storage/file.go (3)

10-39: Well-structured refactoring for single-user use.

The removal of Lock/Unlock from the Storage interface and mutex fields from FileStorage aligns perfectly with the PR objectives. The caching approach using simple nil checks is appropriate for a single-threaded CLI context.


121-125: Good addition for cache management.

The InvalidateCache() method provides a clean way to force reloading from files. This is useful for testing or scenarios where external modifications to the data files need to be reflected.


41-119: Cache sharing pattern is safe—all modifications are properly persisted.

The verification confirms the cache sharing concern is handled correctly. All code paths that modify loaded data (UpsertQuestion, DeleteQuestion, Undo, MigrateToUTC) follow a consistent load-modify-save pattern:

  1. Load returns cached instance
  2. Code modifies the cached data directly
  3. SaveQuestionStore() or SaveDeltas() persists changes and updates the cache

Read-only operations (ListQuestionsOrderByDesc, GetQuestion, SearchQuestions, GetHistory) either avoid modification or defensively copy returned data. No code modifies loaded data without saving, and InvalidateCache() is only used in tests.

The pattern works because each operation is atomic: load → modify → save completes before the next operation begins.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (2)
document/MOTIVATION.md (1)

3-3: Fix Markdown formatting issues.

The linter has identified several formatting inconsistencies that should be addressed:

  • Line 3: The author/date metadata uses emphasis (italics) which triggers MD036. Consider using a proper heading level or plain text.
  • Lines 8-17: The navigation list has inconsistent indentation and uses hard tabs instead of spaces (MD007, MD010). Use consistent space-based indentation (2 spaces for each level is typical).
📋 Example fix for consistent indentation

Replace hard tabs with spaces and use consistent 2-space indentation:

 - [Motivation \& Design Notes](#motivation--design-notes)
-	- [Motivation](#motivation)
-		- [Zero Dependencies Philosophy](#zero-dependencies-philosophy)
-	- [Why SM-2 Algorithm](#why-sm-2-algorithm)
-		- [Custom Adaptations to SM-2](#custom-adaptations-to-sm-2)
-	- [Data Structures](#data-structures)
-		- [Indexing for Lookup](#indexing-for-lookup)
-		- [Trie for Text Search](#trie-for-text-search)
-		- [Heap for Top-K Problems](#heap-for-top-k-problems)
-		- [Stack for Undo](#stack-for-undo)
-	- [Closing Note](#closing-note)
+  - [Motivation](#motivation)
+    - [Zero Dependencies Philosophy](#zero-dependencies-philosophy)
+  - [Why SM-2 Algorithm](#why-sm-2-algorithm)
+    - [Custom Adaptations to SM-2](#custom-adaptations-to-sm-2)
+  - [Data Structures](#data-structures)
+    - [Indexing for Lookup](#indexing-for-lookup)
+    - [Trie for Text Search](#trie-for-text-search)
+    - [Heap for Top-K Problems](#heap-for-top-k-problems)
+    - [Stack for Undo](#stack-for-undo)
+  - [Closing Note](#closing-note)

Also applies to: 8-17

storage/file.go (1)

121-125: Document the intended usage of InvalidateCache.

The InvalidateCache method is public but its purpose and usage scenarios are not immediately clear. Consider adding a doc comment explaining when this should be called.

Suggested scenarios to document:

  • Testing: clearing cache between test cases
  • External modification: when files are modified outside the application
  • Manual cache management: if the application detects inconsistency
📝 Suggested documentation
-// InvalidateCache clears the cache, forcing next load to read from file
+// InvalidateCache clears the in-memory cache, forcing the next Load operation
+// to read fresh data from disk. Use this when:
+// - Files have been modified externally (e.g., manual edits)
+// - Testing scenarios require clean state between test cases
+// - Manual cache management is needed for consistency
+// Note: Callers holding references to previously loaded data will retain stale pointers.
 func (fs *FileStorage) InvalidateCache() {
 	fs.questionStoreCache = nil
 	fs.deltasCache = nil
 }
📜 Review details

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 2bf294c and 94e0d12.

📒 Files selected for processing (8)
  • config/test_utils.go
  • document/MOTIVATION.md
  • internal/fileutil/fileutil.go
  • internal/search/trie.go
  • storage/file.go
  • storage/file_test.go
  • usecase/usecase.go
  • usecase/usecase_test.go
💤 Files with no reviewable changes (4)
  • usecase/usecase_test.go
  • internal/search/trie.go
  • usecase/usecase.go
  • storage/file_test.go
🧰 Additional context used
🧬 Code graph analysis (1)
storage/file.go (3)
internal/fileutil/fileutil.go (1)
  • FileUtil (10-19)
config/config.go (1)
  • Delta (181-184)
core/model.go (1)
  • Delta (110-116)
🪛 markdownlint-cli2 (0.18.1)
document/MOTIVATION.md

3-3: Emphasis used instead of a heading

(MD036, no-emphasis-as-heading)


8-8: Unordered list indentation
Expected: 2; Actual: 1

(MD007, ul-indent)


8-8: Hard tabs
Column: 1

(MD010, no-hard-tabs)


9-9: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


9-9: Hard tabs
Column: 1

(MD010, no-hard-tabs)


10-10: Unordered list indentation
Expected: 2; Actual: 1

(MD007, ul-indent)


10-10: Hard tabs
Column: 1

(MD010, no-hard-tabs)


11-11: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


11-11: Hard tabs
Column: 1

(MD010, no-hard-tabs)


12-12: Unordered list indentation
Expected: 2; Actual: 1

(MD007, ul-indent)


12-12: Hard tabs
Column: 1

(MD010, no-hard-tabs)


13-13: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


13-13: Hard tabs
Column: 1

(MD010, no-hard-tabs)


14-14: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


14-14: Hard tabs
Column: 1

(MD010, no-hard-tabs)


15-15: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


15-15: Hard tabs
Column: 1

(MD010, no-hard-tabs)


16-16: Unordered list indentation
Expected: 4; Actual: 2

(MD007, ul-indent)


16-16: Hard tabs
Column: 1

(MD010, no-hard-tabs)


17-17: Unordered list indentation
Expected: 2; Actual: 1

(MD007, ul-indent)


17-17: Hard tabs
Column: 1

(MD010, no-hard-tabs)

🔇 Additional comments (4)
internal/fileutil/fileutil.go (1)

17-18: LGTM! Clean implementation of Delete operation.

The Delete method is implemented correctly with proper error handling: it returns nil when the file doesn't exist (idiomatic for delete operations) and propagates any other errors.

Also applies to: 85-91

config/test_utils.go (1)

10-25: LGTM! Mock properly extended for Delete testing.

The MockFileUtil updates follow the established pattern for error simulation in tests, maintaining consistency with existing loadError and saveError fields.

storage/file.go (2)

33-39: LGTM! Mutex removal aligns with single-user CLI design.

The simplified FileStorage struct removes all synchronization primitives, which is appropriate given the PR's stated objective of removing unused concurrency controls for a single-user CLI application.


41-88: The shared-pointer caching behavior is intentional and appropriate for this single-user CLI.

The tests explicitly verify that multiple LoadQuestionStore() calls return the same cached pointer instance, and that SaveQuestionStore() updates the cache with the modified state. All usage patterns in the codebase follow a strict load → modify → save sequence, ensuring the cache always reflects the current working state. The scenario where a caller modifies the store without saving and then loads again to get the unsaved state does not occur in practice.

This design is efficient and correct for a single-threaded CLI where all mutations are immediately persisted via SaveQuestionStore(). Cache invalidation is available via InvalidateCache() if needed.

- Added a Delete method to the FileUtil interface for removing specified files.
- Implemented the Delete method in JSONFileUtil to handle file deletion with appropriate error handling.
- Updated MockFileUtil to include a deleteError field for testing purposes.
- Removed unnecessary synchronization locks from the Trie structure and related methods to simplify the code.
- Cleaned up test files by removing commented-out concurrent access tests to improve readability.
@eannchen eannchen force-pushed the refactor/remove-unused-locks branch from 94e0d12 to beeb285 Compare January 1, 2026 02:22
@eannchen eannchen merged commit fc8c0ab into main Jan 1, 2026
8 checks passed
@eannchen eannchen deleted the refactor/remove-unused-locks branch January 1, 2026 02:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant