Locks this request holds as options. */ private static array $optionLocks = []; public static function acquire(string $name, int $timeout = 0): bool { if (!self::namedLocks()) { return self::acquireOption($name, $timeout); } global $wpdb; $got = $wpdb->get_var($wpdb->prepare('SELECT GET_LOCK(%s, %d)', self::key($name), $timeout)); // phpcs:ignore WordPress.DB.DirectDatabaseQuery if ($got === '1' || $got === 1) { return true; } if ($got === '0' || $got === 0) { return false; // held by another connection } // NULL or anything unexpected: this database does not do named locks after all. return self::acquireOption($name, $timeout); } public static function release(string $name): void { if (isset(self::$optionLocks[$name])) { unset(self::$optionLocks[$name]); delete_option(self::optionKey($name)); return; } global $wpdb; $wpdb->query($wpdb->prepare('SELECT RELEASE_LOCK(%s)', self::key($name))); // phpcs:ignore WordPress.DB.DirectDatabaseQuery } /** * Whether the database has real named locks (MySQL, MariaDB). */ public static function namedLocks(): bool { global $wpdb; $sqlite = (defined('DB_ENGINE') && DB_ENGINE === 'sqlite') || (is_object($wpdb) && stripos(get_class($wpdb), 'sqlite') !== false); return (bool) apply_filters('aipcf_named_locks', !$sqlite); } private static function acquireOption(string $name, int $timeout): bool { $key = self::optionKey($name); $deadline = time() + max(0, $timeout); do { // add_option() inserts only when the row does not exist yet (unique option_name). if (add_option($key, (string) time(), '', false)) { self::$optionLocks[$name] = true; return true; } wp_cache_delete($key, 'options'); $since = (int) get_option($key, 0); if ($since > 0 && $since < time() - self::STALE) { // Left by a process that died: take it over. delete_option($key); continue; } if (time() >= $deadline) { return false; } usleep(250000); } while (true); } private static function optionKey(string $name): string { return 'aipcf_lock_' . $name; } private static function key(string $name): string { global $wpdb; // Named locks are server-wide; scope them to this site's tables. return substr('aipcf_' . $wpdb->prefix . $name, 0, 64); } }