forked from retoor/devplacepy
feat: add six specialized Claude agent definitions under .claude/agents for audit, devii, docs, dry, fanout, and frontend maintenance
This commit is contained in:
@@ -1,7 +1,7 @@
|
||||
<div class="docs-content" data-render>
|
||||
# Multi-worker and concurrency
|
||||
|
||||
DevPlace runs safely under multiple Uvicorn workers. This page documents the model, what is safe by design, and the rules that keep it safe. See also [Production overview](/docs/production.html), [Deploy and update](/docs/production-deploy.html), and [nginx and networking](/docs/production-nginx.html).
|
||||
DevPlace runs safely under multiple Uvicorn workers. This page covers the model, what is safe by design, and the rules that keep it safe. See also [Production overview](/docs/production.html), [Deploy and update](/docs/production-deploy.html), and [nginx and networking](/docs/production-nginx.html).
|
||||
|
||||
## The model
|
||||
|
||||
@@ -19,9 +19,9 @@ DevPlace runs safely under multiple Uvicorn workers. This page documents the mod
|
||||
The in-process caches (`_user_cache` in `utils.py`, `_settings_cache` in `database.py`) are fast but per-process. To keep them correct across workers, invalidation is coordinated through a `cache_state` table of named integer versions:
|
||||
|
||||
- A mutation that must invalidate a cache calls `bump_cache_version(name)` - an atomic `UPDATE ... version + 1` in the shared table.
|
||||
- Before serving from a cache, the worker calls `sync_local_cache(name, cache)`, which reads the table's version directly and compares it to the version its local cache was built against; if they differ, it clears the local cache and rebuilds from the database. The version read is an uncached indexed primary-key lookup on the tiny `cache_state` table, so invalidation is immediate across workers and never lags behind a write.
|
||||
- Before serving from a cache, the worker calls `sync_local_cache(name, cache)`, which reads the table's version and compares it to the version its local cache was built against; if they differ, it clears and rebuilds the local cache from the database. The version read is an uncached indexed primary-key lookup on the tiny `cache_state` table, so invalidation is immediate across workers.
|
||||
|
||||
The result: a logout, ban, role change, or settings save in one worker is honored by every other worker on its next request, while the common path stays a cheap in-memory hit on the data itself. `auth` covers `_user_cache` (logout, deactivation, role and password changes); `settings` covers `_settings_cache` (every `set_setting` and the admin settings save).
|
||||
A logout, ban, role change, or settings save in one worker is therefore honored by every other worker on its next request, while the common path stays a cheap in-memory hit. `auth` covers `_user_cache` (logout, deactivation, role and password changes); `settings` covers `_settings_cache` (every `set_setting` and the admin settings save).
|
||||
|
||||
## Rate limiting
|
||||
|
||||
@@ -29,10 +29,10 @@ The rate limiter counts per IP in a per-process structure, so N workers would ot
|
||||
|
||||
## First-boot serialization
|
||||
|
||||
Two startup actions are not safe to run concurrently from a cold state, so both are serialized with a file lock:
|
||||
Two startup actions are unsafe to run concurrently from a cold state, so both are serialized with a file lock:
|
||||
|
||||
- **Database initialization.** Every worker runs `init_db()` at startup; the seed-if-missing inserts have a check-then-insert race that could duplicate rows on a fresh database. Startup wraps `init_db()` in a blocking `flock` on `devplace-init.lock`, so one worker initializes while the others wait, then run the idempotent path.
|
||||
- **VAPID key generation.** If the push keys are absent, concurrent workers could each generate a different keypair and overwrite each other, breaking push. `ensure_certificates()` early-returns when the keys exist and otherwise generates them under a `.vapid.lock` `flock`, so exactly one worker creates the keypair.
|
||||
- **VAPID key generation.** If the push keys are absent, concurrent workers could each generate a different keypair and overwrite each other, breaking push. `ensure_certificates()` early-returns when the keys exist, and otherwise generates them under a `.vapid.lock` `flock`, so exactly one worker creates the keypair.
|
||||
|
||||
## Preferred rules
|
||||
|
||||
|
||||
Reference in New Issue
Block a user