CoolCMS API

ReservationExpirer
in package
uses SourceLockTransactionTrait

FinalYes

Cron worker (this is the service only, the presenter is separate): releases lapsed active reservations (`status=ACTIVE AND expires_datetime < NOW()`). Raw DBAL, per-source serialization, deadlock retry.

For each distinct source with lapsed rows it takes a non-blocking GET_LOCK(source, 0): a busy source (a concurrent confirm/reserve/release holds the lock) is skipped and reconciled on the next run. Under the lock, in a transaction, the rows are re-selected FOR UPDATE with the same condition — a concurrent confirm may have flipped them to CONFIRMED between the discovery scan and the lock, so the re-check is what makes "confirm × expire" safe (exactly one of the two wins). The advisory lock is always released in a finally.

Table of Contents

Constants

LEVELS  : string = 'cms_mod_store_stock_levels'
LOCK_MAX_ATTEMPTS  : int = 3
LOCK_SCALE  : int = 3
LOCK_TIMEOUT  : int = 0
RESERVATIONS  : string = 'cms_mod_store_stock_reservations'

Properties

$em  : EntityManagerInterface

Methods

__construct()  : mixed
run()  : array{sources: int, expiredSources: int, skipped: int, rowsExpired: int}
acquireSourceLock()  : bool
Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).
connection()  : Connection
lockLogChannel()  : string
Tracy log channel for a failed `RELEASE_LOCK`. Override per module (Eshop: `eshop.general`).
releaseSourceLock()  : void
Releases the advisory lock.
runInSourceTransaction()  : T
Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).
ladd()  : numeric-string
lcmp()  : int
ldec()  : numeric-string
ldiv()  : numeric-string
Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).
lInt()  : int
lmul()  : numeric-string
ln()  : numeric-string
lStr()  : string
lStrN()  : string|null
lsub()  : numeric-string

Constants

Properties

Methods

__construct()

public __construct(EntityManagerInterface $em) : mixed
Parameters
$em : EntityManagerInterface

run()

public run() : array{sources: int, expiredSources: int, skipped: int, rowsExpired: int}
Return values
array{sources: int, expiredSources: int, skipped: int, rowsExpired: int}

acquireSourceLock()

Calls `GET_LOCK(name, timeout)` and returns true only on a definite 1 (0 = timed out or busy, null = error). `$timeout` is in seconds; 0 is non-blocking (try-lock, used by the expirer).

protected acquireSourceLock(string $name, int $timeout) : bool
Parameters
$name : string
$timeout : int
Return values
bool

lockLogChannel()

Tracy log channel for a failed `RELEASE_LOCK`. Override per module (Eshop: `eshop.general`).

protected lockLogChannel() : string
Return values
string

releaseSourceLock()

Releases the advisory lock.

protected releaseSourceLock(string $name) : void

IMPORTANT: it is called from a finally, so a failure here (a gone-away connection after a fatal query) is swallowed and logged; rethrowing would mask the original exception being unwound. MySQL drops session locks when the connection dies anyway, so nothing leaks past the process.

Parameters
$name : string

runInSourceTransaction()

Runs a closure in one raw-DBAL transaction with a deadlock retry (`SQLSTATE` 40001 / errno 1213).

protected runInSourceTransaction(callable(Connection): T $fn) : T

The advisory lock (held by the caller across this call) is not released by a rollback, so every attempt runs under the same serialization.

Parameters
$fn : callable(Connection): T
Tags
template
Return values
T

ladd()

private ladd(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

ldiv()

Divides with half-up rounding to scale 3 (bcmath itself truncates; same rounding as {@see \App\UI\Api\Eshop\Models\Helpers\OrderCalculator::round3()}).

private ldiv(string $a, string $b) : numeric-string

IMPORTANT: truncation would systematically drop up to 0.001 from every unit on large quantities, so the invariant sum(unitPrice x quantity) ~ sum(rowBase) would drift in one direction (an issue document undervalued against the order).

Parameters
$a : string
$b : string
Return values
numeric-string

lmul()

private lmul(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string

lsub()

private lsub(string $a, string $b) : numeric-string
Parameters
$a : string
$b : string
Return values
numeric-string
On this page

Search results