AccessControl (full-featured RW lock)
64-bit reader-writer lock with waiter fairness, in-place promotion, and contention tracking.
Status: โ Implemented ยท Visibility: Internal ยท Category: Foundation
๐ฏ What it solves
Some engine structures are touched by many threads but exist as a single shared instance โ the transaction pool's central lock, a page cache's occupancy map, a segment's buffer lock. For these, raw lock size doesn't matter, but starving a writer under continuous read pressure does. AccessControl gives that handful of high-traffic, low-instance-count structures a true reader-writer lock with fairness guarantees and built-in contention visibility, without the overhead of .NET's ReaderWriterLockSlim.
โ๏ธ How it works (in brief)
The entire lock state โ mode, shared-reader count, three waiter counters, the exclusive holder's thread ID, a sticky contention flag โ packs into one 64-bit word, mutated only via CAS. A writer-preferring "class of waiters" fairness policy blocks new shared (read) acquisitions whenever an exclusive or promoter waiter exists, so continuous readers can never starve a pending writer indefinitely โ though there's no strict FIFO ordering within a waiter class. A thread holding Shared can call TryPromoteToExclusiveAccess to upgrade in place (no release-then-reacquire window) when it's the sole reader; DemoteFromExclusiveAccess reverses it.
๐ป Usage
private AccessControl _control;
// Shared (read) access, bounded wait
var ctx = WaitContext.FromTimeout(TimeSpan.FromMilliseconds(100));
if (_control.EnterSharedAccess(ref ctx))
{
try { /* read */ }
finally { _control.ExitSharedAccess(); }
}
// Exclusive (write) access, infinite wait โ WaitContext.Null costs nothing extra
_control.EnterExclusiveAccess(ref WaitContext.Null);
try { /* write */ }
finally { _control.ExitExclusiveAccess(); }
// Read, then upgrade in place only if needed
if (_control.EnterSharedAccess(ref ctx))
{
try
{
if (NeedsUpdate() && _control.TryPromoteToExclusiveAccess(ref ctx))
{
try { ApplyUpdate(); }
finally { _control.DemoteFromExclusiveAccess(); }
}
}
finally { _control.ExitSharedAccess(); }
}
| Acquisition | Method | Blocking |
|---|---|---|
| Shared (read) | EnterSharedAccess(ref WaitContext) / TryEnterSharedAccess() |
Waits / non-blocking |
| Exclusive (write) | EnterExclusiveAccess(ref WaitContext) / TryEnterExclusiveAccess() |
Waits / non-blocking |
| Promote shared โ exclusive | TryPromoteToExclusiveAccess(ref WaitContext) |
Waits; fails if other readers are present |
| Demote exclusive โ shared | DemoteFromExclusiveAccess() |
Never blocks |
โ ๏ธ Guarantees & limits
- 8 bytes per instance โ reserve for low-instance-count, high-contention resources; use
AccessControlSmallfor per-node/per-page locks numbering in the thousands. - Writer-preferring fairness via three sticky waiter counters; priority order is Promoter > Exclusive > Shared.
- Max 255 concurrent shared holders and max 255 waiters per class (8-bit counters); overflow is a
Debug.Assertin this type (compareAccessControlSmall, which throws on overflow). WasContendedis a sticky diagnostic bit, cleared only byReset().internaltype โ engine plumbing, not callable from application code.
๐งช Tests
- AccessControlTests โ shared/exclusive acquisition, in-place promote/demote, timeout and cancellation paths,
WasContendedsticky bit, multi-thread blocking/contention.
๐ Related
- Parent feature: Reader-Writer & Resource Lifecycle Locks
- Sibling: AccessControlSmall โ compact 4-byte variant for thousands of embedded per-node/per-page latches.
- Sibling: ResourceAccessControl โ 3-mode lifecycle lock for structures where growth mustn't block readers.