Conversation
check_stream reads Streams.Version without lock hints. Two appends with ExpectedStreamVersion.Any to the same stream can read the same version, and the second append fails: - Existing stream: duplicate key on UQ_StreamIdAndStreamPosition. - New stream: "WrongExpectedVersion -2, stream already exists". The failed insert also burns a GlobalPosition, which leaves a gap. The tests use a gate connection that holds a lock. Both appends read the stream, then wait before they write, so the race happens on every run. The tests fail until check_stream takes UPDLOCK, HOLDLOCK. StoreFixture exposes SchemaName so that the tests can query the schema.
PR Summary by QodoSerialize concurrent SQL Server appends to the same stream
AI Description
Diagram
High-Level Assessment
Files changed (3)
|
Code Review by Qodo
1. New SQL tests omit context-free awaits
|
Test Results 48 files ±0 48 suites ±0 15m 33s ⏱️ +18s Results for commit 4950055. ± Comparison against base commit ed6cfd1. This pull request removes 9 and adds 17 tests. Note that renamed tests count towards both.♻️ This comment has been updated with latest results. |
|
Code review by qodo was updated up to the latest commit eca31ab |
|
The net9 failure in CancelledMessageTests.Handler_cancelled_by_shutdown_is_not_acknowledged_and_is_redelivered is in Core subscriptions, which this PR doesn't touch. It passes 30/30 locally on net9. It looks like a timing flake in the 5 s Wait.Until under CI load |
f5ff6c8 to
817bf34
Compare
|
Code review by qodo was updated up to the latest commit 817bf34 |
…e decide who creates one (Eventuous#597) check_stream read Streams without lock hints. Two appends to the same stream could read the same version, so an append with ExpectedStreamVersion.Any failed, and a version conflict failed after its insert, which left a gap in GlobalPosition. - An existing stream is read with UPDLOCK. A second append to the same stream waits for the first one to commit, then reads the new version. A version conflict now fails before anything is inserted. - When two appends create the same stream, the insert into Streams runs with XACT_ABORT off. UQ_StreamName, with the collation of the lookup, makes the second insert wait and then fail with a duplicate key. The second append then reads the stream that the first one created, and gets the same expected version check. - No range or application lock is used, so creating a stream does not block other streams, and names that differ only in case are one stream. Tests: - Two appends with the same expected version: one fails, no gap. - Creating a stream does not block creating another stream, or appending to the existing stream next to it in the index. - Two appends that create one stream through names that differ only in case both succeed. - Two NoStream appends that create the same stream: exactly one fails. - The gap test asserts that both appends succeed before it checks the identity. Fixes Eventuous#597
817bf34 to
4950055
Compare
|
Code review by qodo was updated up to the latest commit 4950055 |
|
@alexeyzimarev this is ready for review when you have time. It fixes #597: check_stream locks an existing stream with UPDLOCK, and when two appends create the same stream, UQ_StreamName decides which one wins, so no range or application locks are needed. The first commit adds tests that fail on dev; the second commit fixes them. All 63 tests in Eventuous.Tests.SqlServer pass. The Qodo findings are addressed. |
Fixes #597
Adds
WITH (UPDLOCK, HOLDLOCK)to the stream lookup incheck_stream, so concurrent appends to the same stream run one after the other instead of reading the same version. Appends to different streams still run in parallel.ConcurrentAppendTests, which fail ondev.All 58 tests in
Eventuous.Tests.SqlServerpass.