SourceLockTransactionTrait
Shared concurrency framework for writers serialized per source document, following the `DocumentService` pattern: the Store reservation writers (`ReservationService`, `ReservationExpirer`), Eshop order, complaint and credit-note managers, the Invoicer credit-note manager and the Company reservation, time-slot, work-list and work-sheet managers. It is not applied to `DocumentService` itself, which keeps its own private copy. A trait is not a class, so the DI scan never tries to instantiate it. A consumer outside Store may override `lockLogChannel()` to log into its own channel; a trait is not a cross-module dependency, it becomes part of the consuming class after `use`.
The advisory lock is a MySQL session lock on SourceRef::lockName():
GET_LOCKbeforebeginTransaction(),RELEASE_LOCKin afinally;- it survives a transaction rollback and the deadlock retry, so it is held across every attempt;
- IMPORTANT:
GET_LOCKand the transaction must run on the same connection (same MySQL session), otherwise the lock does not protect the transaction; hence the single connection() accessor.
Also carries the bcmath (scale 3) and guarded DBAL row accessors both services need, so neither duplicates them.
Table of Contents
Constants
- LOCK_MAX_ATTEMPTS : int = 3
- LOCK_SCALE : int = 3
Methods
- 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
- The single connection used for both the advisory lock and the transaction.
- 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
LOCK_MAX_ATTEMPTS
private
int
LOCK_MAX_ATTEMPTS
= 3
LOCK_SCALE
private
int
LOCK_SCALE
= 3
Methods
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
boolconnection()
The single connection used for both the advisory lock and the transaction.
protected
abstract connection() : Connection
Return values
ConnectionlockLogChannel()
Tracy log channel for a failed `RELEASE_LOCK`. Override per module (Eshop: `eshop.general`).
protected
lockLogChannel() : string
Return values
stringreleaseSourceLock()
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
Return values
Tladd()
private
ladd(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string
Return values
numeric-stringlcmp()
private
lcmp(string $a, string $b) : int
Parameters
- $a : string
- $b : string
Return values
intldec()
private
ldec(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringldiv()
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-stringlInt()
private
lInt(mixed $value) : int
Parameters
- $value : mixed
Return values
intlmul()
private
lmul(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string
Return values
numeric-stringln()
private
ln(string $value) : numeric-string
Parameters
- $value : string
Return values
numeric-stringlStr()
private
lStr(mixed $value) : string
Parameters
- $value : mixed
Return values
stringlStrN()
private
lStrN(mixed $value) : string|null
Parameters
- $value : mixed
Return values
string|nulllsub()
private
lsub(string $a, string $b) : numeric-string
Parameters
- $a : string
- $b : string