Fix: [for cherry-picking] Removed the shared reentrancy-depth bypass by - #134
Conversation
- Remove reentrant lock bypass - Bound write lock acquisition - Keep transfer result type private
There was a problem hiding this comment.
Hey - I've found 2 issues
Prompt for AI Agents
Please address the comments from this code review:
## Individual Comments
### Comment 1
<location path="QuickShell.Raycast/src/lib/storage.ts" line_range="660-661" />
<code_context>
- * Runs `operation` exclusively against other writers.
- * Re-entrant so nested save/flush calls from an outer mutator do not deadlock.
- */
+ /** Runs `operation` exclusively against other writers, with bounded acquisition. */
private async withWriteLock<T>(operation: () => Promise<T>): Promise<T> {
- if (this.writeDepth > 0) {
- return operation();
</code_context>
<issue_to_address>
**issue (bug_risk):** Dropping re-entrancy on the write lock introduces a deadlock risk if future code accidentally calls locked APIs from inside a locked section.
The previous `writeDepth` logic allowed nested `save`/`flush` calls without blocking. Now, correctness depends on always using `saveUnlocked`/`flushRecentWritesUnlocked` inside `withWriteLock`. A future nested call to `save` or `flushRecentWrites` from within `withWriteLock` would queue behind the current lock and then wait on itself, causing a deadlock until timeout. Please either reintroduce a re-entrancy mechanism (e.g., per-instance depth/flag that bypasses the queue) or add runtime assertions that prevent calling the locked variants from within `withWriteLock`, so misuse fails fast.
</issue_to_address>
### Comment 2
<location path="docs/architecture/parity-matrix.md" line_range="44" />
<code_context>
| Import / export JSON | Full | Via settings / shared file | Full (extension import-export) | Desktop shares one file; Raycast is separate blob; import always untrusted |
| Layout undo / redo | Full | Partial (plugin + editor) | Full (storage history) | |
| Section separators | Full | Partial | Full (Raycast-local `layoutEntries`) | Desktop shared layout file; Raycast blob parallel |
+| Reset all / backup restore | Full | Partial | Full (durable Raycast backup) | Raycast backup is local to the extension and survives restart; mutations are serialized |
---
</code_context>
<issue_to_address>
**nitpick (typo):** Consider aligning the spelling of "reset-all" across documentation for consistency.
Elsewhere this flow is referred to as "reset-all" (hyphenated). Please update this row to match that spelling so the feature name is consistent across the docs.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| /** Runs `operation` exclusively against other writers, with bounded acquisition. */ | ||
| private async withWriteLock<T>(operation: () => Promise<T>): Promise<T> { |
There was a problem hiding this comment.
issue (bug_risk): Dropping re-entrancy on the write lock introduces a deadlock risk if future code accidentally calls locked APIs from inside a locked section.
The previous writeDepth logic allowed nested save/flush calls without blocking. Now, correctness depends on always using saveUnlocked/flushRecentWritesUnlocked inside withWriteLock. A future nested call to save or flushRecentWrites from within withWriteLock would queue behind the current lock and then wait on itself, causing a deadlock until timeout. Please either reintroduce a re-entrancy mechanism (e.g., per-instance depth/flag that bypasses the queue) or add runtime assertions that prevent calling the locked variants from within withWriteLock, so misuse fails fast.
| | Import / export JSON | Full | Via settings / shared file | Full (extension import-export) | Desktop shares one file; Raycast is separate blob; import always untrusted | | ||
| | Layout undo / redo | Full | Partial (plugin + editor) | Full (storage history) | | | ||
| | Section separators | Full | Partial | Full (Raycast-local `layoutEntries`) | Desktop shared layout file; Raycast blob parallel | | ||
| | Reset all / backup restore | Full | Partial | Full (durable Raycast backup) | Raycast backup is local to the extension and survives restart; mutations are serialized | |
There was a problem hiding this comment.
nitpick (typo): Consider aligning the spelling of "reset-all" across documentation for consistency.
Elsewhere this flow is referred to as "reset-all" (hyphenated). Please update this row to match that spelling so the feature name is consistent across the docs.
Fixed Findings
Automated fix from agentic review of #133
Summary by cubic
Removed the reentrant write-lock bypass in Raycast storage and added a 5s lock acquisition timeout to prevent deadlocks. Tightened write paths and clarified the durable backup used by reset/restore.
writeDepthgate with an exclusivewithWriteLockbounded to 5000 ms (warns and errors on timeout).saveUnlocked/flushRecentWritesUnlockedfor internal calls; publicsave/flushRecentWritesnow wrap with the lock.StorageTransferResultinternal and updated docs to describe the bounded lock and the reset-all durable backup/restore behavior.Written for commit ae83fd8. Summary will update on new commits.