Skip to content

[AAS] Recover agent processes after overlapped recycles, and log their lifecycle - #9397

Open
Lewis-E wants to merge 15 commits into
masterfrom
lewis.lewis/svls-9057-agent-lifecycle-logging
Open

Lewis-E wants to merge 15 commits into
masterfrom
lewis.lewis/svls-9057-agent-lifecycle-logging

Conversation

@Lewis-E

@Lewis-E Lewis-E commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

Summary of changes

In Azure App Service, AgentProcessManager now:

  • logs the agent lifecycle at the default level: the child PID on start, the exit code on exit, when it reuses an agent it did not start, and when that agent goes away;
  • never starts a second agent while one it started is still running;
  • counts an agent it did not start as healthy only while its named pipe exists, not because a process with that name is running;
  • treats a busy named pipe as present;
  • backs off retries instead of giving up for good after 5 failures. Failed starts and health-check restarts share one backoff: 2 s, doubling, up to DD_AAS_AGENT_MAX_RETRY_INTERVAL_SECONDS (default 300). The count resets only after the agent has stayed healthy for 60 s. An exit of an agent this process started also counts, even between checks, and after each wait it re-checks health so an agent another worker started meanwhile is reused;
  • checks that the trace agent answers GET /info on its pipe, with the connect and the read both time-limited;
  • restarts an agent it started after 3 failed health checks in a row. It never kills an agent it did not start.

Reason for change

See(SLES-2826, tracked in SVLS-9057) -- A customer loses telemetry after Azure restarts. Overlapped IIS recycling runs an old and a new worker process that share the extension's named pipes. Today:

  • a running dogstatsd.exe that lost the pipe still counts as healthy by name, so it is never replaced;
  • the manager gives up for good after 5 fast failures;
  • if the agent takes more than 2 s to start, the manager can start another copy 10 s later without checking;
  • the default logs don't show which process owns which agent.

This picks up from the closed #8874 and addresses its review comments:

  • DFIX-01 tracks our own process by PID, so a slow-binding agent is never killed or duplicated.
  • The startup race between AppDomains remains, but with agent 7.83+ and dogstatsd_require_listener (datadog-aas-extension#487) the losing DogStatsD exits instead of staying idle.

Implementation details

  • A new Unhealthy state means "our process is running but failing its check". The pure helpers GetKeepAliveAction and GetRetryDelay hold the decisions.
  • The pipe check uses WaitNamedPipeW with a 1 ms timeout: busy or timeout counts as present, anything else as absent.
  • The /info check opens a new NamedPipeClientStream per check, connects within 500 ms, and finishes the exchange within 2 s.
  • The "Maximum retries" message is gone along with the give-up. Each restart logs the wait and the number of failures in a row.
  • A failed health check records why (pipe missing, connect timeout, no response within the timeout, closed, unexpected response), and the default-level state-change logs include that reason. Per-check exception details stay at Debug, so a long outage doesn't repeat them.
  • New setting DD_AAS_AGENT_MAX_RETRY_INTERVAL_SECONDS (product AzureAppService, int, default 300) in supported-configurations.yaml, read through ImmutableAzureAppServiceSettings. A value that isn't positive falls back to the default.

Test coverage

New AgentProcessManagerTests cover the decisions: never starting while our own process runs, restarting at 3 failures, never restarting an agent we did not start, ignoring a same-named process when its pipe is missing, the retry delay with its default, configured, and largest accepted caps, and a child that exits between checks. Windows-only tests run the /info check against a real pipe server that answers and one that accepts but never answers, checking the result, that it finishes within the timeout, and that the connection is closed.

In an Azure App Service repro (Windows B2, the published extension with this build laid over it, agent 7.84.0 with dogstatsd_require_listener), an overlapped recycle under load:

  • the new worker reused the old agents;
  • it restarted both within 1 s of the old ones going away;
  • both passed health checks 15 s later.

There were no duplicates and no restart loops.

Other details

  • At the 5-minute cap, recovery after a long outage can take up to 5 minutes once the cause clears. Lower DD_AAS_AGENT_MAX_RETRY_INTERVAL_SECONDS if faster recovery matters more than CPU.
  • The new setting will need an entry in the central configuration registry. Waiting on initial discussion first.
  • Ideally, we follow this with per-process pipe names in both tracers to avoid any possible race conditions.

@github-actions github-actions Bot added the area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations) label Oct 9, 2026
@Lewis-E Lewis-E changed the title [AAS] Log agent process lifecycle with PIDs and exit codes [DO NOT MERGE] [AAS] Repro build: log agent process lifecycle with PIDs and exit codes Oct 9, 2026
@Lewis-E Lewis-E changed the title [DO NOT MERGE] [AAS] Repro build: log agent process lifecycle with PIDs and exit codes [AAS] Log agent process lifecycle with PIDs and exit codes Oct 9, 2026
@dd-trace-dotnet-ci-bot

dd-trace-dotnet-ci-bot Bot commented Oct 9, 2026 •

Copy link
Copy Markdown

Execution-Time Benchmarks Report ⏱️

Execution-time results for samples comparing This PR (9397) and master.

✅ No regressions detected

📄 View the full report (charts + all metrics) →

@Lewis-E Lewis-E changed the title [AAS] Log agent process lifecycle with PIDs and exit codes [AAS] Recover agent processes after overlapped recycles, and log their lifecycle Oct 15, 2026
@pr-commenter

pr-commenter Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Benchmarks

Benchmark execution time: 2026-10-16 17:18:41

Comparing candidate commit af0e9b7 in PR branch lewis.lewis/svls-9057-agent-lifecycle-logging with baseline commit 16b7c53 in branch master.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 72 metrics, 0 unstable metrics, 9 known flaky benchmarks, 117 flaky benchmarks without significant changes.

Explanation

This is an A/B test comparing a candidate commit's performance against that of a baseline commit. Performance changes are noted in the tables below as:

  • 🟩 = significantly better candidate vs. baseline
  • 🟥 = significantly worse candidate vs. baseline

We compute a confidence interval (CI) over the relative difference of means between metrics from the candidate and baseline commits, considering the baseline as the reference.

If the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD), the change is considered significant.

Feel free to reach out to #apm-benchmarking-platform on Slack if you have any questions.

More details about the CI and significant changes

You can imagine this CI as a range of values that is likely to contain the true difference of means between the candidate and baseline commits.

CIs of the difference of means are often centered around 0%, because often changes are not that big:

---------------------------------(------|---^--------)-------------------------------->
                              -0.6%    0%  0.3%     +1.2%
                                 |          |        |
         lower bound of the CI --'          |        |
sample mean (center of the CI) -------------'        |
         upper bound of the CI ----------------------'

As described above, a change is considered significant if the CI is entirely outside the configured SIGNIFICANT_IMPACT_THRESHOLD (or the deprecated UNCONFIDENCE_THRESHOLD).

For instance, for an execution time metric, this confidence interval indicates a significantly worse performance:

----------------------------------------|---------|---(---------^---------)---------->
                                       0%        1%  1.3%      2.2%      3.1%
                                                  |   |         |         |
       significant impact threshold --------------'   |         |         |
                      lower bound of CI --------------'         |         |
       sample mean (center of the CI) --------------------------'         |
                      upper bound of CI ----------------------------------'

Known flaky benchmarks

These benchmarks are marked as flaky and will not trigger a failure. Modify FLAKY_BENCHMARKS_REGEX to control which benchmarks are marked as flaky.

scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan net472

  • 🟥 throughput [-17638.995op/s; -15576.917op/s] or [-8.792%; -7.764%]

scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled net472

  • 🟥 throughput [-12184.956op/s; -10024.398op/s] or [-6.925%; -5.697%]

scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled net472

  • 🟥 throughput [-12709.229op/s; -10024.864op/s] or [-6.443%; -5.082%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net6.0

  • unstable execution_time [-25.437ms; +63.271ms] or [-5.701%; +14.179%]

scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest netcoreapp3.1

  • unstable execution_time [+60.363ms; +127.951ms] or [+14.845%; +31.467%]

scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net472

  • unstable execution_time [+27.437ms; +110.282ms] or [+5.356%; +21.527%]
  • unstable throughput [-0.942op/s; +62.526op/s] or [-0.178%; +11.792%]

scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice netcoreapp3.1

  • unstable throughput [+14.673op/s; +60.692op/s] or [+4.129%; +17.078%]

scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark netcoreapp3.1

  • unstable execution_time [-4.225µs; +1.897µs] or [-7.558%; +3.393%]

scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore netcoreapp3.1

  • 🟩 throughput [+24303868.818op/s; +26981924.494op/s] or [+11.106%; +12.329%]

Known flaky benchmarks without significant changes:

  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_AddEvent_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_GetContext_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetAttributes_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_SetStatus_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.ActivityBenchmark.StartSpan_UpdateName_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_AddEvent_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_GetContext_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_RecordException_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetAttributes_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_SetStatus_Sampled netcoreapp3.1
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled net472
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled net6.0
  • scenario:Benchmarks.OpenTelemetry.InstrumentedApi.Trace.TelemetrySpanBenchmark.StartSpan_UpdateName_Sampled netcoreapp3.1
  • scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net472
  • scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild net6.0
  • scenario:Benchmarks.Trace.ActivityBenchmark.StartStopWithChild netcoreapp3.1
  • scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net472
  • scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
  • scenario:Benchmarks.Trace.AgentWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleMoreComplexBody netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.AllCycleSimpleBody netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorMoreComplexBody netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net472
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecBodyBenchmark.ObjectExtractorSimpleBody netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net472
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeArgs netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net472
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecEncoderBenchmark.EncodeLegacyArgs netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net472
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmark netcoreapp3.1
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net472
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack net6.0
  • scenario:Benchmarks.Trace.Asm.AppSecWafBenchmark.RunWafRealisticBenchmarkWithAttack netcoreapp3.1
  • scenario:Benchmarks.Trace.AspNetCoreBenchmark.SendRequest net472
  • scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces net6.0
  • scenario:Benchmarks.Trace.CIVisibilityProtocolWriterBenchmark.WriteAndFlushEnrichedTraces netcoreapp3.1
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSlice net6.0
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool net6.0
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OptimizedCharSliceWithPool netcoreapp3.1
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net472
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice net6.0
  • scenario:Benchmarks.Trace.CharSliceBenchmark.OriginalCharSlice netcoreapp3.1
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net472
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch net6.0
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearch netcoreapp3.1
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net472
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync net6.0
  • scenario:Benchmarks.Trace.ElasticsearchBenchmark.CallElasticsearchAsync netcoreapp3.1
  • scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net472
  • scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync net6.0
  • scenario:Benchmarks.Trace.GraphQLBenchmark.ExecuteAsync netcoreapp3.1
  • scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net472
  • scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog net6.0
  • scenario:Benchmarks.Trace.ILoggerBenchmark.EnrichedLog netcoreapp3.1
  • scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net472
  • scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark net6.0
  • scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatAspectBenchmark netcoreapp3.1
  • scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net472
  • scenario:Benchmarks.Trace.Iast.StringAspectsBenchmark.StringConcatBenchmark net6.0
  • scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net472
  • scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog net6.0
  • scenario:Benchmarks.Trace.Log4netBenchmark.EnrichedLog netcoreapp3.1
  • scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net472
  • scenario:Benchmarks.Trace.RedisBenchmark.SendReceive net6.0
  • scenario:Benchmarks.Trace.RedisBenchmark.SendReceive netcoreapp3.1
  • scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net472
  • scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog net6.0
  • scenario:Benchmarks.Trace.SerilogBenchmark.EnrichedLog netcoreapp3.1
  • scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net472
  • scenario:Benchmarks.Trace.SingleSpanAspNetCoreBenchmark.SingleSpanAspNetCore net6.0
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net472
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope net6.0
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishScope netcoreapp3.1
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net472
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan net6.0
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishSpan netcoreapp3.1
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net472
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes net6.0
  • scenario:Benchmarks.Trace.SpanBenchmark.StartFinishTwoScopes netcoreapp3.1
  • scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net472
  • scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin net6.0
  • scenario:Benchmarks.Trace.TraceAnnotationsBenchmark.RunOnMethodBegin netcoreapp3.1

@Lewis-E
Lewis-E marked this pull request as ready for review October 15, 2026 20:15
@Lewis-E
Lewis-E requested review from a team as code owners October 15, 2026 20:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-15T20:22:14.502160Z 1469fd4 Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@datadog-datadog-prod-us1 datadog-datadog-prod-us1 Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bits Code Review: FAIL

Restart backoff can launch a duplicate agent after another worker has already restored service. Very large configured retry caps can also overflow the delay and permanently terminate the keep-alive.

Open Bits AI session

🤖 Bits Code Review · Commit 1469fd4

Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs Outdated
Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 1469fd4f5f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs Outdated
Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs Outdated
Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs
Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs Outdated
Comment thread tracer/src/Datadog.Trace/AgentProcessManager.cs
@Lewis-E
Lewis-E force-pushed the lewis.lewis/svls-9057-agent-lifecycle-logging branch 2 times, most recently from 1f1016d to fc83561 Compare October 16, 2026 02:02
In Azure App Service the tracer starts and keeps alive the trace agent and
DogStatsD. When overlapping worker processes compete during platform
restarts, default logs could not show which agent was started, which one
exited and why, or that an agent started elsewhere was being reused.

- Log the child PID when an agent is started, and its exit code when it
  exits during startup or later.
- Say when the startup check passed only because another instance is
  running, instead of reporting a successful start.
- Warn when an agent is still running but has not opened its named pipe
  by the end of the startup wait.
- Log once when an agent not started by this process is reused, and when
  the agent in use goes away; move the per-check "no longer running" line
  to debug.
- Fix the argument order of the "Maximum retries" log message.
DFIX-01 (SVLS-9057). If the startup wait timed out while the agent we
started was still running, the state stayed ReadyToStart and the next
keep-alive pass started another copy without re-checking.

- Re-check health on every pass after the first, including after a
  startup timeout.
- Add an Unhealthy state for "the process we started is running but the
  health check fails", and only start a new process when ours is not
  running (GetKeepAliveAction).
- Log the transitions into and out of Unhealthy once.
DFIX-02 (SVLS-9057). File.Exists on a named pipe can report false while
every server instance is busy, which is why the health check needed a
process-name fallback.

Check the pipe with WaitNamedPipe and a 1 ms timeout instead: success or
ERROR_SEM_TIMEOUT/ERROR_PIPE_BUSY means the pipe exists, anything else
(ERROR_FILE_NOT_FOUND) means it does not. The check does not connect to
the pipe and returns at once.
DFIX-03 (SVLS-9057). The health check passed whenever any process named
datadog-trace-agent or dogstatsd was running, so an orphaned, hung, or
shutting-down agent from another worker was trusted forever.

Drop the process-name fallback: the agent is healthy only while its
named pipe exists. The process we started is still tracked by PID
(OwnProcessIsRunning), so a slow-binding agent of ours is not started
twice. The startup wait now waits for the pipe instead of passing as
soon as the child is running.
DFIX-04 (SVLS-9057). After 5 failed starts in a row the keep-alive logged
"Maximum retries" and exited for the rest of the worker's life, for
example after a burst of failures during a platform restart.

Keep retrying instead, waiting 2 s after the first failure and doubling
with each further failure up to 60 s (GetRetryDelay). A healthy check
resets the count. Rethrow ThreadAbortException in the loop, since the
try block is now the loop's only statement (DD0001).
DFIX-05 (SVLS-9057). A trace agent that still held its pipe but had
stopped serving requests passed the health check forever.

For the trace agent only, the health check now also sends GET /info over
a new pipe client stream (same options as the per-request trace stream)
and requires an HTTP status line back. The connect is bounded to 500 ms
and the whole exchange to 2 s; on timeout the stream is disposed, which
aborts the pending read. The request is only sent once the pipe exists,
so it runs once per keep-alive pass (10 s) in steady state. DogStatsD
never replies, so its check stays "pipe present".

The startup wait is now bounded by elapsed time rather than by loop
count, since a check can take up to 2 s.
DFIX-06 (SVLS-9057). A hung agent that this tracer started was never
replaced, because nothing stopped it and DFIX-01 rightly refuses to
start a second copy next to it.

When the process this keep-alive started is still running and fails
MaxFailedHealthChecks (3) checks in a row, about 30 s at the 10 s
keep-alive interval, kill it and start a new one in the same pass. An
agent we did not start is never stopped: with no own process running,
the action is always Start. If the kill does not take effect within
1 s, keep tracking the process and try again on the next pass rather
than start next to it. The restart is logged once.
Log the start attempt at Information again, log exceptions while checking
or stopping our own agent as errors or warnings, and log a trace agent
that does not answer its health check request at Information, so these
show up in customer logs without enabling debug logging.
An agent this process started that keeps failing its health check was
restarted about every 32 s forever, because only failed starts backed
off. Failed starts and health-check restarts now share one counter and
one backoff: 2 s, doubling, up to DD_AAS_AGENT_MAX_RETRY_INTERVAL_SECONDS
(default 300). The counter resets only after the agent has stayed
healthy for 60 s, so an agent that flaps between healthy and unhealthy
keeps backing off. Retries still never stop. Each restart logs the wait
and how many failures in a row led to it.
…gerTests

Five failures in a row wait 32 s (2, 4, 8, 16, 32); the 60 s cap only applies from the sixth.
- Count an exit of an agent this process started as a failure, even when it
  happens after the startup wait and before the next check, and wait for the
  shared retry delay before starting a new one. First starts and reuse of an
  already-healthy agent stay immediate.
- After a retry wait, check health again and reuse an agent another worker
  started meanwhile instead of starting a duplicate.
- Keep per-probe health check exceptions at Debug. Record why the check
  failed (pipe missing, connect timeout, no response within the timeout,
  closed, unexpected response) and include that reason in the default-level
  state-change logs.
- Double the retry delay in long so a very large configured cap cannot
  overflow into a negative delay.
- Log PIDs and exit codes as numbers instead of strings.
- Tests: retry delay at the largest accepted cap, start delay, a child that
  exits between checks, and Windows tests of the trace agent check against a
  pipe server that answers and one that accepts but never answers.
Follow the repository's log level rule for retries: the first failure in a
run of failures logs at Warning (or Information for starts and exits), and
later attempts in the same run log at Debug. Failed starts are retried with
backoff, so they are no longer logged as errors. State changes (unhealthy,
healthy again, no longer available, reusing another agent, successfully
started) still log once at the default level.
Inline the message templates in both the Warning and Debug branches so
the DDLOG004 analyzer sees constants, and add the blank lines SA1513
requires after closing braces.
CA2022 rejects ReadAsync calls whose byte count is ignored. Assert that
each read of the probe's request returns data.
@Lewis-E
Lewis-E force-pushed the lewis.lewis/svls-9057-agent-lifecycle-logging branch from fc83561 to 411614f Compare October 16, 2026 13:39
The test pipe servers have no buffer, so a pipe write completes only once
the other side reads it. Both new health check tests failed on every
Windows framework because of that, not because of the health check:

- The answering agent's response write broke with "Pipe is broken" after
  the probe read the status line and closed the pipe. Ignore that error.
- The silent agent never read the request, so the probe's write never
  completed and no request bytes ever arrived. Read the request first,
  then stay silent, so the test covers an agent that never answers.

This branch has not been deployed

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

Labels

area:tracer The core tracer library (Datadog.Trace, does not include OpenTracing, native code, or integrations)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant