Skip to content

fix(ipc)!: fix windows bugs; support optional handles - #2567

Merged
gh-worker-dd-mergequeue-cf854d[bot] merged 6 commits into
mainfrom
glopes/ipc-changes
Sep 28, 2026
Merged

gh-worker-dd-mergequeue-cf854d[bot] merged 6 commits into
mainfrom
glopes/ipc-changes

Conversation

@cataphract

Copy link
Copy Markdown
Contributor

What does this PR do?

  1. Fix a verified hang on Windows when sending data to a full pipe. The previous code used the legacy PIPE_NOWAIT mode to simulate unix-style nonblocking I/O to (unsuccessfully) try to achieve this. Use instead PIPE_WAIT and overlapped I/O (the way the pipe is already opened) and, in a somewhat hacky fashion, to simulate Unix behavior, cancel pending requests when the operation returns ERROR_IO_PENDING.
  2. Avoid leaking duplicated handles when there is a failure transmitting them to the other process.
  3. ipc protocol improvement: support optional handles in messages

Motivation

See DataDog/dd-trace-php#4219

try_send used non-overlapped WriteFile on a pipe in PIPE_WAIT.
When a busy sidecar filled the pipe, the call could block the
instrumented process instead of reporting backpressure. Temporarily
selecting PIPE_NOWAIT avoided that block, but a full message pipe may
report a successful write with zero bytes and silently lose the
request.

Keep the pipe in PIPE_WAIT and open the client endpoint for
overlapped I/O, matching the server endpoint. Route every ReadFile and
WriteFile operation, including the PID handshake, through one helper.

Blocking calls wait for completion. Nonblocking calls cancel a
pending request and wait only for its final status. Completion that
wins the cancellation race remains successful; confirmed cancellation
becomes WouldBlock. Waiting for a final status also keeps the
OVERLAPPED structure and borrowed buffers valid until Windows has
released them.

Require the complete byte count so success is authoritative about
whether the message entered the pipe. Add a regression test that fills
a pipe and verifies try_send returns WouldBlock instead of hanging.
Windows handle transfer duplicates each source handle into the receiver
before writing its numeric value into the pipe message. If a later
duplication or the write fails, the receiver never sees the message and
cannot close those duplicates.

Track receiver-owned duplicates until the complete message write
succeeds. On failure, move each duplicate back with
DUPLICATE_CLOSE_SOURCE and close the local copy. On success, commit the
guard so ownership remains with the receiver.

Keep truncating the temporary handle suffix on every return so an
unsent request can be retried from its original bytes, and reject handle
counts that do not fit in the wire format.

Also take ownership of a connected pipe before fallible setup and
report failures to select message read mode instead of leaking the raw
handle or continuing with an invalid connection.
The service macro treated every SerializedHandle parameter as a
concrete value convertible into one PlatformHandle. That prevents a
Windows-only notification target from being optional in the shared
service protocol.

Delegate handle copying and receiving to TransferHandles on the
complete marked value. Option forwards those operations for Some and
consumes no handles for None, while wrapper types can delegate to their
contained handle-bearing value.

Preserve cfg attributes in the generated match fields and transfer
statements so a platform-specific handle parameter is omitted together
with its field on other targets. Require marked parameter types to
implement TransferHandles explicitly.
@cataphract
cataphract requested review from a team as code owners September 23, 2026 15:19
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 23, 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-09-23T15:24:34.334825Z 93402d2 PR opened
ℹ️ 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.

@pr-commenter

pr-commenter Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Benchmarks

Comparison

Benchmark execution time: 2026-09-28 15:44:12

Comparing candidate commit aaf3c80 in PR branch glopes/ipc-changes with baseline commit 7028079 in branch main.

📊 Benchmarking dashboard

Found 0 performance improvements and 0 performance regressions! Performance is the same for 2 metrics, 0 unstable metrics.

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 ----------------------------------'

Candidate

Candidate benchmark details

Group 1

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz aaf3c80 1790609694 glopes/ipc-changes
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
two way interface execution_time 15.228µs 15.533µs ± 0.266µs 15.501µs ± 0.078µs 15.577µs 15.787µs 16.076µs 18.571µs 19.81% 7.769 83.767 1.71% 0.019µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
two way interface execution_time [15.496µs; 15.570µs] or [-0.237%; +0.237%] None None None

Group 2

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz aaf3c80 1790609694 glopes/ipc-changes
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
write only interface execution_time 1.066µs 1.079µs ± 0.005µs 1.079µs ± 0.002µs 1.081µs 1.085µs 1.089µs 1.098µs 1.75% -0.130 2.048 0.42% 0.000µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
write only interface execution_time [1.078µs; 1.079µs] or [-0.059%; +0.059%] None None None

Baseline

Baseline benchmark details

Group 1

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 7028079 1790605004 main
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
two way interface execution_time 15.402µs 15.797µs ± 0.248µs 15.770µs ± 0.101µs 15.870µs 16.127µs 16.256µs 18.346µs 16.33% 5.618 53.939 1.57% 0.018µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
two way interface execution_time [15.762µs; 15.831µs] or [-0.218%; +0.218%] None None None

Group 2

cpu_model git_commit_sha git_commit_date git_branch
Intel(R) Xeon(R) Platinum 8259CL CPU @ 2.50GHz 7028079 1790605004 main
scenario metric min mean ± sd median ± mad p75 p95 p99 max peak_to_median_ratio skewness kurtosis cv sem runs sample_size
write only interface execution_time 1.066µs 1.084µs ± 0.005µs 1.085µs ± 0.003µs 1.088µs 1.091µs 1.096µs 1.097µs 1.05% -0.670 0.730 0.48% 0.000µs 1 200
scenario metric 95% CI mean Shapiro-Wilk pvalue Ljung-Box pvalue (lag=1) Dip test pvalue
write only interface execution_time [1.084µs; 1.085µs] or [-0.067%; +0.067%] None None None

@github-actions

github-actions Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Clippy Allow Annotation Report

Tracked Clippy allow annotations changed vs main: ⚠️ +4 (4 → 8)

Rule Base PR Δ
expect_used 1 6 ⚠️ +5
todo 1 0 ✅ -1
By file and crate

By file

File Base PR Δ
libdd-ipc/src/codec.rs 1 4 ⚠️ +3
libdd-ipc/src/handles.rs 1 0 ✅ -1
libdd-ipc/src/platform/windows/sockets/writer.rs 0 2 ⚠️ +2

By crate

Crate Base PR Δ
libdd-ipc 21 25 ⚠️ +4

About This Report

This report tracks Clippy allow annotations for specific rules, showing how they've changed in this PR. Decreasing the number of these annotations generally improves code quality. Panic-inducing macros in particular should be avoided. In the future, this report may become a PR-blocking quality gate.

@datadog-prod-us1-5

datadog-prod-us1-5 Bot commented Sep 23, 2026 •

Copy link
Copy Markdown

Tests

✅ All CI checks and tests passed.

🎉 All green!

🧪 All tests passed
❄️ No new flaky tests detected

🎯 Code Coverage (details)
• Patch Coverage: 96.36%
• Overall Coverage: 78.65% (+0.02%)

This comment will be updated automatically if new data arrives.
🔗 Commit SHA: aaf3c80 | Docs | View more details | Give us feedback!

@dd-octo-sts

dd-octo-sts Bot commented Sep 23, 2026 •

Copy link
Copy Markdown
Contributor

Artifact Size Benchmark Report

aarch64-alpine-linux-musl
Artifact Baseline Commit Change
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.a 95.93 MB 95.93 MB 0% (0 B) 👌
/aarch64-alpine-linux-musl/lib/libdatadog_profiling.so 9.02 MB 9.02 MB 0% (0 B) 👌
aarch64-unknown-linux-gnu
Artifact Baseline Commit Change
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.so 12.19 MB 12.19 MB 0% (0 B) 👌
/aarch64-unknown-linux-gnu/lib/libdatadog_profiling.a 107.31 MB 107.31 MB 0% (0 B) 👌
libdatadog-x64-windows
Artifact Baseline Commit Change
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.dll 29.05 MB 29.05 MB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.lib 96.08 KB 96.08 KB 0% (0 B) 👌
/libdatadog-x64-windows/debug/dynamic/datadog_profiling_ffi.pdb 191.41 MB 191.41 MB 0% (0 B) 👌
/libdatadog-x64-windows/debug/static/datadog_profiling_ffi.lib 816.30 MB 816.30 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.dll 9.70 MB 9.70 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.lib 96.08 KB 96.08 KB 0% (0 B) 👌
/libdatadog-x64-windows/release/dynamic/datadog_profiling_ffi.pdb 27.49 MB 27.49 MB 0% (0 B) 👌
/libdatadog-x64-windows/release/static/datadog_profiling_ffi.lib 55.57 MB 55.57 MB 0% (0 B) 👌
libdatadog-x86-windows
Artifact Baseline Commit Change
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.dll 25.41 MB 25.41 MB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.lib 97.58 KB 97.58 KB 0% (0 B) 👌
/libdatadog-x86-windows/debug/dynamic/datadog_profiling_ffi.pdb 196.64 MB 196.64 MB 0% (0 B) 👌
/libdatadog-x86-windows/debug/static/datadog_profiling_ffi.lib 802.94 MB 802.94 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.dll 7.52 MB 7.52 MB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.lib 97.58 KB 97.58 KB 0% (0 B) 👌
/libdatadog-x86-windows/release/dynamic/datadog_profiling_ffi.pdb 29.59 MB 29.60 MB +.02% (+8.00 KB) 🔍
/libdatadog-x86-windows/release/static/datadog_profiling_ffi.lib 52.52 MB 52.52 MB 0% (0 B) 👌
x86_64-alpine-linux-musl
Artifact Baseline Commit Change
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.a 85.92 MB 85.92 MB 0% (0 B) 👌
/x86_64-alpine-linux-musl/lib/libdatadog_profiling.so 10.04 MB 10.04 MB 0% (0 B) 👌
x86_64-unknown-linux-gnu
Artifact Baseline Commit Change
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.a 101.79 MB 101.79 MB 0% (0 B) 👌
/x86_64-unknown-linux-gnu/lib/libdatadog_profiling.so 12.26 MB 12.26 MB 0% (0 B) 👌

@bwoebi bwoebi left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

I'll trust you that this is correct.
It seems to still work, but I'm not an expert to the degree here that I can tell that the specific write operations are correct.

The important invariant is that it's still non-blocking and the overlapped IO guarantees that.

@cataphract

Copy link
Copy Markdown
Contributor Author

The only questionable thing here, as I signaled in the description, is the i/o cancellation. I still need to run stress tests on wine and windows.

@cataphract

cataphract commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

@bwoebi Unfortunately, while this works fine on Wine, the same is not true for windows. It's possible, even though unlikely (in my test it happened 8 times in 400k writes), that:

  • WriteFile return ERROR_IO_PENDING,
  • CancelIoEx return success,
  • GetOverlappedResult return ERROR_OPERATION_ABORTED, zero bytes, and simultaneously:
  • the receiver nevertheless receive and validate the complete message.

I'll looking for alternatives

EDIT, further test:

Parameter Value
Pipe quota 4,096 bytes
Message size 3,936 bytes
Buffered capacity Exactly one message
Writer cadence Tight loop
Reader behavior Deliberately stalled periodically
500,000 attempts
    9,224 reported successful
  490,776 reported ERROR_OPERATION_ABORTED
        6 aborted writes were nevertheless delivered completely

so around 0.001% of the "aborts" actually completed

@cataphract

cataphract commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

@bwoebi OK, these my tentative interim conclusions, focused on the client code (the server operations have their own problems, like parking one thread per idle client, but let's set that aside):

  • The previous code has more bugs than merely the one for which I provided a regression test. The PIPE_WAIT -> NOWAIT transition cannot be done except under specific conditions -- fails with ERROR_PIPE_BUSY when that endpoint has a pending read, a pending write, or unread outbound data. We're currently ignoring this error.
  • We could change permanently the client handle to NOWAIT and go with it. But as it stands, it presents a problem for the blocking calls, as win32 has no poll/select mechanism to wait for pipe readiness. WaitNamedPipe and PeekNamedPipe are not adequate.
  • The Windows kernel DOES have such mechanism, and it's used in OpenVMM by Microsoft itself, so it might be the way forward. Wine doesn't support it though.
  • The more "natural" way of solving the problem we have PIPE_WAIT + overlapped would involve significant work, but might also be the way forward.
  • Separately, the method of transmitting handles is prone to leaking. The thing is that if we can confirm that data was accepted into the pipe, we have no way of ascertaining whether the server actually read the data and took ownership the handle. The ACK is enough because the server may have taken ownership of the handle and yet the ACK didn't go through. Making this safe require duplicating the handle on the client side rather than the server side.

@cataphract
cataphract requested a review from a team as a code owner September 28, 2026 01:36
CancelIoEx cannot safely turn a pending named-pipe write into
Unix-style WouldBlock. NPFS may expose the complete buffer to the peer
before cancellation claims completion. It can then report
ERROR_OPERATION_ABORTED even though the message was delivered. Retrying
can duplicate messages and transferred handles.

Give each Windows connection one thread-pool I/O-backed write slot.
Keep the encoded buffer, OVERLAPPED state, and handle-transfer state
alive until terminal completion. Retire immediate writes inline and
latch asynchronous errors. Blocking senders wait for the slot or
completion; nonblocking senders reject before submission. Teardown
cancels pending I/O and quiesces callbacks before closing the pipe.

Move encoded Vec ownership through the connection APIs so the pending
slot can retain the caller allocation. Generated IPC encoding reserves
the exact Windows handle suffix, avoiding allocation when metadata is
appended. Keep read cancellation separate because NPFS read
cancellation is rollback-safe.

Cover repeated backpressure, ordered delivery, concurrent blocked
reads and writes, cancellation, peer closure, allocation ownership,
and teardown on native Windows.
Creating and closing an event for every overlapped read adds
avoidable kernel-handle churn, especially under Wine.

Keep one completed event per connection and claim or return it through a
pointer-sized atomic slot. Concurrent readers use independent events
instead of sharing or serializing (there's not much use for concurrent
blocking readers, but kept for correctness/completeness -- in practice,
the cached event will always be used).

Refactor the reader to a dedicated module.
@cataphract

cataphract commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

Keep using PIPE_WAIT, but, on the write path, instead of a cancellation request if the message is not immediately accepted, leave the write queued to pipe and return success. We allow only ONE such queued requests, and return WouldBlock if a write is requested while than one pending request has not yet completed.

This effectively enlarges the pipe for writes, allowing one more message beyond its capacity.

The other path I considered, the undocumented FSCTL_PIPE_EVENT_SELECT is actually OK-ish (I had Opus implement it and took a look at the result), however I chose not go that path for two reasons:

  • No support in Wine, which would have to sleep/try repeatedly
  • More importantly, the server side uses PIPE_WAIT + overlapped I/O. So while in this implementation both sides are in PIPE_WAIT + overlapped, in that other we'd be operating in different modes in server and client, which makes the respective code look substantially different.

@cataphract

cataphract commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-09-28 14:06:33 UTC ℹ️ Start processing command /merge


2026-09-28 14:06:40 UTC ℹ️ MergeQueue: Pull request is not mergeable yet

It will be processed automatically as soon as GitHub reports it as mergeable. View in MergeQueue UI.

  • Run /code blockers to see what is blocking it.
  • Run /remove to cancel it.

2026-09-28 14:49:00 UTC ⚠️ MergeQueue: This merge request was unqueued

gustavo.lopes@datadoghq.com unqueued this merge request

@cataphract
cataphract removed the request for review from a team September 28, 2026 14:22
@gyuheon0h
gyuheon0h requested a review from yannham September 28, 2026 14:42
@cataphract

cataphract commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor Author

/merge -c

pending review

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-09-28 14:48:53 UTC ℹ️ Start processing command /merge -c

@yannham yannham left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

A lot of this is out of my expertise tbh, so I've only done a bird eye's review. I'll trust sidecar people for the technical details.

Comment thread libdd-ipc/src/platform/windows/sockets/reader.rs Outdated
Comment thread libdd-ipc/src/platform/windows/sockets/reader.rs
@cataphract

Copy link
Copy Markdown
Contributor Author

/merge

@gh-worker-devflow-routing-ef8351

gh-worker-devflow-routing-ef8351 Bot commented Sep 28, 2026 •

Copy link
Copy Markdown

View all feedbacks in Devflow UI.

2026-09-28 15:36:37 UTC ℹ️ Start processing command /merge


2026-09-28 15:36:44 UTC ℹ️ MergeQueue: Pull request is not mergeable yet

It will be processed automatically as soon as GitHub reports it as mergeable. View in MergeQueue UI.

  • Run /code blockers to see what is blocking it.
  • Run /remove to cancel it.

2026-09-28 16:08:11 UTC ℹ️ MergeQueue: merge request added to the queue

The expected merge time in main is approximately 48m (p90).


2026-09-28 16:41:05 UTC ℹ️ MergeQueue: This merge request was merged

@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot merged commit e0ad8ac into main Sep 28, 2026
136 checks passed
@gh-worker-dd-mergequeue-cf854d
gh-worker-dd-mergequeue-cf854d Bot deleted the glopes/ipc-changes branch September 28, 2026 16:40
gh-worker-dd-mergequeue-cf854d Bot pushed a commit that referenced this pull request Sep 29, 2026
# What does this PR do?

Replace the Windows remote-config notification mechanism that called
`CreateRemoteThread` in the client process with a client-owned auto-reset event
and Windows thread-pool wait.

The event handle is transferred to the sidecar over IPC. The sidecar signals
the event when configuration may have changed, and the client callback runs on
the process thread pool. Dropping the registration disarms the wait, cancels
callbacks that have not started, and drains any callback already running.

A stable notification ID lets the sidecar deduplicate repeated transfers of
the same event even when they have different sidecar-local `HANDLE` values.

# Motivation

The previous sidecar implementation passed an extension DLL entry point to
`CreateRemoteThread`. A notification racing PHP module shutdown could execute
after the DLL was unloaded. The Windows VM Red scenario reproduced this as an
immediate `c0000005` access violation in `<Unloaded_php_ddtrace.dll>`.

The new client-owned registration makes module shutdown independent of sidecar
work while ensuring that callback code and state are no longer in use before
the DLL is unloaded. It also avoids creating a remote thread for every
notification.

# Additional Notes

This PR is stacked on #2567 and uses its optional-handle IPC support. Review the
single commit on top of `glopes/ipc-changes`. It should be retargeted to `main`
after #2567 merges.

# How to test the change?

## Windows Red/Green integration validation

I prepared an adopting [dd-trace-php checkout](DataDog/dd-trace-php@3df08d9) which:

- creates one process-wide notification registration during PHP MINIT;
- gives the registration to `ddog_sidecar_session_set_config` for each sidecar
  connection; and
- drops the registration during PHP MSHUTDOWN, before the extension DLL is
  unloaded.

I built that extension on the Windows Server 2019 VM with MSVC. Each stress
iteration launches a short-lived PHP CLI process which starts tracing, asks a
test agent to publish an APM_TRACING configuration update, and waits for the
sidecar's next `/v0.7/config` poll. The test agent records that poll, and the PHP
script prints the `client.client_tracer.process_tags` sent in its request body.
Adding the configuration gives the sidecar an update to notify the extension
about; terminating the process immediately afterwards exercises that
notification against extension shutdown and DLL unload.

The harness runs those PHP processes under CDB with a subprocess sidecar and a
100 ms remote-config polling interval. Every iteration must both exit
successfully and print `runtime.sapi:cli` among the recorded process tags. The
tag assertion proves that the sidecar made the remote-config request; a clean
process exit without exercising remote config would not pass. CDB was
configured to catch access violations and failures in unloaded module code.

- **Red:** with the pre-fix libdatadog implementation, the same workload failed
  on its first iteration with `c0000005` in
  `<Unloaded_php_ddtrace.dll>+0x1118030`. This reproduces the shutdown race: a
  sidecar-created remote thread was still executing an extension entry point
  after Windows had unloaded the extension DLL.
- **Green:** with this PR and the adopting caller, all 321/321 iterations exited
  successfully and produced the expected tag. CDB reported no access violation,
  execution in `<Unloaded_php_ddtrace.dll>`, or other captured exception.

Finally, I ran one process with CDB breakpoints on dd-trace-php's remote-config
MSHUTDOWN function and on
`ddog_sidecar_remote_config_notification_drop`. Both breakpoints fired, in that
order, before the extension unloaded, and shutdown completed without an
exception. This verifies that the adopting caller invokes the draining drop
path at the point whose lifetime the fix is intended to protect.


Co-authored-by: gustavo.lopes <gustavo.lopes@datadoghq.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants