Atomic Locks¶
Available from version 4.8.0
Atomic locks provide a simple way to prevent the same task from running concurrently across requests, CLI commands, or workers that share the same cache storage.
Locks are advisory. Your code must acquire the lock before entering the critical section, and release it when the work is finished.
Configuration¶
The Locks library uses the Cache service. The cache handler must support owner-aware lock operations. The built-in File, Redis, and Predis cache handlers support atomic lock operations. The Memcached cache handler also supports locks, with Memcached-specific limitations described below.
Note
Locks are most useful when all competing processes share the same cache storage. The File handler is suitable for a single server. For multiple application servers, use a shared handler such as Redis.
Important
Locks are stored in the configured cache handler. Clearing or flushing that cache storage, for example with cache()->clean() or a Redis FLUSHDB, may remove active locks. Avoid clearing shared lock storage while lock-protected work is running, or use a dedicated cache store for locks when that separation is important.
Note
Memcached lock support requires the memcached PHP extension. The older memcache extension does not provide the CAS operations used for owner-aware refresh and best-effort release. Memcached does not provide an atomic compare-and-delete command, so releasing a Memcached lock cannot provide the same owner-checked atomic release guarantee as Redis or Predis. A delayed release may delete a lock that expired and was acquired by another owner. Memcached locks may also be lost if the Memcached server restarts, evicts keys, or flushes its cache.
Note
File-backed locks clear released and expired lock contents, but may leave empty lock files in the cache directory. These files do not represent active locks and may be removed by normal cache cleanup when no lock-protected work is running.
Example Usage¶
You can create a lock through the locks service. The second argument is the lock TTL, in seconds. The TTL prevents abandoned locks from being held forever if a process exits unexpectedly.
<?php
$lock = service('locks')->create('reports.daily-export', 300);
if (! $lock->acquire()) {
return;
}
try {
// Run the work that must not overlap.
} finally {
$lock->release();
}
Warning
If the work takes longer than the lock TTL, another process may acquire the same lock while the first process is still running. For long-running work, choose a TTL that comfortably covers the operation, call refresh() while the lock is held, or check isAcquired() before performing irreversible side effects.
Running a Callback¶
The run() method acquires the lock, runs the callback, and releases the lock in a finally block.
<?php
$result = service('locks')
->create('reports.daily-export', 300)
->run(static fn () => build_daily_report());
If the lock cannot be acquired, run() returns false and the callback is not called.
Blocking¶
The block() method waits up to the given number of seconds for the lock to become available:
<?php
$lock = service('locks')->create('imports.customer-feed', 300);
if ($lock->block(10)) {
try {
import_customer_feed();
} finally {
$lock->release();
}
}
Restoring a Lock by Owner¶
Each acquired lock has an owner token. You may pass this token to another process and restore the lock there, for example to release a lock from a queued worker that continues work started by the current request.
<?php
$lock = service('locks')->create('exports.monthly', 300);
if ($lock->acquire()) {
queue_export_job($lock->owner());
}
// Later, in another process:
$restored = service('locks')->restore('exports.monthly', $owner);
$restored->release();
Locks and Cache Handlers¶
The default File cache handler supports locks, so locks work without additional configuration in a standard application.
If the configured cache handler does not support locks, resolving the locks service or constructing a lock manager throws a CodeIgniter\Lock\Exceptions\LockException.
Custom cache handlers can support locks by implementing CodeIgniter\Cache\LockStoreProviderInterface and returning a CodeIgniter\Cache\LockStoreInterface instance. This keeps lock support opt-in and does not require all cache handlers to implement lock operations.
Custom lock stores must implement owner-aware acquisition, release, refresh, force release, and owner lookup methods. acquireLock() should atomically claim the lock for an owner token and TTL. releaseLock() and refreshLock() must only affect the lock when the supplied owner token still matches the current owner. forceReleaseLock() intentionally ignores ownership, and getLockOwner() should return null when the lock is absent or expired.
Class Reference¶
CodeIgniter\Lock¶
LockManager¶
create(string $name[, int $ttl = 300[, ?string $owner = null]])¶
-
Parameters
- $name
stringThe logical lock name. - $ttl
intNumber of seconds before the lock expires. - $owner
string|nullOptional owner token.
- $name
-
Returns: A lock instance.
-
Return type:
LockInterface
Creates a lock for the given logical name.
restore(string $name, string $owner[, int $ttl = 300])¶
-
Parameters
- $name
stringThe logical lock name. - $owner
stringThe owner token. - $ttl
intNumber of seconds before the lock expires.
- $name
-
Returns: A lock instance.
-
Return type:
LockInterface
Restores a lock instance for an existing owner token.
LockInterface¶
acquire()¶
- Returns:
trueif the lock was acquired,falseotherwise. - Return type:
bool
block(int $seconds)¶
-
Parameters
- $seconds
intMaximum number of seconds to wait.
- $seconds
-
Returns:
trueif the lock was acquired,falseotherwise. -
Return type:
bool
run(Closure $callback[, int $waitSeconds = 0])¶
-
Parameters
- $callback
ClosureThe callback to run while the lock is held. - $waitSeconds
intMaximum number of seconds to wait.
- $callback
-
Returns: The callback result, or
falseif the lock was not acquired. -
Return type:
mixed
release()¶
- Returns:
trueif the lock was released by its owner. - Return type:
bool
forceRelease()¶
- Returns:
trueif the lock was force released. - Return type:
bool
Releases the lock without checking the owner token.
refresh([?int $ttl = null])¶
-
Parameters
- $ttl
int|nullNumber of seconds before the lock expires.
- $ttl
-
Returns:
trueif the owned lock was refreshed. -
Return type:
bool
isAcquired()¶
- Returns:
trueif this lock instance still owns the lock. - Return type:
bool
owner()¶
- Returns: The owner token.
- Return type:
string