phacility/phabricator · error · AphrontQueryTimeoutQueryException

Query timed out after %s second(s)!

Error message

Query timed out after %s second(s)!

What it means

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.

Source

Thrown at src/infrastructure/storage/connection/mysql/AphrontMySQLiDatabaseConnection.php:156

    // If we have a query time limit, run this query synchronously but use
    // the async API. This allows us to kill queries which take too long
    // without requiring any configuration on the server side.
    if ($time_limit && $this->supportsAsyncQueries()) {
      $conn->query($raw_query, MYSQLI_ASYNC);

      $read = array($conn);
      $error = array($conn);
      $reject = array($conn);

      $result = mysqli::poll($read, $error, $reject, $time_limit);

      if ($result === false) {
        $this->closeConnection();
        throw new Exception(
          pht('Failed to poll mysqli connection!'));
      } else if ($result === 0) {
        $this->closeConnection();
        throw new AphrontQueryTimeoutQueryException(
          pht(
            'Query timed out after %s second(s)!',
            new PhutilNumber($time_limit)));
      }

      return @$conn->reap_async_query();
    }

    $trap = new PhutilErrorTrap();

    $result = @$conn->query($raw_query);

    $err = $trap->getErrorsAsString();
    $trap->destroy();

    // See T13238 and PHI1014. Sometimes, the call to "$conn->query()" may fail
    // without setting an error code on the connection. One way to reproduce
    // this is to use "LOAD DATA LOCAL INFILE" with "mysqli.allow_local_infile"

View on GitHub (pinned to 5720a38cfe)

Solutions

  1. Catch AphrontQueryTimeoutQueryException and retry with backoff — it is typed as recoverable, but first make the operation re-runnable/idempotent
  2. Make the query faster: add the missing index, or chunk the work (process IDs in batches) so each statement is quick
  3. Raise or remove the per-query time limit for long-running batch/CLI contexts, keeping tight limits only for web requests
  4. If lock waits are the cause, find and shorten the competing transaction rather than raising the limit

Example fix

// before: one giant statement that trips the query time limit
queryfx($conn_w, 'UPDATE t SET flag = 1');

// after: chunked, retryable work
foreach (array_chunk($ids, 512) as $chunk) {
  queryfx(
    $conn_w,
    'UPDATE t SET flag = 1 WHERE id IN (%Ld)',
    $chunk);
}
Defensive patterns

Strategy: retry

Validate before calling

// Prevent: keep each statement comfortably under the per-query limit by
// chunking keys before issuing large UPDATE/DELETE/SELECT work:
foreach (array_chunk($ids, 512) as $chunk) {
  queryfx($conn_w, 'UPDATE t SET flag = 1 WHERE id IN (%Ld)', $chunk);
}

Try / catch

// AphrontQueryTimeoutQueryException extends AphrontRecoverableQueryException:
try {
  queryfx($conn_w, '%s', $sql);
} catch (AphrontQueryTimeoutQueryException $ex) {
  // Recoverable by contract: back off and retry, ideally with smaller scope.
  sleep(1);
  // ...retry with chunked/optimized version of the work
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Understand the failure class

Related errors


AI-assisted analysis of phacility/phabricator@5720a38cfe (2026-08-21). Data as JSON: /api/errors/9c9dbb5df2da93c3. Report an issue: GitHub.