Skip to content

fix(core): synchronize CanRegistry against concurrent access - #22

Open
dborgards wants to merge 1 commit into
pkuyo:masterfrom
dborgards:fix/canregistry-concurrency
Open

fix(core): synchronize CanRegistry against concurrent access#22
dborgards wants to merge 1 commit into
pkuyo:masterfrom
dborgards:fix/canregistry-concurrency

Conversation

@dborgards

Copy link
Copy Markdown

Summary

CanRegistry's dictionaries were plain, unsynchronized maps: any late (re-)registration racing the public read surface (Resolve/Factory/TryOpenEndPoint/EnumerateEndPoints) could corrupt state or throw from concurrent enumeration. This is a realistic scenario for applications that register endpoints/providers after startup (plugin-style hosts, adapter assemblies finishing their preload registration while a bus is already being opened).

Fix

  • All registry accesses now take one private lock.
  • Readers that iterate (Factory's known-list, EnumerateEndPoints) snapshot under the lock and enumerate outside of it.
  • Endpoint handlers are invoked outside the lock, so a slow Open never blocks other readers (and a handler that itself touches the registry cannot deadlock).

No public API changes. Ported from a fix we have been running in our fork.

Tests

  • New CanRegistryConcurrencyTests: parallel RegisterEndPoint churn (16 schemes) plus 4 concurrent readers spinning on TryOpenEndPoint/EnumerateEndPoints against the shared singleton. CanKit.Core now exposes InternalsVisibleTo to the test assembly so the test can drive the internal Register* surface.
  • Non-vacuity check: with the registry reverted to the unsynchronized version, the new test fails consistently (concurrent-enumeration exception); with the fix it passes 5/5 filtered runs (~59 ms each, no flakiness).
  • dotnet build CanKitAdapters.slnf -c Release / -c Fake clean; CANKIT_TEST_ADAPTERS=CanKit.Adapter.Virtual dotnet test CanKitAdapters.slnf -c Release -f net8.0 → 73 passed, 0 failed (72 baseline + 1 new).

Note

The test cleans up its stress registrations from the shared singleton via reflection over the registry's private field names — a rename of those fields would break the test with a clear error message.

The registry's dictionaries were plain, unsynchronized maps: any late
(re-)registration racing the public read surface (Resolve/Factory/
TryOpenEndPoint/EnumerateEndPoints) could corrupt state or throw from
concurrent enumeration. All accesses now take one private lock; readers
that iterate (Factory's known-list, EnumerateEndPoints) snapshot under
the lock and enumerate outside, and endpoint handlers are invoked
outside the lock so slow opens never block readers.

CanKit.Core exposes InternalsVisibleTo to the test assembly so the new
CanRegistryConcurrencyTests can drive the internal Register* surface:
parallel RegisterEndPoint churn plus concurrent readers on the shared
singleton.
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