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
- Cancel-and-await the analysis pass in the host's
StopAsync before disposal, so the disposed-object path becomes unreachable rather than merely quiet.
- 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.
- 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.
What happens
Every clean, operator-initiated
Stop-Servicelogs a burst of ERRORs from work that is still in flight after the collection loop has already reported it stopped. Fromprod-sql-use2-monitor-01tonight: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 streamduring 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— theNpgsqlDataSourceis 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
serverIdand cannot gate itself, which is the same seam #2213's review flagged — the gating belongs at the call site.Suggested shape
StopAsyncbefore disposal, so the disposed-object path becomes unreachable rather than merely quiet.57P01,ObjectDisposedExceptionandOperationCanceledExceptionare 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.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.