CoolCMS API

CronLockManager
in package

FinalYes

Guards against concurrent cron runs with a two-layer lock.

  • MySQL GET_LOCK() is authoritative. The named lock is held by the process connection until it is released or the process ends (even by a crash), so it does not expire during a long live run.
  • A row in cms_cron_locks (INSERT IGNORE on the PK name, plus ttl) gives visibility for isLocked() and compatibility with a process running the old code during a deployment.

Without the GET_LOCK() layer, a run that exceeded the TTL without a refresh had its expired row deleted by the next start, which acquired the lock while the first process still ran, and the first process's release() then deleted the second one's lock. Now the second process is refused by GET_LOCK() and release() deletes only a lock this instance actually holds.

Fail-open: any error (missing table, DB outage, GET_LOCK returning null) must not block the cron; acquire() then returns true and the cron runs without a lock. Set-based DBAL, no hydration.

IMPORTANT: assumes one connection per process, without a connection pool or proxy (ProxySQL etc.) that swaps connections between queries. If the connection is renewed mid-run, GET_LOCK is lost silently and only the row protection remains.

Table of Contents

Constants

DEFAULT_TTL  : int = 300
MAX_LOCK_NAME  : int = 64
MySQL limits a named lock to 64 characters.
TABLE  : string = 'cms_cron_locks'

Properties

$connection  : Connection
$held  : array<string, true>

Methods

__construct()  : mixed
acquire()  : bool
Acquires the lock; false means another instance runs (the lock exists and has not expired).
isLocked()  : bool
refresh()  : void
Extends a held lock (long batch runs, e.g. product import after each batch): moves `locked_at` to now and sets a new TTL. Only for a lock this instance holds. Fail-open like {@see acquire()}.
release()  : void
Releases the lock only if this instance holds it (otherwise it would delete another run's lock).
lockName()  : string
`GET_LOCK` is valid for the whole DB server, not one database, so two sites on one server would lock each other. The key therefore contains the database name; above the MySQL limit it is hashed.
releaseNamedLock()  : void

Constants

MAX_LOCK_NAME

MySQL limits a named lock to 64 characters.

private int MAX_LOCK_NAME = 64

Properties

$held

private array<string, true> $held = []

locks held by this instance (and thus this process)

Methods

__construct()

public __construct(Connection $connection) : mixed
Parameters
$connection : Connection

acquire()

Acquires the lock; false means another instance runs (the lock exists and has not expired).

public acquire(string $name[, int $ttl = self::DEFAULT_TTL ]) : bool
Parameters
$name : string
$ttl : int = self::DEFAULT_TTL
Return values
bool

isLocked()

public isLocked(string $name) : bool
Parameters
$name : string
Return values
bool

refresh()

Extends a held lock (long batch runs, e.g. product import after each batch): moves `locked_at` to now and sets a new TTL. Only for a lock this instance holds. Fail-open like {@see acquire()}.

public refresh(string $name[, int $ttl = self::DEFAULT_TTL ]) : void
Parameters
$name : string
$ttl : int = self::DEFAULT_TTL

release()

Releases the lock only if this instance holds it (otherwise it would delete another run's lock).

public release(string $name) : void
Parameters
$name : string

lockName()

`GET_LOCK` is valid for the whole DB server, not one database, so two sites on one server would lock each other. The key therefore contains the database name; above the MySQL limit it is hashed.

private lockName(string $name) : string
Parameters
$name : string
Return values
string

releaseNamedLock()

private releaseNamedLock(string $name) : void
Parameters
$name : string
On this page

Search results