fix(locks): make cron admission atomic - #522
Conversation
Persist concurrency state in the options table and bind workers to generation-aware leases so stale cleanup cannot release a replacement lock. Refs: PLTFRM-2759 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
There was a problem hiding this comment.
Pull request overview
This PR refactors Cron Control’s concurrency locking to use an atomic, database-backed lock state (stored in the options table) and introduces generation-aware release logic to prevent stale workers from accidentally releasing newer/recovered locks.
Changes:
- Replaced object-cache-based lock counters with an atomic
INSERT ... ON DUPLICATE KEY UPDATElock state stored inwp_options. - Added generation tracking per admitted lock acquisition and made
free_lock()generation-aware (including deleting inactive lock rows). - Added unit tests covering admission limits, stale recovery, and old-generation cleanup behavior.
Reviewed changes
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| includes/class-lock.php | Implements atomic options-table lock state + generation-aware acquisition/release and parsing helpers. |
| includes/class-events.php | Routes event action lock release through Lock::free_lock() consistently. |
| tests/unit-tests/test-lock.php | Adds unit tests validating concurrency limits and generation-safe cleanup semantics. |
Suppressed comments (2)
includes/class-lock.php:152
random_int()can throw, and this code path currently doesn’t handle that. A failure here would prevent priming locks and may cascade into cron admission failures. Consider the same try/catch +wp_rand()fallback used incheck_lock().
$wpdb->query(
$wpdb->prepare(
"INSERT IGNORE INTO `$wpdb->options` (`option_name`, `option_value`, `autoload`) VALUES (%s, %s, 'no')",
self::get_key( $lock ),
self::build_lock_state( 0, time(), random_int( 1, \PHP_INT_MAX ) )
)
includes/class-lock.php:192
random_int()can throw and isn’t caught here, which could makereset_lock()fatally error (potentially leaving locks stuck). Consider catching\Throwableand falling back towp_rand()for generation selection.
$now = time();
$generation = random_int( 1, \PHP_INT_MAX );
$result = $wpdb->query(
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Use WordPress's non-throwing random wrapper for lock-row generation so an unavailable CSPRNG cannot interrupt cron admission, lock priming, or lock reset. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Resolve the namespaced wp_rand call so the test bootstrap can load the lock class. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Use the WordPress random wrapper when available and a bounded native fallback while the lock class initializes before WordPress functions load. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 2 comments.
Suppressed comments (1)
Previously missed (1) — in code that hasn't changed since the last review.
tests/unit-tests/test-lock.php:37
- Despite the test name, these calls are sequential, so the previous read-then-increment implementation would also pass this test. The PR's core regression is simultaneous admissions observing the same prior count; add a test using separate database connections/processes synchronized to contend on the same lock and assert that only one acquisition succeeds.
$this->assertTrue( Lock::check_lock( $lock, 1 ) );
$this->assertFalse( Lock::check_lock( $lock, 1 ) );
Keep an acquired generation after a failed database release, and stop unnecessary lock priming writes. Refs: PLTFRM-2759 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Summary
Testing
git diff --check/tmp/wordpress-tests-liband its MariaDB-backed WordPress test database are unavailableRefs: https://linear.app/a8c/issue/PLTFRM-2759