›
byrcsc/laravel-hold · 1.x
Free capacity from an expired timestamp without waiting for a scheduler.
Expiry is lazy. Every check the package performs reads the clock:
availableSlots(), isFullyHeld(), the active() scope, the status accessor,
and the count inside acquisition. None of them reads a stored status.
The consequence is the whole design. The instant expires_at passes, the slot
is free for the next acquirer, whether or not any command has run.
A hold is active while expires_at is strictly greater than now. A hold whose
expiry equals the current instant is expired.
$hold = $seat->acquireHold($user, expiresAt: now());
$hold->status; // HoldStatus::Expired
$seat->availableSlots(); // 1The scopes agree: active() compares expires_at > now(), and expired()
compares expires_at <= now().
A hold acquired without an expiry has expires_at set to null. It is
indefinite, and no clock will ever free it.
$hold = $seat->acquireHold($user);
$hold->isIndefinite(); // true
$hold->status; // HoldStatus::Active, foreverThree things follow:
release() on it.extend() refuses it, because there is no expiry to push.hold:prune never deletes it, however old it gets.isIndefinite() describes the expiry alone. A released indefinite hold is
still indefinite.
hold:expire does not free slots. They were already free.
The command stamps expired_at on holds whose expiry has passed and fires one
HoldExpired event for each. That stamp is bookkeeping: nothing in the package
reads expired_at to decide anything, and the expired() scope compares
expires_at against the clock rather than looking at the stamp.
Hold::query()->expired()->count(); // 3, before any command runsSchedule the command only when something listens for the event. If nothing does, you do not need the scheduler at all. See scheduling expiry.
HoldExpired fires when the command notices, not at the expiry instant. A hold
can expire and its slot be re-acquired by somebody else before the event fires.
Write listeners that tolerate that. A listener that assumes the slot is still free at the moment it runs is making a guarantee the package does not offer.
When a hold has both timestamps, the status is Released. A release records a
decision; expiry only records the clock passing.
hold:expire respects the same order. A hold released after its expiry passed,
but before the command reached it, is left alone and announces nothing. The
release is what happened to it.
The expired() scope returns clock-expired holds whether or not they carry a
stamp:
use ByRcsc\LaravelHold\Models\Hold;
Hold::query()->expired()->get();To find the holds a command has already announced, filter the stamp yourself:
Hold::query()->expired()->whereNotNull('expired_at')->get(); // announced
Hold::query()->expired()->whereNull('expired_at')->get(); // not yet$hold = $seat->acquireHold($user, expiresAt: now()->addSeconds(2));
$seat->availableSlots(); // 0
$hold->isActive(); // true
sleep(3);
$seat->availableSlots(); // 1, with no command having run
$hold->isActive(); // false
$hold->isExpired(); // true
$hold->expired_at; // still null, nothing has stamped it
$other = $seat->acquireHold($someoneElse); // succeedsRun hold:expire after that and it stamps the first hold and fires
HoldExpired for it, long after the slot changed hands.