Commit 8d8bc49
authored
fix(profiling): clear finished physical span links (#19529)
## Description
This is the first PR in the span-attribution stack. It fixes stale endpoint and span attribution for physical thread stacks before the later PRs introduce logical asyncio and gevent targets.
### Problem
The stack profiler samples threads independently of application execution, so it keeps a native per-thread copy of the span metadata needed to label profile samples. That copy must follow the lifetime of the tracing context. Otherwise, work that runs after a span finishes can still be labeled with the finished span ID, local-root span ID, and endpoint.
Context activation events are necessary, but they are not sufficient to keep these copies accurate. Consider this sequence:
1. A worker thread activates a span and publishes a native span link.
2. Another thread finishes that span.
3. The worker continues running without performing another context activation.
Finishing the span does not produce an activation event on the worker. Without a separate finish-time cleanup path, its copied link remains stale and subsequent samples are incorrectly attributed to the finished trace.
A local root can also finish before one of its children. In that case the child is still valid and can continue producing profile samples, so cleanup for the root must not remove mappings whose current span is that unfinished child.
### Solution
This PR makes the native `ThreadSpanLinks` store own both directions of each physical association under one mutex:
- thread ID to copied span metadata;
- current span ID to every thread carrying a copy of that exact span.
It then uses two complementary lifecycle signals:
- `ddtrace.context_provider.activate` handles immediate state replacement on the current thread. Activating a span or propagated `Context` publishes its mapping, restoring a parent replaces it, and activating `None` or a `Context` without a span ID clears it.
- `trace.span_finish` performs global reverse invalidation for the exact span that finished. It removes every copied physical-thread mapping for that span, including mappings published on another thread that may never receive another activation. It deliberately preserves mappings for unfinished children when their local root finishes first.
Updating the forward and reverse indexes atomically prevents the two views from diverging. Re-linking a thread removes its previous reverse-index entries first, so a later finish event for an older span cannot erase the thread's newer attribution.
Listener registration occurs only after the collector completes fallible initialization, and both listeners are removed during shutdown. Fork handling reconstructs the native mutex and inherited indexes without traversing potentially inconsistent parent state.
### Review follow-up
Native lifecycle bookkeeping now records in-flight physical span publications before releasing the GIL. If a span finishes while publication is waiting on the native mutex, cleanup is deferred until the final publication completes. This preserves the GIL release introduced in #14852 to avoid thread-pool deadlocks.
## Testing
Added coverage for:
- immediate physical-link clearing on context deactivation;
- finished child-span cleanup across physical threads;
- cross-thread finish cleanup before the linked worker performs another activation;
- preservation of active child links when the local root finishes first;
- reverse-index replacement when a thread advances to a new span;
- nested child-to-parent restoration;
- cleanup on reused thread-pool workers.
Validation completed on the reordered physical-only PR:
- Python 3.13 profiling suite in normal and fast-copy modes: `359 passed, 35 skipped, 2 xfailed` per mode
- Python 3.13 focused cross-thread finish regression: `1 passed` in each mode
- Python 3.11 profiling suite after hardening the reused-worker assertion: `309 passed, 85 skipped, 2 xfailed` in both normal and fast-copy modes
- Reused-worker regression repeated 11 times in both modes: `22 passed`
- Control experiment with the span-finish listener disabled: the cross-thread regression failed in both modes because post-finish samples retained the endpoint, confirming activation alone is insufficient
- `scripts/lint checks`
- targeted style and typing checks
- `scripts/lint cformat`
- `scripts/lint profiling-native-check`
- rebased onto current `main` at `3efdcbe3af`
- `git diff --check`
## Risks
- Physical activation updates perform expected O(1) forward and reverse-index mutations under the existing span-link mutex.
- Finished-span cleanup visits only threads indexed by the exact span that finished.
- The reverse index adds bounded memory proportional to currently linked physical threads.
- The rollback is to disable stack profiling or revert this PR.
## Additional Notes
- Next, common logical stack helpers: #19420
- Gevent integration: #19379
- Asyncio integration: #19346
- Includes a customer-facing profiling fix release note.
Before
<img width="1732" height="905" alt="Screenshot 2026-08-06 at 10 15 34 AM" src="https://github.com/user-attachments/assets/d6a69ad3-1324-4328-b241-c3dc2fdf7cd5" />
After
<img width="1749" height="908" alt="Screenshot 2026-08-06 at 10 15 24 AM" src="https://github.com/user-attachments/assets/cef79a9e-ddc9-4299-b3ff-492126e13ad2" />
Co-authored-by: taegyun.kim <taegyun.kim@datadoghq.com>1 parent df0dc65 commit 8d8bc49
12 files changed
Lines changed: 533 additions & 42 deletions
File tree
- ddtrace
- internal/datadog/profiling/stack
- include
- src
- test
- profiling/collector
- releasenotes/notes
- tests/profiling/collector
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
35 | 35 | | |
36 | 36 | | |
37 | 37 | | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
38 | 44 | | |
39 | 45 | | |
40 | 46 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
80 | 80 | | |
81 | 81 | | |
82 | 82 | | |
| 83 | + | |
83 | 84 | | |
84 | 85 | | |
85 | 86 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
41 | 41 | | |
42 | 42 | | |
43 | 43 | | |
| 44 | + | |
44 | 45 | | |
45 | 46 | | |
46 | 47 | | |
| |||
Lines changed: 26 additions & 2 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
| 9 | + | |
| 10 | + | |
9 | 11 | | |
10 | 12 | | |
11 | 13 | | |
| |||
47 | 49 | | |
48 | 50 | | |
49 | 51 | | |
| 52 | + | |
50 | 53 | | |
51 | 54 | | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
52 | 60 | | |
53 | 61 | | |
54 | 62 | | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
55 | 75 | | |
56 | | - | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
57 | 81 | | |
58 | 82 | | |
59 | 83 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
162 | 162 | | |
163 | 163 | | |
164 | 164 | | |
| 165 | + | |
| 166 | + | |
| 167 | + | |
165 | 168 | | |
166 | | - | |
| 169 | + | |
167 | 170 | | |
168 | 171 | | |
| 172 | + | |
| 173 | + | |
| 174 | + | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
169 | 178 | | |
170 | 179 | | |
171 | 180 | | |
| |||
214 | 223 | | |
215 | 224 | | |
216 | 225 | | |
| 226 | + | |
| 227 | + | |
| 228 | + | |
| 229 | + | |
| 230 | + | |
| 231 | + | |
| 232 | + | |
| 233 | + | |
| 234 | + | |
| 235 | + | |
| 236 | + | |
| 237 | + | |
| 238 | + | |
| 239 | + | |
| 240 | + | |
| 241 | + | |
| 242 | + | |
| 243 | + | |
| 244 | + | |
| 245 | + | |
217 | 246 | | |
218 | 247 | | |
219 | 248 | | |
| |||
1010 | 1039 | | |
1011 | 1040 | | |
1012 | 1041 | | |
| 1042 | + | |
| 1043 | + | |
| 1044 | + | |
| 1045 | + | |
1013 | 1046 | | |
1014 | 1047 | | |
1015 | 1048 | | |
| |||
Lines changed: 89 additions & 26 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
| 7 | + | |
7 | 8 | | |
8 | 9 | | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
9 | 30 | | |
10 | 31 | | |
11 | 32 | | |
12 | 33 | | |
13 | 34 | | |
14 | | - | |
15 | | - | |
16 | | - | |
17 | | - | |
18 | | - | |
19 | | - | |
20 | | - | |
21 | | - | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
22 | 41 | | |
23 | 42 | | |
24 | 43 | | |
25 | 44 | | |
26 | 45 | | |
27 | 46 | | |
28 | 47 | | |
29 | | - | |
30 | 48 | | |
31 | | - | |
32 | | - | |
| 49 | + | |
| 50 | + | |
33 | 51 | | |
34 | | - | |
| 52 | + | |
35 | 53 | | |
36 | 54 | | |
37 | 55 | | |
38 | 56 | | |
39 | 57 | | |
40 | 58 | | |
41 | | - | |
42 | | - | |
| 59 | + | |
43 | 60 | | |
44 | 61 | | |
45 | 62 | | |
| |||
48 | 65 | | |
49 | 66 | | |
50 | 67 | | |
51 | | - | |
52 | | - | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
53 | 81 | | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
54 | 87 | | |
55 | 88 | | |
56 | 89 | | |
57 | 90 | | |
58 | 91 | | |
59 | 92 | | |
60 | 93 | | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
61 | 126 | | |
62 | 127 | | |
63 | 128 | | |
| |||
66 | 131 | | |
67 | 132 | | |
68 | 133 | | |
69 | | - | |
70 | | - | |
71 | | - | |
72 | | - | |
73 | | - | |
74 | | - | |
75 | | - | |
76 | | - | |
77 | | - | |
78 | | - | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
79 | 142 | | |
80 | 143 | | |
81 | 144 | | |
Lines changed: 53 additions & 0 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
100 | 100 | | |
101 | 101 | | |
102 | 102 | | |
| 103 | + | |
| 104 | + | |
| 105 | + | |
| 106 | + | |
| 107 | + | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
| 126 | + | |
| 127 | + | |
| 128 | + | |
| 129 | + | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
| 133 | + | |
| 134 | + | |
| 135 | + | |
| 136 | + | |
| 137 | + | |
| 138 | + | |
| 139 | + | |
| 140 | + | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
| 147 | + | |
| 148 | + | |
| 149 | + | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
103 | 156 | | |
104 | 157 | | |
105 | 158 | | |
| |||
0 commit comments