Skip to content

Report automatic queue startup failures instead of leaving them unhandled #1381

Description

@dahlia

Motivation

manuallyStartQueue defaults to false, so Fedify calls _startQueueInternal() automatically from inbox handling and from sendActivity(). Those call sites do not await or catch the returned promise.

WorkersMessageQueue.listen() throws a TypeError because Cloudflare Queues cannot be polled, as documented at packages/cfworkers/src/mod.ts:470-479. _startQueueInternal() is async, so the synchronous throw becomes a rejected promise, and the call site discards it. The first request that reaches one of these paths produces an unhandled rejection from the first queue whose listen() throws; later requests skip that queue because it has already been marked as started.

With WorkersMessageQueue, the queue itself is consumed by the Worker's queue() handler, so no message is lost. The rejection is still a real bug: it adds noise to production logs, trains users to ignore unhandled rejections, and can mask an actual startup failure from another queue.

This is the same Workers lifetime problem as #661, but on the automatic queue startup path rather than the fanout enqueue path.

The automatic call sites on main are middleware.ts:3785, :4924, :6609 and :7657, and tasks/enqueue.ts:96. On 2.0-maintenance they are middleware.ts:1480, :1778 and :2691.

Expected behavior

Automatic startup failures should be logged once and not become unhandled rejections. Explicit Federation.startQueue() should keep rejecting so a caller that starts workers deliberately sees the error.

Fix

Wrap each automatic call:

void this._startQueueInternal(data).catch((error) => {
  getLogger(["fedify", "federation", "queue"]).warn(
    "Failed to start queue workers automatically: {error}",
    { error },
  );
});

Inside _startQueueInternal(), attach a handler to each listen() promise before awaiting the aggregate, so one synchronous throw does not strand listeners that were already started.

Also update the Cloudflare Workers example, docs/manual/deploy.md, and docs/manual/mq.md to set manuallyStartQueue: true. On Workers this is the supported configuration, and the warning then does not appear at all.

Scope

  • Fix 2.0-maintenance first at middleware.ts:1480, :1778 and :2691, then forward-port to later maintenance branches and main.
  • No public API change.
  • Add a regression test with a fake queue whose listen() throws. The test should assert that fetch() does not emit an unhandled rejection and that explicit startQueue() still rejects.

Metadata

Metadata

Assignees

Labels

Type

Fields

Priority

None yet

Effort

None yet

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions