Skip to content

Commit ce8502e

Browse files
committed
docs(lock): document atomic locks
Signed-off-by: memleakd <[email protected]>
1 parent 5232f87 commit ce8502e

7 files changed

Lines changed: 204 additions & 0 deletions

File tree

user_guide_src/source/changelogs/v4.8.0.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -222,6 +222,7 @@ Libraries
222222

223223
- **Context**: This new feature allows you to easily set and retrieve normal or hidden contextual data for the current request. See :ref:`Context <context>` for details.
224224
- **Images:**: Added support for the AVIF file format.
225+
- **Locks:** Added :doc:`Atomic Locks </libraries/locks>` for owner-aware, cross-process mutual exclusion backed by supported cache handlers.
225226
- **Logging:** Log handlers now receive the full context array as a third argument to ``handle()``. When ``$logGlobalContext`` is enabled, the CI global context is available under the ``HandlerInterface::GLOBAL_CONTEXT_KEY`` key. Built-in handlers append it to the log output; custom handlers can use it for structured logging.
226227
- **Logging:** Added :ref:`per-call context logging <logging-per-call-context>` with three new ``Config\Logger`` options (``$logContext``, ``$logContextTrace``, ``$logContextUsedKeys``). Per PSR-3, a ``Throwable`` in the ``exception`` context key is automatically normalized to a meaningful array. All options default to ``false``.
227228

user_guide_src/source/libraries/index.rst

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ Library Reference
1515
file_collections
1616
honeypot
1717
images
18+
locks
1819
pagination
1920
publisher
2021
security
Lines changed: 162 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,162 @@
1+
############
2+
Atomic Locks
3+
############
4+
5+
.. versionadded:: 4.8.0
6+
7+
.. contents::
8+
:local:
9+
:depth: 2
10+
11+
Atomic locks provide a simple way to prevent the same task from running
12+
concurrently across requests, CLI commands, or workers that share the same
13+
cache storage.
14+
15+
Locks are advisory. Your code must acquire the lock before entering the
16+
critical section, and release it when the work is finished.
17+
18+
*************
19+
Configuration
20+
*************
21+
22+
The Locks library uses the Cache service. The cache handler must support atomic
23+
lock operations. The built-in **File**, **Redis**, and **Predis** cache handlers
24+
support locks.
25+
26+
.. note:: Locks are most useful when all competing processes share the same cache
27+
storage. The File handler is suitable for a single server. For multiple
28+
application servers, use a shared handler such as Redis.
29+
30+
*************
31+
Example Usage
32+
*************
33+
34+
You can create a lock through the ``locks`` service. The second argument is the
35+
lock TTL, in seconds. The TTL prevents abandoned locks from being held forever
36+
if a process exits unexpectedly.
37+
38+
.. literalinclude:: locks/001.php
39+
40+
.. warning:: If the work takes longer than the lock TTL, another process may
41+
acquire the same lock while the first process is still running. For
42+
long-running work, choose a TTL that comfortably covers the operation, call
43+
``refresh()`` while the lock is held, or check ``isAcquired()`` before
44+
performing irreversible side effects.
45+
46+
Running a Callback
47+
==================
48+
49+
The ``run()`` method acquires the lock, runs the callback, and releases the lock
50+
in a ``finally`` block.
51+
52+
.. literalinclude:: locks/002.php
53+
54+
If the lock cannot be acquired, ``run()`` returns ``null`` and the callback is
55+
not called.
56+
57+
Blocking
58+
========
59+
60+
The ``block()`` method waits up to the given number of seconds for the lock to
61+
become available:
62+
63+
.. literalinclude:: locks/003.php
64+
65+
Restoring a Lock by Owner
66+
=========================
67+
68+
Each acquired lock has an owner token. You may pass this token to another
69+
process and restore the lock there, for example to release a lock from a queued
70+
worker that continues work started by the current request.
71+
72+
.. literalinclude:: locks/004.php
73+
74+
************************
75+
Locks and Cache Handlers
76+
************************
77+
78+
The default File cache handler supports locks, so locks work without additional
79+
configuration in a standard application.
80+
81+
If the configured cache handler does not support locks, creating a lock throws a
82+
``CodeIgniter\Lock\Exceptions\LockException``.
83+
84+
Custom cache handlers can support locks by implementing
85+
``CodeIgniter\Lock\LockStoreInterface``. This keeps lock support opt-in and does
86+
not require all cache handlers to implement lock operations.
87+
88+
***************
89+
Class Reference
90+
***************
91+
92+
.. php:namespace:: CodeIgniter\Lock
93+
94+
.. php:class:: LockManager
95+
96+
.. php:method:: create(string $name[, int $ttl = 300[, ?string $owner = null]])
97+
98+
:param string $name: The logical lock name.
99+
:param int $ttl: Number of seconds before the lock expires.
100+
:param string|null $owner: Optional owner token.
101+
:returns: A lock instance.
102+
:rtype: LockInterface
103+
104+
Creates a lock for the given logical name.
105+
106+
.. php:method:: restore(string $name, string $owner[, int $ttl = 300])
107+
108+
:param string $name: The logical lock name.
109+
:param string $owner: The owner token.
110+
:param int $ttl: Number of seconds before the lock expires.
111+
:returns: A lock instance.
112+
:rtype: LockInterface
113+
114+
Restores a lock instance for an existing owner token.
115+
116+
.. php:interface:: LockInterface
117+
118+
.. php:method:: acquire()
119+
120+
:returns: ``true`` if the lock was acquired, ``false`` otherwise.
121+
:rtype: bool
122+
123+
.. php:method:: block(int $seconds)
124+
125+
:param int $seconds: Maximum number of seconds to wait.
126+
:returns: ``true`` if the lock was acquired, ``false`` otherwise.
127+
:rtype: bool
128+
129+
.. php:method:: run(Closure $callback[, int $waitSeconds = 0])
130+
131+
:param Closure $callback: The callback to run while the lock is held.
132+
:param int $waitSeconds: Maximum number of seconds to wait.
133+
:returns: The callback result, or ``null`` if the lock was not acquired.
134+
:rtype: mixed
135+
136+
.. php:method:: release()
137+
138+
:returns: ``true`` if the lock was released by its owner.
139+
:rtype: bool
140+
141+
.. php:method:: forceRelease()
142+
143+
:returns: ``true`` if the lock was force released.
144+
:rtype: bool
145+
146+
Releases the lock without checking the owner token.
147+
148+
.. php:method:: refresh([?int $ttl = null])
149+
150+
:param int|null $ttl: Number of seconds before the lock expires.
151+
:returns: ``true`` if the owned lock was refreshed.
152+
:rtype: bool
153+
154+
.. php:method:: isAcquired()
155+
156+
:returns: ``true`` if this lock instance still owns the lock.
157+
:rtype: bool
158+
159+
.. php:method:: owner()
160+
161+
:returns: The owner token.
162+
:rtype: string
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<?php
2+
3+
$lock = service('locks')->create('reports.daily-export', 300);
4+
5+
if (! $lock->acquire()) {
6+
return;
7+
}
8+
9+
try {
10+
// Run the work that must not overlap.
11+
} finally {
12+
$lock->release();
13+
}
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
<?php
2+
3+
$result = service('locks')
4+
->create('reports.daily-export', 300)
5+
->run(static fn () => build_daily_report());
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
$lock = service('locks')->create('imports.customer-feed', 300);
4+
5+
if ($lock->block(10)) {
6+
try {
7+
import_customer_feed();
8+
} finally {
9+
$lock->release();
10+
}
11+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<?php
2+
3+
$lock = service('locks')->create('exports.monthly', 300);
4+
5+
if ($lock->acquire()) {
6+
queue_export_job($lock->owner());
7+
}
8+
9+
// Later, in another process:
10+
$restored = service('locks')->restore('exports.monthly', $owner);
11+
$restored->release();

0 commit comments

Comments
 (0)