Skip to content

Operate

Errors and retries

Tell apart errors to fix from errors to retry, and back off correctly.

Errors reach you in two ways: createRoom() rejects when a room cannot open, and a running room reports problems to onError and to the promises of the commands that failed.

When a room can't open

ErrorCauseRetry?
TypeError: A valid API key is required.Missing or malformed keyNo: fix the configuration
Error: Unknown room setting: … and other setting errorsInvalid createRoom() settingsNo: fix the code
RoomAdmissionError with status 400, 401, 403Settings rejected, key invalid, or key lacks permissionNo
RoomAdmissionError with status 409 or 410The request went staleYes, as a new createRoom() call
RoomAdmissionError with status 429A room limit or request budgetYes, after retryAfterSeconds
RoomAdmissionError with status 503The service is temporarily unavailableYes, with backoff
TimeoutErrorStartup took longer than 15 secondsYes, with backoff
Signaling timed out, Host authority was not grantedSignaling failed during startupYes, with backoff

The errors reference lists the status codes in detail.

Back off

Retry only errors that can succeed later, wait longer after each failure, and honour retryAfterSeconds:

open-room.mjs
import { createRoom, RoomAdmissionError } from 'ball2d/node';

const permanent = new Set([400, 401, 403]);

export async function openRoom(config) {
  for (let attempt = 0; ; attempt++) {
    try {
      return await createRoom(config);
    } catch (error) {
      if (error instanceof TypeError) throw error;
      if (
        error instanceof RoomAdmissionError &&
        permanent.has(error.status)
      )
        throw error;
      const backoff =
        Math.min(60, 2 ** attempt) + Math.random();
      const wait = error.retryAfterSeconds ?? backoff;
      console.warn(
        `Room did not open (${error.message});` +
          ` retrying in ${Math.round(wait)} s`,
      );
      await new Promise((resolve) =>
        setTimeout(resolve, wait * 1000),
      );
    }
  }
}

While a room runs

  • Failed commands reject their promise, and the room also reports them to onError. Handle the promise where the failure matters, such as a ban the service didn't confirm.
  • Handler errors never crash your process. They are reported to onError as <event>: <error>.
  • The room ending is reported to onError with the reason, and room.signal aborts. See what closes a room.

Log onError everywhere. It is the one place every problem in a running room shows up:

JavaScript
room.onError = (message) =>
  logger.warn({ room: room.roomId, message }, 'room error');