option = 'wpai_' . $name . '_lock'; $this->ttl = $ttl; } /** * Returns the option name holding the lock. * * @since 1.5.0 * * @return string The option name. */ public function get_option_name(): string { return $this->option; } /** * Returns the seconds after which an unrefreshed lock counts as abandoned. * * @since 1.5.0 * * @return int The TTL, in seconds. */ public function get_ttl(): int { return $this->ttl; } /** * Tries to acquire the lock. * * @since 1.5.0 * * @param int|null $now Optional. Unix time. Default now. * @return bool True when acquired. */ public function acquire( ?int $now = null ): bool { global $wpdb; $now = $now ?? time(); $value = $now . ':' . wp_generate_password( 12, false ); $inserted = $wpdb->query( $wpdb->prepare( "INSERT IGNORE INTO {$wpdb->options} (option_name, option_value, autoload) VALUES (%s, %s, 'off')", $this->option, $value ) ); if ( 1 === (int) $inserted ) { return $this->held( $value ); } $current = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", $this->option ) ); // Released between the two queries: the next run will get it. if ( ! is_string( $current ) ) { return false; } if ( $now - (int) $current < $this->ttl ) { return false; } $taken = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s", $value, $this->option, $current ) ); return 1 === (int) $taken ? $this->held( $value ) : false; } /** * Refreshes the lock's timestamp so a long run does not look abandoned. * * @since 1.5.0 * * @param int|null $now Optional. Unix time. Default now. * @return bool True when this instance still holds the lock. */ public function refresh( ?int $now = null ): bool { global $wpdb; if ( '' === $this->value ) { return false; } $token = substr( $this->value, (int) strpos( $this->value, ':' ) + 1 ); $value = ( $now ?? time() ) . ':' . $token; $updated = $wpdb->query( $wpdb->prepare( "UPDATE {$wpdb->options} SET option_value = %s WHERE option_name = %s AND option_value = %s", $value, $this->option, $this->value ) ); if ( 1 === (int) $updated ) { return $this->held( $value ); } $current = $wpdb->get_var( $wpdb->prepare( "SELECT option_value FROM {$wpdb->options} WHERE option_name = %s", $this->option ) ); if ( $value === $current || $this->value === $current ) { return $this->held( $current ); } $this->value = ''; return false; } /** * Releases the lock if this instance holds it. * * @since 1.5.0 */ public function release(): void { global $wpdb; if ( '' === $this->value ) { return; } $wpdb->query( $wpdb->prepare( "DELETE FROM {$wpdb->options} WHERE option_name = %s AND option_value = %s", $this->option, $this->value ) ); $this->value = ''; wp_cache_delete( $this->option, 'options' ); } /** * Checks whether this instance holds the lock. * * @since 1.5.0 * * @return bool True when held. */ public function is_held(): bool { return '' !== $this->value; } /** * Records the held value and drops any cached copy of the option. * * @since 1.5.0 * * @param string $value The value written. * @return bool Always true. */ private function held( string $value ): bool { $this->value = $value; wp_cache_delete( $this->option, 'options' ); return true; } }