Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions docs/features/l1-invalidation.md
Original file line number Diff line number Diff line change
Expand Up @@ -291,9 +291,9 @@ A process created by `fork()`, such as a `multiprocessing` fork-context worker o

The parent's entries stay in the child's memory, shared with the parent, until the child first stores a value in its L1. Then a background thread frees them. Freeing copies the memory they held into the child, about 2.3 times the size of the parent's L1 in cachekit's own measurement, because each free also writes to the memory allocator's bookkeeping beside the entry; the child's own L1 then reuses that memory. That copy avoids a larger one. The parent's background sweep rewrites those entries as they expire, within one L1 TTL, and a child that kept them would from then on hold a private copy that nothing can reach, beside its own L1. A master that fills no L1 before it forks avoids both.

uWSGI forks its workers without running Python's at-fork hooks. Run every app that uses cachekit with `--enable-threads --py-call-uwsgi-fork-hooks` (uWSGI 2.0.21 or later); that keeps the rule above. Without the fork-hook flag, and with the app loaded in the master (uWSGI's default), a worker keeps the L1 entries its master held at fork, runs no background sweep of expired L1 entries, and runs no invalidation listener, so its L1 heals by TTL. An expired entry is still never served: it is evicted when it is read. `--lazy-apps` is no substitute: without the flag, each worker first imports cachekit after the fork, so cachekit takes it for a fresh process and may start a thread there that hangs. Nor is `--py-call-osafterfork`: with `--enable-threads` it aborts every worker on Python 3.13 and later, and on earlier versions it does not hold the master's threads off at the fork, so a worker can hang. Under uWSGI without `--py-call-uwsgi-fork-hooks`, cachekit logs one WARNING `uWSGI is running without py-call-uwsgi-fork-hooks` when the master imports it. A process uWSGI forked logs nothing, because a log call there can hang on a handler lock that a master thread held at the fork. So under `--lazy-apps`, or when your app first imports cachekit in a worker, no WARNING appears. The [Deployment Reference](https://docs.cachekit.io/python/reference/deployment/) gives the full uWSGI setup.
uWSGI forks its workers without running Python's at-fork hooks. Run every app that uses cachekit with `--enable-threads --py-call-uwsgi-fork-hooks` (uWSGI 2.0.21 or later); that keeps the rule above. Without the fork-hook flag, and with the app loaded in the master (uWSGI's default), a worker keeps the L1 entries its master held at fork, runs no background sweep of expired L1 entries, and runs no invalidation listener, so its L1 heals by TTL. An expired entry is still never served: it is evicted when it is read. A sync function with `stale_ttl` serves a stale L2 entry there without revalidating it in the background, until the entry hard-expires; the first read after that recomputes it. `--lazy-apps` is no substitute: without the flag, each worker first imports cachekit after the fork, so cachekit takes it for a fresh process and may start a thread there that hangs. Nor is `--py-call-osafterfork`: with `--enable-threads` it aborts every worker on Python 3.13 and later, and on earlier versions it does not hold the master's threads off at the fork, so a worker can hang. Under uWSGI without `--py-call-uwsgi-fork-hooks`, cachekit logs one WARNING `uWSGI is running without py-call-uwsgi-fork-hooks` when the master imports it. A process uWSGI forked logs nothing, because a log call there can hang on a handler lock that a master thread held at the fork. So under `--lazy-apps`, or when your app first imports cachekit in a worker, no WARNING appears. The [Deployment Reference](https://docs.cachekit.io/python/reference/deployment/) gives the full uWSGI setup.

L1-only mode (`backend=None`, `@cache.local()`) has no L2 to refill from, so a forked child keeps the entries its parent held at fork, each expiring on its own TTL. The exception is a function whose cache a parent thread was using at the fork: that cache starts empty in the child. A background refresh that was running in the parent starts again on the child's next stale read. Under uWSGI this needs `py-call-uwsgi-fork-hooks`, as above: without it, a worker's first call to an L1-only cached function can hang if a master thread was using that function's cache at the fork.
L1-only mode (`backend=None`, `@cache.local()`) has no L2 to refill from, so a forked child keeps the entries its parent held at fork, each expiring on its own TTL. The exception is a function whose cache a parent thread was using at the fork: that cache starts empty in the child. A background refresh that was running in the parent starts again on the child's next stale read. Under uWSGI this needs `py-call-uwsgi-fork-hooks`, as above. Without it, a worker refreshes no entry of a sync function in the background: it serves the stale value until the entry expires, and the first read after that recomputes it. A worker's first call to an L1-only cached function can also hang if a master thread was using that function's cache at the fork.

---

Expand Down
Loading
Loading