Conversation
There was a problem hiding this comment.
Pull request overview
This PR adds locking around the shared transaction field in SqliteDataSource to prevent race conditions when the transaction is committed or disposed while concurrent database operations are still in flight (e.g., during Task.WhenAll in Rescan). The double-checked locking pattern in ExecuteOnConnection ensures safe access to the transaction's connection, and CommitTransaction uses a "capture-under-lock, operate-outside-lock" pattern.
Changes:
- Added a
transactionLockobject and appliedlockblocks inBeginTransaction,CommitTransaction, andExecuteOnConnectionto synchronize access to thetransactionfield. - Refactored
ExecuteOnConnectionto use a double-checked locking pattern that safely falls through to a new connection if the transaction becomes unavailable. - Refactored
CommitTransactionto capture the transaction reference under lock, then commit and dispose outside the lock with proper try/finally cleanup.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
@jamesmoore I've opened a new pull request, #498, to work on those changes. Once the pull request is ready, I'll request review from you. |
…OnConnection callback Co-authored-by: jamesmoore <6506748+jamesmoore@users.noreply.github.com>
Pass captured transaction to ExecuteOnConnection callback instead of re-reading field
|
@codex review |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 1 out of 1 changed files in this pull request and generated 1 comment.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
SDMeta/Cache/SqliteDataSource.cs
Outdated
| if (this.transaction?.Connection != null) | ||
| { | ||
| return func(transaction.Connection); | ||
| } | ||
| else | ||
| { | ||
| using var connection = GetConnection(); | ||
| return func(connection); | ||
| lock (transactionLock) | ||
| { |
There was a problem hiding this comment.
ExecuteOnConnection reads this.transaction in the initial if (this.transaction?.Connection != null) check without holding transactionLock. Since BeginTransaction/CommitTransaction now write this.transaction under that lock, an unsynchronized read can observe a stale null and incorrectly fall back to opening a new connection, causing operations to run outside the intended transaction. Consider taking transactionLock before any read of this.transaction (or using Volatile.Read/Volatile.Write) and then branching based on the captured value.
There was a problem hiding this comment.
@copilot open a new pull request to apply changes based on this feedback
|
@jamesmoore I've opened a new pull request, #499, to work on those changes. Once the pull request is ready, I'll request review from you. |
Co-authored-by: jamesmoore <6506748+jamesmoore@users.noreply.github.com>
Fix unsynchronized read of `transaction` in `ExecuteOnConnection`
No description provided.