Skip to content

CloseCallbackBudget can skip internal PreShutdown hooks (resync stop, LimitFilter cleanup) #151

Description

@benitogf

Problem

Server.Close runs registered close hooks under an optional CloseCallbackBudget: once a positive budget is exhausted, runPhase skips the remaining hooks in that phase. Internal, lifecycle-critical hooks are registered through the same mechanism as user callbacks, so a tight budget can skip them:

  • the resync worker's stopResync (PreShutdown), added in fix(stream): make the broadcast the only writer of the pool cache #150 — a skipped stop leaves the worker goroutine alive into Storage.Close, i.e. a goroutine leak and a possible read-after-close on embedded storages.
  • LimitFilter.StopCleanup (PreShutdown, filters.go) — same exposure, pre-existing.

With the default budget of 0 nothing is skipped, so this is latent, not a live bug. But mixing internal teardown with budgeted user callbacks means an operator who sets a budget to bound user callbacks can silently break internal shutdown.

Suggested direction

Exempt internally-registered lifecycle hooks from CloseCallbackBudget — e.g. a separate non-budgeted registration path (or a flag on the hook) so the budget only ever caps user-supplied callbacks. Internal teardown should always run.

Surfaced during the self-review of #150; out of scope there.

🤖 Generated with Claude Code

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions