Repository navigation
Releases: rasuvaeff/bulkhead
Releases · rasuvaeff/bulkhead
Release list
v1.4.0
- Add
ContextAwareBulkheadandSharedBulkhead::withContext(Context): the same slots, with the wait capped by arasuvaeff/contextbudget (#48). When the budget ends first the wait ends withContextExpiredException/ContextCancelledException— notBulkheadFullException, andonRejectedis not called: the caller's exhausted budget is not an overload signal. A terminated context takes no slot.rasuvaeff/context^0.3 becomes a runtime dependency. - CI: experimental PHP 8.6 job (RC, tests only, not required).
v1.3.0
- Add
KeyedBulkhead: the same wait, lease and observer semantics asSharedBulkhead, with the name and limit passed per call (call(name, max, cb),acquire(),tryAcquire(),availableSlots()) — for one limit per proxy, tenant or browser context without dropping to the rawBulkheadStoreand a hand-writtentry/finally(#43). - Add
Slot, a held slot as a value with an idempotentrelease(), returned by the newSharedBulkhead::acquire()/tryAcquire()and theirKeyedBulkheadtwins: take a slot in one place, release it where the work ends (#43).acquire()waits up tomaxWaitand throwsBulkheadFullException;tryAcquire()never waits and returnsnull. A throwingonAcceptedreleases the slot before rethrowing, as incall(). BulkheadFullExceptionnow carrieswaited(time spent waiting) andlease, andretryAfter(): ?Durationreturns the lease as an upper bound on when a slot frees — aRetry-Afterceiling (#44). Both are new optional constructor parameters; an exception built by hand still works and reportsnull.Sleeper\FakeSleeperaccepts an optionalonSleepclosure called after each recorded sleep, so a test can advance a fake clock without its own sleeper (#45, additive part).- CI: tolerate roave's all-
SKIPPEDBC report, split the mutation path filter, verify the tagged commit before publishing a release.
v1.2.0
- Add
BatchBulkheadStore::activeCounts(list $names): array— an active-load snapshot for many bulkheads at once, for least-loaded selection among hundreds of candidates without one store round trip per name (#38). All three bundled stores implement it: read-only for slots,0for names without active slots, duplicate names collapsed, first-seen order.RedisBulkheadStoredoes it in one Lua round trip that prunes expired leases and counts every key at the same serverTIME(mutually consistent counts; any Redis error propagates, no partial result); APCu and in-memory read per name. Redis Cluster needs a hash tag inkeyPrefix({bulkhead}:) or the multi-key call fails withCROSSSLOT. - Add
BulkheadMultiKeyScriptRunner::runMany(script, keys, args): list<int>, implemented byPredisScriptRunnerandPhpRedisScriptRunner.RedisBulkheadStoreuses it foractiveCounts()and degrades to oneactiveCount()call per name with a custom single-keyBulkheadScriptRunner. Existing interfaces are unchanged. - Switch the test suite's hand-rolled doubles (anonymous
BulkheadStorescripted/throwing/poll-budget fakes and the predisClientInterfacefake) torasuvaeff/understudyvia therasuvaeff/understudy-testoadapter:scriptedStore(n)is a->returns(null × n, 'token')chain (last link repeats), the poll-budget fake is->returns(...)->then()->throws(), an always-full store is a loose double with no stubs, and verification/reset run automatically after each test. Dev-only; no runtime changes.
v1.1.3
- Fix a slot leak: the
onAcceptedobserver callback ran after acquire but outside thetry/finallythat guaranteesrelease(), so a throwing observer (unreachable metrics backend, aTypeErrorin the closure) left the slot occupied — until lease expiry on Redis/APCu, and permanently withInMemoryBulkheadStore(which ignores the lease). The callback now runs inside thetry. - Document the release-path failure semantics: a
release()exception masks the callback's exception (reachable viagetPrevious(), which PHP chains automatically) and loses a successful result — a store exception fromcall()means "outcome unknown", never "did not happen". Pinned by tests. - Docs: fix the name-pattern anchor shown in README.md/README.ru.md/llms.txt to the
\zthe code actually uses ($matches before a trailing newline); document thatkeyPrefixis trusted configuration (not validated, unlikename) and the token trust model (CSPRNG tokens protect against accidents, not adversaries — the trust boundary is Redis access itself).
v1.1.2
- Document that the store is a hard dependency: if Redis/APCu is unreachable,
call()fails closed (throws) rather than admitting the call unboundedly. Callers who only caughtBulkheadFullExceptioncould be surprised by an uncaughtPredis\Connection\ConnectionException(or equivalent) on a store outage. - Raise
rasuvaeff/property-testing-testoto^0.6.
v1.1.1
v1.1.0
- Ship an AI agent skill (
resources/skills/rasuvaeff-bulkhead/SKILL.md+
extra.skillsin composer.json): projects using thellm/skillsComposer
plugin get the skill synced into.agents/skills/automatically on install. - Bump
rasuvaeff/property-testingdev dependency to^2.6. - Property-generator methods in tests are now
public static(private ones are
removed by rector'sRemoveUnusedPrivateMethodRector— they are only called
via reflection).
v1.0.1
PredisScriptRunnerissues EVALSHA/EVAL through the typed
createCommand()/executeCommand()API instead of magic__call— the
@methodannotations foreval/evalshaare not resolvable by psalm across
every supported predis release (broke the prefer-lowest CI job).
v1.0.0
- Initial release:
SharedBulkheadcross-process concurrency limiter with
RedisBulkheadStore(multi-host, sorted set + Lua, EVALSHA-first script
runners for predis andext-redis),ApcuBulkheadStore(single-host,
APCu spinlock) andInMemoryBulkheadStore(tests/CLI). - Optional
pollJitterdesynchronizes waiters polling for a freed slot. onAccepted/onRejectedcallbacks receive the bulkhead name and the time
waited for a slot.- All Redis clients are optional dependencies (
suggest): install
predis/predisorext-redisonly if you useRedisBulkheadStore.