[Debugger] Bound Dynamic Instrumentation probe expression evaluation with a time budget#8806
[Debugger] Bound Dynamic Instrumentation probe expression evaluation with a time budget#8806dudikeleti wants to merge 4 commits into
Conversation
This comment has been minimized.
This comment has been minimized.
Execution-Time Benchmarks Report ⏱️Execution-time results for samples comparing This PR (8806) and master. ✅ No regressions detected - check the details below Full Metrics ComparisonFakeDbCommand
HttpMessageHandler
Comparison explanationExecution-time benchmarks measure the whole time it takes to execute a program, and are intended to measure the one-off costs. Cases where the execution time results for the PR are worse than latest master results are highlighted in **red**. The following thresholds were used for comparing the execution times:
Note that these results are based on a single point-in-time result for each branch. For full results, see the dashboard. Graphs show the p99 interval based on the mean and StdDev of the test run, as well as the mean value of the run (shown as a diamond below the graph). Duration chartsFakeDbCommand (.NET Framework 4.8)gantt
title Execution time (ms) FakeDbCommand (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (73ms) : 69, 76
master - mean (73ms) : 69, 77
section Bailout
This PR (8806) - mean (76ms) : 73, 79
master - mean (74ms) : 73, 76
section CallTarget+Inlining+NGEN
This PR (8806) - mean (1,080ms) : 1035, 1124
master - mean (1,082ms) : 1038, 1126
FakeDbCommand (.NET Core 3.1)gantt
title Execution time (ms) FakeDbCommand (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (113ms) : 108, 119
master - mean (114ms) : 106, 121
section Bailout
This PR (8806) - mean (112ms) : 107, 116
master - mean (111ms) : 109, 113
section CallTarget+Inlining+NGEN
This PR (8806) - mean (774ms) : 753, 794
master - mean (773ms) : 755, 791
FakeDbCommand (.NET 6)gantt
title Execution time (ms) FakeDbCommand (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (99ms) : 94, 104
master - mean (102ms) : 96, 108
section Bailout
This PR (8806) - mean (98ms) : 97, 99
master - mean (100ms) : 95, 105
section CallTarget+Inlining+NGEN
This PR (8806) - mean (937ms) : 895, 980
master - mean (937ms) : 891, 984
FakeDbCommand (.NET 8)gantt
title Execution time (ms) FakeDbCommand (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (95ms) : 92, 98
master - mean (95ms) : 92, 98
section Bailout
This PR (8806) - mean (99ms) : 95, 104
master - mean (101ms) : 96, 106
section CallTarget+Inlining+NGEN
This PR (8806) - mean (814ms) : 772, 856
master - mean (815ms) : 772, 857
HttpMessageHandler (.NET Framework 4.8)gantt
title Execution time (ms) HttpMessageHandler (.NET Framework 4.8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (203ms) : 198, 207
master - mean (202ms) : 197, 206
section Bailout
This PR (8806) - mean (207ms) : 203, 210
master - mean (205ms) : 202, 209
section CallTarget+Inlining+NGEN
This PR (8806) - mean (1,207ms) : 1169, 1245
master - mean (1,206ms) : 1165, 1247
HttpMessageHandler (.NET Core 3.1)gantt
title Execution time (ms) HttpMessageHandler (.NET Core 3.1)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (289ms) : 282, 297
master - mean (291ms) : 285, 297
section Bailout
This PR (8806) - mean (291ms) : 286, 296
master - mean (292ms) : 288, 296
section CallTarget+Inlining+NGEN
This PR (8806) - mean (973ms) : 951, 994
master - mean (969ms) : 949, 989
HttpMessageHandler (.NET 6)gantt
title Execution time (ms) HttpMessageHandler (.NET 6)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (283ms) : 276, 290
master - mean (284ms) : 275, 292
section Bailout
This PR (8806) - mean (284ms) : 279, 289
master - mean (284ms) : 278, 290
section CallTarget+Inlining+NGEN
This PR (8806) - mean (1,174ms) : 1119, 1229
master - mean (1,177ms) : 1140, 1214
HttpMessageHandler (.NET 8)gantt
title Execution time (ms) HttpMessageHandler (.NET 8)
dateFormat x
axisFormat %Q
todayMarker off
section Baseline
This PR (8806) - mean (280ms) : 274, 286
master - mean (280ms) : 275, 285
section Bailout
This PR (8806) - mean (282ms) : 274, 290
master - mean (281ms) : 277, 285
section CallTarget+Inlining+NGEN
This PR (8806) - mean (1,056ms) : 1001, 1111
master - mean (1,041ms) : 994, 1088
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
BenchmarksBenchmark execution time: 2026-06-24 00:28:23 Comparing candidate commit 171cef4 in PR branch Found 0 performance improvements and 0 performance regressions! Performance is the same for 72 metrics, 0 unstable metrics, 58 known flaky benchmarks, 68 flaky benchmarks without significant changes.
|
|
@codex review |
There was a problem hiding this comment.
Pull request overview
This pull request adds a cooperative evaluation time budget to Dynamic Instrumentation (DI) probe expression execution so remotely-authored expressions can’t run unbounded on customer threads, and threads that budget through all compiled sub-expressions for a probe hit (templates/condition/metric/span decorations/captures).
Changes:
- Introduces
EvaluationBudget,CompiledExpressionDelegate<T>(now budget-aware), andEvaluationTimeBudgetExceededException, and plumbs a single budget through the evaluator. - Injects budget checkpoints into generated expression trees (root, loops, string ops, dumps, regex) and replaces LINQ predicate loops with hand-built enumerator loops to checkpoint per-iteration.
- Updates debugger expression language tests and approval snapshots to reflect the new expression tree shapes and budget plumbing.
Reviewed changes
Copilot reviewed 61 out of 61 changed files in this pull request and generated 2 comments.
Show a summary per file
| File | Description |
|---|---|
| tracer/src/Datadog.Trace/Debugger/DebuggerSettings.cs | Adds default max evaluation time constant (50ms). |
| tracer/src/Datadog.Trace/Debugger/Expressions/CompiledExpression.cs | Updates compiled expression container to hold budget-aware delegate and nullable metadata. |
| tracer/src/Datadog.Trace/Debugger/Expressions/CompiledExpressionDelegate.cs | Adds delegate type that carries ref EvaluationBudget. |
| tracer/src/Datadog.Trace/Debugger/Expressions/EvaluationBudget.cs | Implements cooperative deadline + amortized checkpointing. |
| tracer/src/Datadog.Trace/Debugger/Expressions/EvaluationTimeBudgetExceededException.cs | Adds exception used when the evaluation budget is exceeded. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ExpressionEvaluationResult.cs | Stores/reuses a budget across evaluation and capture expression phases. |
| tracer/src/Datadog.Trace/Debugger/Expressions/FilterEvaluationHelpers.cs | Threads budget into bounded capture filtering and adds per-item checkpoints. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionEvaluator.cs | Creates one budget per probe hit and threads it through all sub-evaluations; normalizes timeout error message. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.cs | Adds evaluation budget parameter to generated lambdas and injects root checkpoints. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.Binary.cs | Adds checkpoint before string lexicographic comparisons. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.Collection.cs | Replaces LINQ Any/All/Filter with explicit enumerator loops and adds per-iteration checkpoints; budgets bounded capture filter predicate. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.Dump.cs | Adds checkpoints inside dump loops and adjusts dump formatting/null handling paths. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.General.cs | Uses StringUtil.IsNullOrEmpty for consistency and compatibility. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParser.String.cs | Adds budget-aware regex matching with real regex timeout + checkpoints for string operations. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionParserHelper.cs | Extends parsed expression parameter bundle to include evaluation budget parameter expression. |
| tracer/src/Datadog.Trace/Debugger/Expressions/ProbeExpressionsBucket.cs | Improves nullability annotations for TryGetFirstEntry out parameter. |
| tracer/src/Datadog.Trace.Trimming/build/Datadog.Trace.Trimming.xml | Adds TryExpression preservation for trimming due to Expression.TryFinally usage. |
| tracer/test/Datadog.Trace.Tests/Debugger/DebuggerExpressionLanguageTests.cs | Updates tests to use budgeted delegates and adds budget/timeout behavior tests; updates readable-expression sanitization. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.AccessNullObject.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.AccessNullableStruct.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.AccessNullableStructNotNull.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.AllGt.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ArrayAtIndex.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ChildPrivateMember.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ChildStaticPublicMember.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.Collection.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.CollectionCount.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.CollectionIndex.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.CollectionIndexOutOfRange.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.CustomArrayAtIndex.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.DictionaryKey.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.DictionaryKeyNotExist.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.Duration.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.Exception.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.Filter.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.GreaterThanString.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.GreaterthanOrEqualString.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HashAnyKeyValueAnd.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HashFilterKeyValue.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HasAll.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HasAny.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HasAnyCustomObject.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.HasAnyGt.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.IllegalCollectionOperation.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.IllegalStringOperation.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.IsEmpty.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.IsEmptyCollection.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.LenAndCountOrHasAny.verified.txt | Updates approved snapshot output for new explicit loop shape. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.LessThanString.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.NestedFieldAccess.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.NotEqual.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ParentPrivateMember.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ParentStaticProtectedMember.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.RefGetMember.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.RefGetMemberTwoLevels.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ReturnString.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.This.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.ToStringNotSupported.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.TypeofCustomType.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/test/Datadog.Trace.Tests/Debugger/ProbeExpressionsResources/Approvals/DebuggerExpressionLanguageTests.TypeofString.verified.txt | Updates approved snapshot output for rendering/sanitization changes. |
| tracer/missing-nullability-files.csv | Removes files that are now nullability-enabled from the tracking list. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
|
Codex Review: Didn't find any major issues. Keep them coming! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
1246ce2 to
d58479c
Compare
| public const string DebuggerMetricPrefix = "dynamic.instrumentation.metric.probe"; | ||
| public const int DefaultMaxDepthToSerialize = 3; | ||
| public const int DefaultMaxSerializationTimeInMilliseconds = 200; | ||
| public const int DefaultMaxEvaluationTimeInMilliseconds = 50; |
There was a problem hiding this comment.
why not publishing an env var to change it?
In Java, I am concern about releasing a hard coded value that is not correct (for whatever reason) and we are no way for the user to modify it!
Moreover, If I am reading correctly, this is wall time budget, right?
means a compacting Gen2 may provoke a timeout of evals (maybe other kind of collection) for whatever reasons.
for eval time I more inclined to use CPU Time when possible.
There was a problem hiding this comment.
why not publishing an env var to change it?
I agree that it’s better to make it configurable. I initially added it and then removed it because I had some concerns, I've added the env var 171cef4
Moreover, If I am reading correctly, this is wall time budget, right?
means a compacting Gen2 may provoke a timeout of evals
I see your point, but I still prefer this approach with the known limitation. It's much simpler. Also regexes supports wall-clock timeouts out of the box, and enumerables with custom enumerators may not behave well when using CPU time-based limits.
db7fb26 to
171cef4
Compare
Summary of changes
EvaluationBudget(aref-passed value type),CompiledExpressionDelegate<T>(delegate signature now carriesref EvaluationBudget), andEvaluationTimeBudgetExceededException.any/all/filterloops (instead of LINQ) so a checkpoint can be placed inside each iteration; uses a realRegextimeout for pattern matching.SafeEqualsbinary path and member/property access (the latter ahead of a separate change restricting member access to fields and auto-properties).Reason for change
Implementation details
The budget (
EvaluationBudget)refeverywhere (no allocation, no copies).Create(maxMs)records a deadline asStopwatch.GetTimestamp() + duration(with an overflow guard so very large values clamp tolong.MaxValue).ThrowIfExceeded()is the hot checkpoint and is the only thing injected into the common paths. It is amortized: reading the clock on every operation would be too expensive, so it only samples the clock once everyOperationsBeforeTimeCheck(32) checkpoints.[MethodImpl(AggressiveInlining)]; the throw/clock helpers are[NoInlining]to keep the inlined hot path tiny.TimedOutis sticky: once the deadline is hit, all later checkpoints throw immediately.GetRemainingTimeout()converts the remaining budget into aTimeSpanand is handed toRegex.IsMatch(...)— regex is the only primitive that can block for a long time inside a single call, so it gets a real hard timeout; aRegexMatchTimeoutExceptionis converted into the budget exception (and marks the budget timed out).flowchart TD Start["ThrowIfExceeded() — inlined into the hot path"] --> T{TimedOut?} T -- "yes (sticky)" --> Throw["ThrowTimedOut() [NoInlining]"] T -- no --> Dec["--operationsUntilTimeCheck"] Dec --> C{"> 0 ?"} C -- "yes (31 of 32 calls)" --> Ret["return — no clock read"] C -- "no (every 32nd call)" --> Clock["ThrowIfTimeExceeded() [NoInlining]<br/>reset counter to 32, read Stopwatch"] Clock --> D{"now >= deadline?"} D -- no --> Ret D -- yes --> Mark["MarkTimedOut() + throw EvaluationTimeBudgetExceededException"]Threading the budget through compiled expressions
CompiledExpressionDelegate<T>adds a trailingref EvaluationBudget budgetparameter;CompiledExpression<T>.BudgetedDelegateis the compiled instance.evaluationBudgetrefparameter for the generated lambda and emitsEvaluationBudget.ThrowIfExceeded(ref evaluationBudget)(BudgetCheck()) at strategic spots.ProbeExpressionEvaluator.Evaluatecreates a single budget and passes the samerefto every sub-expression, then stores it back on the (ref struct) result.EvaluateCaptureExpressionsreuses that same budget if present, so the deadline spans the whole probe evaluation rather than resetting per sub-expression.flowchart TD A["Probe hit → Evaluate(scopeMembers)"] --> B["CreateBudget(): deadline = now + 50ms"] B --> C["Templates(... , ref budget)"] C --> D["Condition(... , ref budget)"] D --> E["Metric(... , ref budget)"] E --> F["Span decorations(... , ref budget)"] F --> G["result.EvaluationBudget = budget"] G --> H["Capture expressions reuse the same budget (same deadline)"]Where checkpoints are injected (
BudgetCheck())any/all/filterare now hand-built loops (BuildEnumerableLoop) with a single checkpoint at the top of each iteration; enumerator disposal usesExpression.TryFinally. The bounded capture-filter path threads the budget throughFilterEvaluationHelpers.FilterForCapture+FilterPredicate<T>, checking once per item and inside the predicate.Substring,Contains/StartsWith/EndsWith,IsEmpty(string and collection length).Regex.IsMatchtimeout viaGetRemainingTimeout().Checkpoints intentionally removed
SafeEquals: equality only dispatches to an allowlisted set ofEqualsimplementations that are bounded and fast, so a per-comparison checkpoint added overhead with no protective value.Notes / risks
EvaluationTimeBudgetExceededException, surfaced as an evaluation error. As before, a condition that errors defaults totrue.DefaultMaxEvaluationTimeInMilliseconds = 50; the evaluator is currently always constructed with this default (not yet exposed as an environment variable).Datadog.Trace.Trimming.xmlgains aSystem.Linq.Expressions.TryExpressionentry (auto-generated) because the new enumerable loops useExpression.TryFinally.refvalue type, the checkpoint is aggressively inlined, the clock is sampled only every 32 checkpoints, and throw helpers are non-inlined to keep the steady-state path minimal.Test coverage
DebuggerExpressionLanguageTestswas extended and its snapshots regenerated to reflect the new loop structure (the budget plumbing is sanitized out of the rendered form).