diff --git a/src/Runtime/class-wp-agent-option-run-control-store.php b/src/Runtime/class-wp-agent-option-run-control-store.php index d108d64..0ac7643 100644 --- a/src/Runtime/class-wp-agent-option-run-control-store.php +++ b/src/Runtime/class-wp-agent-option-run-control-store.php @@ -281,7 +281,11 @@ private function acquire_lock( \wpdb $db, string $table, string $lock_key ): str return $lock_value; } if ( false === $created ) { - throw new WP_Agent_Run_Control_Store_Exception( 'Atomic run-control lock creation failed.' ); + if ( ! $this->is_transient_lock_contention( $db ) ) { + throw new WP_Agent_Run_Control_Store_Exception( 'Atomic run-control lock creation failed.' ); + } + usleep( 50000 ); + continue; } $current = $this->get_var( $db, $db->prepare( 'SELECT option_value FROM %i WHERE option_name = %s LIMIT 1', $table, $lock_key ) ); @@ -290,7 +294,7 @@ private function acquire_lock( \wpdb $db, string $table, string $lock_key ): str if ( 1 === $replaced ) { return $lock_value; } - if ( false === $replaced ) { + if ( false === $replaced && ! $this->is_transient_lock_contention( $db ) ) { throw new WP_Agent_Run_Control_Store_Exception( 'Atomic run-control lock takeover failed.' ); } } @@ -301,6 +305,19 @@ private function acquire_lock( \wpdb $db, string $table, string $lock_key ): str throw new WP_Agent_Run_Control_Store_Exception( 'Atomic run-control lock acquisition timed out.' ); } + /** + * Two connections racing an `INSERT IGNORE` (or a conditional takeover + * `UPDATE`) against the same unique lock row can make InnoDB report a + * deadlock or a lock-wait timeout instead of the clean "0 rows + * affected" outcome this loop already retries. Both are transient + * admission-race artifacts of the exact contention this method exists + * to serialize, not a genuine query or storage failure, so they get + * the same retry treatment as a lost duplicate-key race. + */ + private function is_transient_lock_contention( \wpdb $db ): bool { + return str_contains( $db->last_error, 'Deadlock found' ) || str_contains( $db->last_error, 'Lock wait timeout' ); + } + private function release_lock( \wpdb $db, string $table, string $lock_key, string $lock_value ): void { $released = $this->query( $db, $db->prepare( 'DELETE FROM %i WHERE option_name = %s AND option_value = %s', $table, $lock_key, $lock_value ) ); if ( false === $released ) {