Skip to content

[BUG] A clean service stop logs 7 ERRORs from a disposed data source — 7 of the day's 9 ERROR lines #2299

Description

@erikdarlingdata

What happens

Every clean, operator-initiated Stop-Service logs a burst of ERRORs from work that is still in flight after the collection loop has already reported it stopped. From prod-sql-use2-monitor-01 tonight:

18:52:12.940 [INFO ] [DarlingWebHostService] Stopping web dashboard
18:52:12.972 [INFO ] [DarlingMcpHostService] Stopping MCP server
18:52:13.379 [INFO ] [DarlingWorker] PerformanceMonitor Darling collection loop stopped
18:52:14.160 [ERROR] [DarlingWorker] [PgBaselineProvider] Failed to compute baselines for io_latency:
                     57P01: terminating connection due to administrator command
18:52:14.162 [ERROR] [DarlingWorker] [PgBaselineProvider] Failed to compute baselines for batch_requests:
                     Cannot access a disposed object.
18:52:14.162 [ERROR] [DarlingWorker] [PgBaselineProvider] Failed to compute baselines for session_count: ...
18:52:14.162 [ERROR] [DarlingWorker] [PgBaselineProvider] Failed to compute baselines for query_duration: ...
18:52:14.163 [ERROR] [DarlingWorker] [PgBaselineProvider] Failed to compute baselines for memory: ...
18:52:14.163 [ERROR] [DarlingWorker] [PgAnomalyDetector] Object stats anomaly detection failed:
                     Cannot access a disposed object.
18:52:14.164 [ERROR] [DarlingWorker] [PgFindingStore] FilterMutedFindingsAsync failed:
                     Cannot access a disposed object.

Seven ERRORs in 800 milliseconds, all after "collection loop stopped". Reproducible: the earlier stop today (12:54, a different build) produced the same shape from the same two components.

Why it is worth fixing rather than tolerating

The whole day's log on that box carries 9 ERROR lines. Seven of them are these. The other two are genuine (NpgsqlException: Exception while reading from stream during a drill-down and a baseline). So an operator or an alert rule looking at ERROR volume sees a 4.5x-inflated count whose dominant cause is "someone restarted the service", and the two real ones are the needles. That is the same failure mode as #2294 — a stop being reported as a fault — one layer up.

It is also, mechanically, unfinished work rather than pure noise: the analysis pass is still computing baselines and filtering findings when its data source is disposed underneath it, so whatever those five baselines would have written is lost. Losing it during shutdown is fine; not knowing whether it was lost or failed is not.

Mechanism

Two distinct causes in the same burst, and they want different handling:

  • 57P01: terminating connection due to administrator command — the store's own postmaster is going down (the managed Postgres stops with the service), so an in-flight query is killed server-side. This is an expected consequence of a stop, not a fault.
  • Cannot access a disposed object — the NpgsqlDataSource is disposed while the analysis pass is still running. The pass is started but the host does not wait for it (or cancel it) before disposing, so the shutdown is racing its own work.

The second is the one with a real fix: the analysis pass should observe the stopping token and complete or abandon before the data source goes away. Note the pass takes a serverId and cannot gate itself, which is the same seam #2213's review flagged — the gating belongs at the call site.

Suggested shape

  1. Cancel-and-await the analysis pass in the host's StopAsync before disposal, so the disposed-object path becomes unreachable rather than merely quiet.
  2. Classify the residue as shutdown, not error: with the stopping token signalled, 57P01, ObjectDisposedException and OperationCanceledException are the expected outcomes of a stop, and belong at Information ("baselines abandoned at shutdown") — one line, not seven. Keep them at ERROR when the token is not signalled, because then they mean something real.
  3. Do not simply lower the log level unconditionally — that hides the case where a data source is disposed while the service is meant to be running, which is a genuine bug this text would otherwise be the only evidence of.

The distinction to preserve throughout: unfinished because we asked it to stop versus unfinished because something broke. Today the log cannot tell those apart, which is exactly why the seven lines are indistinguishable from the two that matter.

Found while dogfooding on the use2 box (#2150 flip deploy); unrelated to that change — the same burst is in the 12:54 stop on the previous build.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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