Skip to content

style(redis): join re-export import as ruff 0.16.6 sorts it - #53

Merged
allen0099 merged 21 commits into
masterfrom
feature/atomic-backend-primitives
Sep 5, 2026
Merged

style(redis): join re-export import as ruff 0.16.6 sorts it#53
allen0099 merged 21 commits into
masterfrom
feature/atomic-backend-primitives

Conversation

@allen0099

Copy link
Copy Markdown
Owner

No description provided.

Redis and Memcached each carried their own copy of the CacheEntry JSON
serialization (including the orjson fallback); it now lives in
backends/codec.py, and decode_entry treats every malformed document as a
miss instead of letting a non-object JSON value escape as a TypeError.

The Redis backend's four SCAN loops collapse into _scan_keys/_delete_keys,
and the memory backend's clear_path/clear_pattern share one key parser
and one locked eviction pass.
…ocket

Every MemcachedBackend method runs the synchronous pymemcache client in a
worker thread, but the client is not thread-safe: two overlapping calls
interleave on one socket and read each other's replies (an incr would
see b'STORED'). HashClient(use_pooling=True) gives each worker its own
connection.
BaseCacheBackend.increment(key, delta=1, ttl=None) adds delta to an
integer counter and returns the new value. The counter is created on
first use and ttl applies only then, so it lives in a fixed window: the
shape rate limiters need without a get-then-set race.

MemoryBackend does the read-modify-write under its lock, Redis runs a
registered Lua script (EXISTS + INCRBY + EXPIRE in one server-side step)
and Memcached uses ADD + INCR/DECR. The base class keeps a non-atomic
fallback so third-party subclasses stay instantiable. Counters surface
through get() as a CacheEntry with the COUNTER_FINGERPRINT fingerprint
and the decimal value as content, so delete/clear/monitoring treat them
like any other entry; incrementing a key that holds a cached response
raises CacheXError on every backend.
StateManager.consume_state did get -> expiry check -> delete, so two
concurrent callbacks presenting the same OAuth state could both be
accepted. BaseCacheBackend.get_and_delete removes and returns the entry
in one step: MemoryBackend pops under its lock, Redis uses GETDEL
(server 6.2+) and Memcached returns the value only when its own DELETE
won. The base class keeps a non-atomic fallback so third-party
subclasses stay instantiable; delete() itself is unchanged.

consume_state, delete_state, CacheManager.delete and invalidate() now
report accurately under contention, and a state whose wall-clock
expires_at has passed is removed from the backend instead of lingering
until its TTL.
With use_pooling=True a noreply set can still be in flight on one socket
while a get on another socket is already served, so a caller could miss
its own write (seen as a flaky get_and_delete winner test). Every command
now waits for the server reply.
Renovate's lock maintenance moved ruff to 0.16, where both rules left
preview and are now pulled in by extend-select = ['ALL']. The project has
no copyright-header convention, and the Redis backend's constructor
options are positional public API.
@allen0099
allen0099 merged commit 8a7dd08 into master Sep 5, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant