runs = $runs; $this->tasks = $tasks; $this->findings = $findings; $this->checkers = $checkers; $this->registry = $registry; $this->coordinator = $coordinator; $this->transaction = $transaction; } /** * @return array{state:string,run_status:string,check_id:string|null} */ public function run_one( string $run_uuid ): array { $run = $this->runs->get_by_uuid( $run_uuid ); if ( null === $run ) { throw new StorageException( 'run_not_found' ); } if ( RunStatus::RUNNING !== $run->status() ) { return array( 'state' => 'idle', 'run_status' => $run->status(), 'check_id' => null, ); } $lease = $this->tasks->acquire_next( $run->id(), 60 ); if ( null === $lease ) { $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'idle', 'run_status' => $settled->status(), 'check_id' => null, ); } $checker = $this->checkers->get( $lease->check_id() ); if ( null === $checker ) { $this->tasks->fail( $lease, 'checker_unavailable' ); $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'failed', 'run_status' => $settled->status(), 'check_id' => $lease->check_id(), ); } try { $budget = $this->registry->local_budget_for( $lease->check_id() ); } catch ( \InvalidArgumentException $exception ) { $this->tasks->fail( $lease, 'checker_capability_denied' ); $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'failed', 'run_status' => $settled->status(), 'check_id' => $lease->check_id(), ); } $started = microtime( true ); $memory_start = memory_get_usage( true ); try { $timestamp = mysql_to_rfc3339( current_time( 'mysql', true ) ); $finding = $checker->execute( $timestamp ); $encoded = wp_json_encode( $finding, JSON_UNESCAPED_SLASHES ); if ( false === $encoded ) { throw new StorageException( 'finding_encode_failed' ); } $elapsed = (int) ceil( ( microtime( true ) - $started ) * 1000 ); $memory_used = max( 0, memory_get_usage( true ) - $memory_start ); $bytes_used = max( strlen( $encoded ), $memory_used ); $item_count = isset( $finding['coverage']['observed'] ) ? max( 1, (int) $finding['coverage']['observed'] ) : 1; $budget = $budget->consume( 'milliseconds', $elapsed ); $budget = $budget->consume( 'items', $item_count ); $budget->consume( 'bytes', $bytes_used ); $usage = array( 'milliseconds' => $elapsed, 'items' => $item_count, 'requests' => 0, 'bytes' => $bytes_used, ); $this->transaction->begin(); $this->runs->consume_budget( $run->id(), $usage ); $this->findings->save( $run->id(), $finding ); if ( ! $this->tasks->complete( $lease, array( 'finding_count' => 1 ) ) ) { throw new StorageException( 'lease_expired_before_completion' ); } $this->transaction->commit(); } catch ( BudgetExceeded $exception ) { $this->transaction->rollback(); $this->tasks->fail( $lease, 'checker_budget_exceeded' ); $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'failed', 'run_status' => $settled->status(), 'check_id' => $lease->check_id(), ); } catch ( Throwable $exception ) { $this->transaction->rollback(); $this->tasks->fail( $lease, 'checker_failed' ); $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'failed', 'run_status' => $settled->status(), 'check_id' => $lease->check_id(), ); } $settled = $this->coordinator->settle( $run_uuid ); return array( 'state' => 'completed', 'run_status' => $settled->status(), 'check_id' => $lease->check_id(), ); } }