From bbeecfecdbf422cfb05321b28691a3e82fb0812b Mon Sep 17 00:00:00 2001 From: mscherer Date: Wed, 8 Apr 2026 19:39:11 +0200 Subject: [PATCH] Add locking documentation --- .vitepress/toc_en.json | 1 + docs/en/contents.md | 1 + docs/en/core-libraries/locking.md | 186 ++++++++++++++++++++++++++++++ docs/en/epub-contents.md | 1 + docs/en/intro.md | 2 + docs/en/pdf-contents.md | 1 + docs/en/topics.md | 1 + 7 files changed, 193 insertions(+) create mode 100644 docs/en/core-libraries/locking.md diff --git a/.vitepress/toc_en.json b/.vitepress/toc_en.json index 0b2bad02a6..8e1b85f174 100644 --- a/.vitepress/toc_en.json +++ b/.vitepress/toc_en.json @@ -185,6 +185,7 @@ "items": [ { "text": "Events", "link": "/core-libraries/events" }, { "text": "Caching", "link": "/core-libraries/caching" }, + { "text": "Locking", "link": "/core-libraries/locking" }, { "text": "Email", "link": "/core-libraries/email" }, { "text": "Logging", "link": "/core-libraries/logging" }, { "text": "Security", "link": "/core-libraries/security" }, diff --git a/docs/en/contents.md b/docs/en/contents.md index 38dfa124fa..81d7091c36 100644 --- a/docs/en/contents.md +++ b/docs/en/contents.md @@ -32,6 +32,7 @@ - [Error & Exception Handling](development/errors) - [Events System](core-libraries/events) - [Internationalization & Localization](core-libraries/internationalization-and-localization) +- [Locking](core-libraries/locking) - [Logging](core-libraries/logging) - [Modelless Forms](core-libraries/form) - [Pagination](controllers/pagination) diff --git a/docs/en/core-libraries/locking.md b/docs/en/core-libraries/locking.md new file mode 100644 index 0000000000..fa4ddd0258 --- /dev/null +++ b/docs/en/core-libraries/locking.md @@ -0,0 +1,186 @@ +# Locking + +`class` Cake\Lock\**Lock** + +Locking helps you coordinate access to shared resources across concurrent +requests, CLI commands, queue workers, or background jobs. Use locks when you +need to ensure that only one process performs a critical section at a time. + +CakePHP provides the `Cake\Lock\Lock` facade together with pluggable lock +engines for Redis, Memcached, local files, and no-op/testing usage. + +## Configuring Lock Engines + +Lock engine configurations are typically defined in **config/app.php**: + +```php +'Lock' => [ + 'default' => [ + 'className' => 'Redis', + 'host' => '127.0.0.1', + 'port' => 6379, + 'prefix' => 'myapp_lock_', + 'ttl' => 60, + ], +], +``` + +You can also configure lock engines at runtime: + +```php +use Cake\Lock\Lock; + +Lock::setConfig('orders', [ + 'className' => 'Redis', + 'host' => '127.0.0.1', + 'port' => 6379, + 'prefix' => 'orders_', + 'ttl' => 30, +]); +``` + +Lock configurations use the same `className` conventions as other registry-based +CakePHP services: + +```php +Lock::setConfig('default', ['className' => 'Redis']); +Lock::setConfig('default', ['className' => 'Cake\Lock\Engine\RedisLockEngine']); +``` + +If an engine cannot be initialized, CakePHP falls back to the noop +`NullLockEngine` and emits a warning. + +## Acquiring and Releasing Locks + +Use `Lock::acquire()` to attempt a non-blocking lock: + +```php +use Cake\Lock\Lock; + +$lock = Lock::acquire('invoice-' . $invoiceId, ttl: 60); +if ($lock === null) { + return; +} + +try { + $this->Invoices->send($invoiceId); +} finally { + $lock->release(); +} +``` + +The returned `AcquiredLock` object represents the held lock. You can: + +- call `release()` to release it explicitly +- call `refresh()` to extend the TTL for long-running work +- rely on its best-effort destructor cleanup if the handle is dropped + +Explicit release is still recommended for predictable behavior. + +## Preferred Usage with `synchronized()` + +In many cases, `Lock::synchronized()` is the simplest and safest API because it +guarantees prompt release: + +```php +$result = Lock::synchronized( + 'reports-daily', + function () { + return $this->Reports->buildDaily(); + }, + ttl: 120, + timeout: 10, +); +``` + +If the lock cannot be acquired before the timeout expires, `synchronized()` +returns `null`. + +## Blocking Acquisition + +Use `Lock::acquireBlocking()` when you want to wait for a lock to become +available: + +```php +$lock = Lock::acquireBlocking( + 'payment-' . $paymentId, + ttl: 60, + timeout: 10, + retryInterval: 100, +); + +if ($lock === null) { + return; +} + +try { + $this->Payments->capture($paymentId); +} finally { + $lock->release(); +} +``` + +The `retryInterval` value is expressed in milliseconds. + +## Inspecting and Managing Locks + +CakePHP provides additional helper methods for lock lifecycle management: + +```php +if (Lock::isLocked('imports-products')) { + return; +} + +$lock = Lock::acquire('imports-products'); + +if ($lock) { + $lock->refresh(120); + $lock->release(); +} + +Lock::forceRelease('imports-products'); +``` + +- `isLocked()` performs a point-in-time check using the underlying engine +- `refresh()` extends the lock TTL if the current owner still holds the lock +- `forceRelease()` bypasses ownership checks and should only be used for + administrative recovery flows + +## Available Engines + +CakePHP ships with the following lock engines: + +- `Redis` Recommended for distributed systems and multi-node deployments +- `Memcached` Suitable when Memcached is already part of your infrastructure +- `File` Useful for single-server deployments using local filesystem locks +- `Null` Useful for tests, local development, and intentional no-op behavior + +### Shared Engine Options + +All lock engines support these common options: + +- `prefix` Prefix added to lock keys +- `ttl` Default lock time-to-live in seconds + +### RedisLockEngine Options + +- `host` Redis server host +- `port` Redis server port +- `password` Redis password +- `database` Redis database index +- `timeout` Connection timeout +- `persistent` Whether to use persistent connections + +### MemcachedLockEngine Options + +- `servers` Array of Memcached servers +- `persistent` Persistent connection identifier + +### FileLockEngine Options + +- `path` Directory used to store lock files + +The `FileLockEngine` is local to a single host. Unlike Redis or Memcached, it +does not provide true TTL-based expiration in the backend. Locks are normally +released explicitly, when the lock handle is destroyed, or when the process +terminates. diff --git a/docs/en/epub-contents.md b/docs/en/epub-contents.md index 20a81f7537..ebfd9e2add 100644 --- a/docs/en/epub-contents.md +++ b/docs/en/epub-contents.md @@ -13,6 +13,7 @@ - [Views](views) - [Database Access & ORM](orm) - [Caching](core-libraries/caching) +- [Locking](core-libraries/locking) - [Bake Console](bake) - [Console Commands](console-commands) - [Debugging](development/debugging) diff --git a/docs/en/intro.md b/docs/en/intro.md index 869265edb9..d23893b461 100644 --- a/docs/en/intro.md +++ b/docs/en/intro.md @@ -155,6 +155,8 @@ features in CakePHP are: - A [caching](core-libraries/caching) framework that integrates with Memcached, Redis and other backends. +- A [locking](core-libraries/locking) API for coordinating critical sections + across concurrent requests, workers, and commands. - Powerful [code generation tools](bake/usage) so you can start immediately. - [Integrated testing framework](development/testing) so you can ensure your code works perfectly. diff --git a/docs/en/pdf-contents.md b/docs/en/pdf-contents.md index 20a81f7537..ebfd9e2add 100644 --- a/docs/en/pdf-contents.md +++ b/docs/en/pdf-contents.md @@ -13,6 +13,7 @@ - [Views](views) - [Database Access & ORM](orm) - [Caching](core-libraries/caching) +- [Locking](core-libraries/locking) - [Bake Console](bake) - [Console Commands](console-commands) - [Debugging](development/debugging) diff --git a/docs/en/topics.md b/docs/en/topics.md index 8c2457859d..7ef20142e9 100644 --- a/docs/en/topics.md +++ b/docs/en/topics.md @@ -24,6 +24,7 @@ Introduction to all the key parts of CakePHP: - [Entities](orm/entities) - [Error & Exception Handling](development/errors) - [Caching](core-libraries/caching) +- [Locking](core-libraries/locking) - [Logging](core-libraries/logging) - [Modelless Forms](core-libraries/form) - [Sessions](development/sessions)