Table of Contents

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 AccessControlSmall for 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.Assert in this type (compare AccessControlSmall, which throws on overflow).
  • WasContended is a sticky diagnostic bit, cleared only by Reset().
  • internal type โ€” engine plumbing, not callable from application code.

๐Ÿงช Tests

  • AccessControlTests โ€” shared/exclusive acquisition, in-place promote/demote, timeout and cancellation paths, WasContended sticky bit, multi-thread blocking/contention.