pyaesoneaung/atomic-locks-middleware
Laravel middleware that prevents overlapping requests by wrapping routes in atomic cache locks. Guard critical endpoints from concurrent execution with configurable keys, wait times, and automatic release—no boilerplate needed.
Cache::lock() / LockProvider primitives. Treat it as a buy only in the sense of "spend zero engineering days"; the real decision is whether to vendor a 34-star dependency or copy the middleware into app/Http/Middleware and own it. For most teams the honest answer is build-in-house, and this package's value is as a reference implementation and a conversation starter.artisan commands or scheduled HTTP endpoints get withoutOverlapping() semantics for routes, not just jobs. This supports a phased roadmap where the queue infrastructure arrives later; it lets you ship a correct-but-simple version now.WithoutOverlapping job middleware and Laravel's RateLimiter: the decision matrix is "is this a route or a job?" and "do we want to reject concurrent requests (429/423) or queue them?" The package's answer is reject-or-wait at the HTTP layer, which is a real product behaviour choice (user sees "already in progress" vs. a spinner for 30s).Adopt or copy when all of the following hold:
LockProvider — Redis, Memcached, database, or DynamoDB. file and array locks are effectively process-local and will not protect you across multiple app servers; if your production cache is file, stop here.block()) or fail fast? How long until the lock expires (must exceed worst-case request duration)? What status code and body does the caller get? Does the lock release on exception (it must — finally, not just success)? Unanswered, this package turns a race condition into a 500 storm or a deadlock after a crashed worker.Look elsewhere when:
WithoutOverlapping and ->onOneServer(). That gives retries, backoff, and observability for free; a route lock gives you none of that.RateLimiter/throttle middleware. Locks are binary; they do not degrade gracefully under load.For executives (30 seconds): We have a handful of endpoints where two requests arriving at the same time corrupt state or double-charge a customer — retried webhooks, double-clicked buttons, a "sync now" button anyone in ops can press. This package is a small, MIT-licensed helper that lets us mark a route "one at a time" using the cache we already pay for. It is not a platform investment; it is a few lines of code we could write ourselves in an afternoon. Recommendation: do not add it as a long-term dependency — copy the ~30-line middleware into our own codebase, add tests, and treat it as internal hardening work. Expected cost: half a day. Expected benefit: eliminates a class of duplicate-action incidents and removes a blocker on shipping the webhook and admin-tools roadmap items.
For engineering (the real pitch): Laravel already ships atomic locks — Cache::lock(), the Lock object, withoutOverlapping() for jobs. The gap is the HTTP layer, where we currently hand-roll Cache::lock(...)->get() boilerplate in controllers, inconsistently, and usually without a finally release. This package is that middleware, done correctly and small enough to read in one sitting. Use it as the shape of the solution, then own it:
->middleware('atomic-lock:invoice-recalc')).409/423 and a "this is already running" response the frontend can render.finally; never rely on TTL expiry as the release path.file driver — and a store we do not let other features evict.Guardrails we should hold ourselves to: locks are not idempotency and not a substitute for a DB unique constraint. For anything with retries or fan-out, we use a queued job with WithoutOverlapping()->onOneServer() instead. And every locked route gets a test that fires two concurrent requests and asserts exactly one executes — otherwise we have a lock nobody has verified, which is worse than no lock because it buys false confidence.
Risk summary to acknowledge out loud: 34 stars, 0 dependents, single maintainer, last release May 2025. That is fine for a pattern reference and unacceptable for a hidden critical dependency on a payment path. Hence: vendor the code, assign an owner, document the TTL and failure semantics in the runbook, and revisit if the queue migration lands — at which point most of these call sites should become jobs.
How can I help you explore Laravel packages today?