query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value < %d", $expiry_name, $now ) ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND NOT EXISTS ( SELECT 1 FROM (SELECT 1 FROM {$wpdb->options} WHERE option_name = %s) AS t )", $option_name, $expiry_name ) ); // Atomic INSERT IGNORE — only succeeds if the row does not yet exist. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching $inserted = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'no'), (%s, %s, 'no')", $expiry_name, $now + $ttl_seconds, $option_name, '1' ) ); // INSERT IGNORE returns 2 when both rows are inserted (lock acquired). return 2 === (int) $inserted; } /** * Release a previously acquired lock. * * Safe to call even if the lock is not held. * * @param string $lock_key Transient key used as the lock name. * @return void */ public static function release_lock( string $lock_key ): void { if ( wp_using_ext_object_cache() ) { wp_cache_delete( $lock_key, 'transient' ); } else { delete_transient( $lock_key ); } } /** * Acquire the lock, execute the callback, then always release the lock. * * If the lock cannot be acquired (another instance is running), the * callback is skipped and false is returned. * * @param string $lock_key Transient key used as the lock name. * @param int $ttl_seconds Lock lifetime in seconds. * @param callable $callback Work to execute while holding the lock. * @param string $log_context Log context for error messages (e.g. '[CRON] pull_all'). * @return bool True if the callback was executed, false if the lock was busy. */ public static function run_with_lock( string $lock_key, int $ttl_seconds, callable $callback, string $log_context = '[PollRunner]' ): bool { if ( ! self::acquire_lock( $lock_key, $ttl_seconds ) ) { return false; } try { $callback(); } catch ( \Throwable $e ) { Logger::write_log( 'info', $log_context . ' error: ' . $e->getMessage() ); } finally { self::release_lock( $lock_key ); } return true; } }