query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'off')", $name, $token ) ); if ( 1 === (int) $inserted ) { self::forget( $name ); return $token; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Reading the live lock row, not a cacheable value. $held = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", $name ) ); if ( ! is_string( $held ) || (int) $held >= $now - $lifetime ) { return ''; } // Abandoned. Taken over only if nobody else took it over first. // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Compare-and-swap on the lock row. $taken = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s", $token, $name, $held ) ); self::forget( $name ); return 1 === (int) $taken ? $token : ''; } /** * Releases a lock this caller holds. * * @param string $name Option name of the lock. * @param string $token Token returned by {@see self::acquire()}. */ public static function release( string $name, string $token ): void { global $wpdb; if ( '' === $token ) { return; } // phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery, WordPress.DB.DirectDatabaseQuery.NoCaching -- Only the holder's own row is removed. $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value = %s", $name, $token ) ); self::forget( $name ); } /** * Drops what the options cache believes about the lock row. * * @param string $name Option name of the lock. */ private static function forget( string $name ): void { wp_cache_delete( $name, 'options' ); wp_cache_delete( 'notoptions', 'options' ); } }