{"record":{"id":"9c9dbb5df2da93c3","repo":"phacility/phabricator","slug":"query-timed-out-after-s-second-s","errorCode":null,"errorMessage":"Query timed out after %s second(s)!","messagePattern":"Query timed out after (.+?) second\\(s\\)!","errorType":"exception","errorClass":"AphrontQueryTimeoutQueryException","httpStatus":null,"severity":"error","filePath":"src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php","lineNumber":156,"sourceCode":"    // If we have a query time limit, run this query synchronously but use\n    // the async API. This allows us to kill queries which take too long\n    // without requiring any configuration on the server side.\n    if ($time_limit && $this->supportsAsyncQueries()) {\n      $conn->query($raw_query, MYSQLI_ASYNC);\n\n      $read = array($conn);\n      $error = array($conn);\n      $reject = array($conn);\n\n      $result = mysqli::poll($read, $error, $reject, $time_limit);\n\n      if ($result === false) {\n        $this->closeConnection();\n        throw new Exception(\n          pht('Failed to poll mysqli connection!'));\n      } else if ($result === 0) {\n        $this->closeConnection();\n        throw new AphrontQueryTimeoutQueryException(\n          pht(\n            'Query timed out after %s second(s)!',\n            new PhutilNumber($time_limit)));\n      }\n\n      return @$conn->reap_async_query();\n    }\n\n    $trap = new PhutilErrorTrap();\n\n    $result = @$conn->query($raw_query);\n\n    $err = $trap->getErrorsAsString();\n    $trap->destroy();\n\n    // See T13238 and PHI1014. Sometimes, the call to \"$conn->query()\" may fail\n    // without setting an error code on the connection. One way to reproduce\n    // this is to use \"LOAD DATA LOCAL INFILE\" with \"mysqli.allow_local_infile\"","sourceCodeStart":138,"sourceCodeEnd":174,"githubUrl":"https://github.com/phacility/phabricator/blob/5720a38cfe95b00ca4be5016dd0d2f3195f4fa04/src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php#L138-L174","documentation":"With a per-query time limit configured, the query ran asynchronously and mysqli::poll() returned 0: no connection became ready within the limit, so the query did not finish in time. The driver closes the connection (the query may keep running on the server) and raises AphrontQueryTimeoutQueryException, which extends AphrontRecoverableQueryException — the library explicitly classifies timeouts as retryable.","triggerScenarios":"Any query slower than the configured query time limit: unindexed scans over large tables, bulk UPDATE/DELETE, imports/reindexing runs executed through a connection with a low per-query limit; locks held by another transaction stalling progress until the limit expires.","commonSituations":"Batch tools inheriting web-request query limits; a table growing until a routine query crosses the threshold; lock contention with a long transaction; running migrations in a web context instead of CLI.","solutions":["Catch AphrontQueryTimeoutQueryException and retry with backoff — it is typed as recoverable, but first make the operation re-runnable/idempotent","Make the query faster: add the missing index, or chunk the work (process IDs in batches) so each statement is quick","Raise or remove the per-query time limit for long-running batch/CLI contexts, keeping tight limits only for web requests","If lock waits are the cause, find and shorten the competing transaction rather than raising the limit"],"exampleFix":"// before: one giant statement that trips the query time limit\nqueryfx($conn_w, 'UPDATE t SET flag = 1');\n\n// after: chunked, retryable work\nforeach (array_chunk($ids, 512) as $chunk) {\n  queryfx(\n    $conn_w,\n    'UPDATE t SET flag = 1 WHERE id IN (%Ld)',\n    $chunk);\n}","handlingStrategy":"retry","validationCode":"// Prevent: keep each statement comfortably under the per-query limit by\n// chunking keys before issuing large UPDATE/DELETE/SELECT work:\nforeach (array_chunk($ids, 512) as $chunk) {\n  queryfx($conn_w, 'UPDATE t SET flag = 1 WHERE id IN (%Ld)', $chunk);\n}","typeGuard":null,"tryCatchPattern":"// AphrontQueryTimeoutQueryException extends AphrontRecoverableQueryException:\ntry {\n  queryfx($conn_w, '%s', $sql);\n} catch (AphrontQueryTimeoutQueryException $ex) {\n  // Recoverable by contract: back off and retry, ideally with smaller scope.\n  sleep(1);\n  // ...retry with chunked/optimized version of the work\n}","preventionTips":["Index the columns your batch queries scan so routine statements stay far under the limit","Chunk bulk operations by ID instead of issuing one giant statement","Reserve low per-query limits for web requests; run long batch work from CLI with higher/no limits","Investigate lock waits (competing long transactions) rather than only raising the limit"],"tags":["mysql","query-timeout","async-query","slow-query","performance"],"backgroundTag":"query-timeout","analyzedSha":"5720a38cfe95b00ca4be5016dd0d2f3195f4fa04","analyzedAt":"2026-08-21T05:07:25.672Z","schemaVersion":2},"datasetVersion":"2026-08-21T11:28:35.574Z"}