Skip to content

[TEST] Time only the quit in ElegantQuitQuick, and bound it where the two outcomes are - #4488

Draft
thc1006 wants to merge 1 commit into
open-telemetry:mainfrom
thc1006:bugfix/elegant-quit-quick-4265
Draft

[TEST] Time only the quit in ElegantQuitQuick, and bound it where the two outcomes are#4488
thc1006 wants to merge 1 commit into
open-telemetry:mainfrom
thc1006:bugfix/elegant-quit-quick-4265

Conversation

@thc1006

@thc1006 thc1006 commented Aug 26, 2026

Copy link
Copy Markdown
Member

Fixes #4265.

The two failures reported on that issue are not the same failure, and only one of them is the test's fault.

What the case is measuring

ElegantQuitQuick destroys the client while its background thread is polling, and checks that the destructor woke the thread rather than letting it sleep out scheduled_delay_milliseconds_, which is 256 ms. A missed wakeup makes the quit slow rather than wrong, so a duration is the only thing there is to assert.

I measured both outcomes on this branch before changing anything. 24 runs of each, same binary, idle host:

runs under 20 ms 20 to 100 ms over 100 ms
as it is 24 24 0 0
with curl_multi_wakeup removed 24 5 0 19

The working quit costs 0.1 to 0.5 ms. A quit that waited the poll out costs 256.5 to 256.9 ms. Nothing lands between them.

Why 20 ms is the wrong bound and 100 ms is not just a larger one

The bound sat 20 ms above an operation that takes half a millisecond. A host that adds scheduling delay to that operation trips it while the wakeup is working perfectly. That is the cost ms: 21 in the issue.

cost ms: 247 is the other one. It matches the measured missed wakeup exactly, so the case was right that time, and disabling it would drop a signal it has already produced once.

100 ms was chosen from the gap rather than by doubling. Both bounds partition those 48 measurements identically, so the wider one gives up no detection while removing the class of false alarm that 20 ms invites. I would rather not raise it further: 256 ms is what it has to stay under.

What else changed

The timed window used to open before FinishAllSessions() with the request still going, after a fixed sleep_for(10ms) that was standing in for the request completing. The window now covers the quit alone, and the request is finished first through waitForRequests(), which this file already uses for the same purpose in SendGetRequest. That removes the sleep as well.

The two response assertions moved above the window, where they now guard it rather than trail it.

Checks

curl_http_test passes 26 of 26. The case passes 30 of 30 idle and 30 of 30 with 64 busy loops on a 32 core host, which is the load that makes the old bound interesting.

Removing curl_multi_wakeup still turns the case red, at cost ms: 256. It is worth saying that this catches it 19 times in 24 rather than every time: with the wakeup gone, the poll is sometimes broken by the cancelled transfer instead. That was true of the old bound too, and it is why the failure on CI is intermittent rather than constant.

One thing this does not fix

It does not explain why the wakeup was missed on the run that produced 247 ms. wakeupBackgroundThread() is called more than once on the destruction path, and libcurl documents that "multiple calls to this function wake up the same waiting operation", so two wakeups collapsing into one while two poll iterations remain would produce exactly this. That is a hypothesis, not a measurement, and it belongs to the transport work in #4448 rather than to a test fix.

For significant contributions please make sure you have completed the following items:

  • CHANGELOG.md updated for non-trivial changes - N/A, test only, no behaviour change
  • Unit tests have been added
  • Changes in public API reviewed - N/A, no API change

… two outcomes are

The case exists to catch a destruction that lets the polling background thread
sleep out scheduled_delay_milliseconds_ instead of waking it. Measured on an idle
host, that quit costs 0.1 to 0.5 ms; measured with the wakeup removed, it costs
256.5 to 256.9 ms, and there is nothing in between. The bound sat at 20 ms, which
is close enough to the fast outcome that a host adding scheduling delay to a sub
millisecond operation trips it. The 21 ms failure reported on open-telemetry#4265 is that, and
the 247 ms one is a quit that really did wait the poll out.

100 ms partitions the same two sets: over 24 runs of each arm, no measurement
landed between 20 and 100 ms, so the wider bound gives up no detection.

The window now covers the quit alone. The request is finished first through the
file's own waitForRequests(), which also removes the fixed 10 ms sleep that was
standing in for it.

Signed-off-by: thc1006 <84045975+thc1006@users.noreply.github.com>
@codecov

codecov Bot commented Aug 26, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.12%. Comparing base (06423fb) to head (3168ce7).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4488      +/-   ##
==========================================
- Coverage   83.13%   83.12%   -0.00%     
==========================================
  Files         519      519              
  Lines       20256    20256              
==========================================
- Hits        16837    16835       -2     
- Misses       3419     3421       +2     

see 1 file with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[TEST] Fix ext.http.curl.BasicCurlHttpTests.ElegantQuitQuick flaky test

1 participant