Skip to content

fix(pxe): stop block synchronizer on PXE shutdown#22604

Open
benesjan wants to merge 1 commit intomerge-train/fairiesfrom
jan/f-339-pxe-stop-release-resources
Open

fix(pxe): stop block synchronizer on PXE shutdown#22604
benesjan wants to merge 1 commit intomerge-train/fairiesfrom
jan/f-339-pxe-stop-release-resources

Conversation

@benesjan
Copy link
Copy Markdown
Contributor

@benesjan benesjan commented Apr 16, 2026

Summary

Fixes https://linear.app/aztec-labs/issue/F-339

The issue reported three resource leaks in PXE.stop():

  1. L2BlockStream — active RunningPromise with timers: The issue assumed the block stream runs in polling mode (start()), but in the PXE context blockStream.start() is never called — the stream is only used in manual sync mode via blockStream.sync(). So there are no active timers or InterruptibleSleep handles keeping the event loop alive. That said, calling blockStream.stop() is still good defensive practice in case the usage mode changes.

  2. LMDB store — open file descriptors: PXE.stop() already calls this.db.close(), so this part was already fixed at some point after the issue was filed.

  3. HTTP keep-alive sockets from createAztecNodeClient: Node's built-in fetch (undici) uses HTTP keep-alive by default — after a request completes, the underlying TCP socket stays open in case another request comes to the same host. These idle sockets register as active handles on the event loop, preventing process.exit() from happening naturally. Fixing this would require either using a custom undici Agent/Pool with a close() method, or setting keepalive: false (which hurts performance). In practice this doesn't matter: the sockets time out on their own after a few seconds, and for long-running services (the main use case) the process stays up anyway. Only relevant if you need the process to exit immediately after stop() without calling process.exit(0).

What this PR actually fixes: BlockSynchronizer had no stop() method at all. If PXE.stop() were called while a sync was in progress, the ongoing sync could race against db.close(). While the job queue draining makes this unlikely in practice, adding an explicit stop() is the right defensive pattern and matches what ServerWorldStateSynchronizer already does.

Changes

  • Adds a stop() method to BlockSynchronizer that awaits any in-progress sync, then stops the block stream
  • Calls blockStateSynchronizer.stop() in PXE.stop() between draining the job queue and closing the DB
  • Shutdown order: job queue drains (no new syncs) → synchronizer stops (waits for in-flight sync) → DB closes

`PXE.stop()` did not stop the `BlockSynchronizer`, which could lead to
a race where an in-progress sync accesses the DB after it's been closed.
Add a `stop()` method to `BlockSynchronizer` that waits for any ongoing
sync and stops the block stream, and call it from `PXE.stop()` before
closing the DB.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant