wpdb->dbhost, $this->wpdb->dbname ) ); return 'shiftora:' . $identity . ':' . $scope; } /** * Claim the advisory lock for a scope. Waits up to $timeout seconds; a * false return means the lock is held elsewhere, not that anything broke. */ public function acquire( string $scope, int $timeout = 0 ): bool { $wpdb = $this->wpdb; $name = $this->lock_name( $scope ); // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- a named lock, not cacheable $got = $wpdb->get_var( $wpdb->prepare( 'SELECT GET_LOCK(%s, %d)', $name, $timeout ), 0, 0 ); if ( null === $got ) { throw new RuntimeException( sprintf( 'GET_LOCK(%s) failed: %s', $name, $wpdb->last_error ?: 'unknown error' ) ); } return 1 === (int) $got; } /** * Hand the lock back. False means this session did not own it, which is * itself a signal to callers that the claim was lost mid-flight. */ public function release( string $scope ): bool { $wpdb = $this->wpdb; $name = $this->lock_name( $scope ); // query() returns the row count (1) for any SELECT, so the release // outcome has to come from get_var, which gives RELEASE_LOCK's 1/0. // phpcs:ignore WordPress.DB.DirectDatabaseQuery -- a named lock, not cacheable $released = $wpdb->get_var( $wpdb->prepare( 'SELECT RELEASE_LOCK(%s)', $name ), 0, 0 ); if ( null === $released ) { throw new RuntimeException( sprintf( 'RELEASE_LOCK(%s) failed: %s', $name, $wpdb->last_error ?: 'unknown error' ) ); } return 1 === (int) $released; } // phpcs:enable WordPress.Security.EscapeOutput.ExceptionNotEscaped }