base_prefix . '|' . DB_NAME ), 0, 24 ); } /** * Named locks only when $wpdb is plain wpdb on MySQL: HyperDB/LudicrousDB may send the * SELECT to a replica or another connection than RELEASE_LOCK. * * @return bool */ private static function use_named() { global $wpdb; return 'mysql' === Environment::db_engine() && 'wpdb' === get_class( $wpdb ) && apply_filters( 'dbmg_use_named_lock', true ); } /** * @param int $ttl Seconds before a crashed holder's fallback lock can be taken over. * @return bool */ public static function acquire( $ttl = 60 ) { global $wpdb; if ( '' !== self::$held ) { return true; } if ( self::use_named() ) { $suppress = $wpdb->suppress_errors( true ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $got = $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, 0)', self::name() ) ); $wpdb->suppress_errors( $suppress ); if ( '1' === (string) $got ) { self::$held = 'named'; return true; } if ( '0' === (string) $got ) { return false; } // NULL/error → fall through to the option lock. } $value = wp_generate_password( 12, false ) . '|' . ( time() + $ttl ); if ( Options::add( self::OPTION, $value ) ) { self::$held = 'option'; self::$value = $value; return true; } $current = self::raw(); if ( null !== $current && self::expiry( $current ) < time() && Options::swap( self::OPTION, $current, $value ) ) { self::$held = 'option'; self::$value = $value; return true; } return false; } /** * Release whatever we hold. */ public static function release() { global $wpdb; if ( 'named' === self::$held ) { // phpcs:ignore WordPress.DB.DirectDatabaseQuery $wpdb->query( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', self::name() ) ); } elseif ( 'option' === self::$held ) { Options::delete( self::OPTION, self::$value ); } self::$held = ''; self::$value = ''; } /** * Is someone (else) running a tick right now? * * @return bool */ public static function is_busy() { global $wpdb; if ( '' !== self::$held ) { return true; } if ( self::use_named() ) { $suppress = $wpdb->suppress_errors( true ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery $free = $wpdb->get_var( $wpdb->prepare( 'SELECT IS_FREE_LOCK(%s)', self::name() ) ); $wpdb->suppress_errors( $suppress ); if ( '0' === (string) $free ) { return true; } } $current = self::raw(); return null !== $current && self::expiry( $current ) >= time(); } /** * Clear a stale fallback lock (Resume, §8.5). Named locks free themselves when PHP dies. */ public static function clear_stale() { $current = self::raw(); if ( null !== $current && self::expiry( $current ) < time() ) { Options::delete( self::OPTION, $current ); } } /** * Unconditional removal (deactivation / delete-all). */ public static function force_clear() { Options::delete( self::OPTION ); } /** * @return string|null */ private static function raw() { $raw = Options::get( self::OPTION ); return is_string( $raw ) ? $raw : null; } /** * @param string $raw token|expiry. * @return int */ private static function expiry( $raw ) { $bits = explode( '|', $raw ); return isset( $bits[1] ) ? (int) $bits[1] : 0; } }