-
Notifications
You must be signed in to change notification settings - Fork 4
Exclusive access
Some maintenance operations must run alone — no other flow of the application reading or writing the same collections while they proceed. Scrinium provides exclusive access for exactly this, and uses it internally for seeding and migrations. Exclusive access lives on the engine, so it binds the flows of this application process; other processes on the same database keep reading and writing normally. Excluding them is the job of the db context lock, which the same seeding and migrations claim alongside it.
RunWithExclusiveAccessAsync on the engine acquires exclusive access for the
duration of an action:
await db.Engine.RunWithExclusiveAccessAsync(async () =>
{
// this flow runs alone against the engine's collections
}, lockOnRead: true);-
lockOnRead: true(default) locks out other flows' reads and writes;falselocks out only writes. - While it runs,
Engine.IsExclusiveReadEnabled/IsExclusiveWriteEnabledreport the state.
The window opens in two steps. First the engine sets its exclusive flags, so every guarded operation starting from now on is denied (or admitted by an allowance, see below). Then it drains the operations admitted a moment before the flags flipped, still running against the collections: every guarded operation counts in flight on the engine for the span of its forwarded driver call, and the action starts only when the counted operations complete — writes always, reads only when locked on read, since reads left open keep flowing during the whole window.
- Operations admitted by an allowance never count: they are the ones meant to work during the window.
- A cursor already handed out iterates past the drain: the count covers the call that opened it, not its iteration.
- The drain is bounded by the
ExclusiveAccessDrainTimeoutdb context option (5 minutes by default): an operation still in flight at the timeout denies the window with aTimeoutExceptionnaming the context and the counts, and the engine returns to normal access — the action never runs beside an operation admitted before it.
While a flow holds exclusive access, any other flow of this process that writes the engine's
collections — and, when locked on read (the default), any read too — gets an
UnauthorizedAccessException (thrown by the guarded collection wrapper, index management included). This is why a migration effectively pauses the application's normal
traffic against the affected context — on this instance. Other instances of the same application see
nothing: they are excluded from starting a seeding or a migration by the
db context lock, not from their regular traffic. A migration
dry run takes no exclusive access: it persists nothing, so the application keeps
working while it scans (it holds the db context lock all the same).
The flow that acquired exclusive access keeps working normally. Work that legitimately must continue during a migration — the Admin dashboard's migration-status reads, for instance — runs with an internal exclusive-access allowance, so it isn't blocked. This allowance is managed by Scrinium; its handler type is internal, so app code can't create one.
An allowance opens only the engine that granted it. It matters when one flow touches more than one db context, as an HTTP request or a migration processor writing an audit record does: the ambient state is shared by the whole flow, while the lock is per engine, so holding an allowance on one context doesn't open another one that somebody else is migrating.
Dependency-update tasks deliberately hold no allowance: executed while a flow holds exclusive access they fail like any other, and the task runner's retry converges them on the post-exclusive state — background propagation never interleaves with a migration.
Reach for RunWithExclusiveAccessAsync directly only
for custom maintenance that must run without concurrent access to the context's data — and expect
other requests hitting that context to fail with UnauthorizedAccessException for the duration, and
the call itself to wait for the requests already in flight before starting, so run it in a maintenance
window.
If the application runs in more than one instance, pair it with the db context lock: exclusive access alone leaves the other instances free to run the same maintenance at the same time.
Next: Db context lock for the cross-instance side, or Migrations and Database seeding, the built-in users of both.
Scrinium — source · issues (SCR) · GNU LGPL-3.0 · info@etherna.io
Getting started
Core concepts
Working with data
Serialization & mapping
Operations & maintenance
Advanced & reference