[API] SpinLockMutex contention stalls a full timer quantum (~15.6 ms) on Windows - #4245
[API] SpinLockMutex contention stalls a full timer quantum (~15.6 ms) on Windows#4245abhinandan-codes wants to merge 2 commits into
Conversation
|
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #4245 +/- ##
==========================================
- Coverage 81.15% 81.07% -0.07%
==========================================
Files 446 446
Lines 18922 18922
==========================================
- Hits 15355 15340 -15
- Misses 3567 3582 +15
🚀 New features to boost your workflow:
|
dbarker
left a comment
There was a problem hiding this comment.
Thanks for the PR and for pointing out the 1ms sleep issue. Please see some initial feedback and question below.
marcalff
left a comment
There was a problem hiding this comment.
Thanks for the patch.
As described by @dbarker, this change breaks the ABI: it does not "backs" the spin lock with a read write lock, it just replaces the spin lock by a read write lock instead.
Given how multiple libraries can be compiled with different versions of this header file, and how each instrumentation will need a lock to use tracer / meter / logger providers, this is a major issue.
A better way would be to change the implementation of the lock() method alone, when waiting for the spin lock to be available.
|
SRWLocks are not re-entrant - not sure if we fall into situation where this might happen. This also calls for AppVerif test on Windows through |
|
Context: reworking this PR from the SRWLOCK approach to a parking back-off, and a question for the SDK team on which direction you'd prefer. Option 1 — park the waiter in
The Option 2 — an SDK-local, CMake-selectable lock for the metrics hot path. Separately wanted to know your testing infrastructure setup as option 1 may need testing it across C++ versions and platforms Which path would you prefer — parking the API |
|
@abhinandan-codes - The Windows issue is real, and replacing the 1 ms sleep with parking is a good idea. My concern is that the measured problem is in the metrics hot path, but this PR changes the shared Because this lock is defined in a header, There is also a concrete compatibility issue: a C++20 build targeting macOS 10.15 can select The change also performs a notification on every unlock, including uncontended paths outside metrics, but the PR only provides measurements for the Windows metrics case. There are no correctness tests for multiple waiters or repeated lock handoff either. I suggest keeping Overall: the problem and general direction are good, but I think the current implementation is too broad and has compatibility risks. I’m requesting changes and would prefer the SDK-internal metrics approach. |
Thanks @lalitb for the in depth analysis. I agree with lalit here, using a different lock instead of a spin lock where there is contention for metrics is less intrusive compared to changing the spin lock implementation itself for every uses. A spin lock is supposed to be held for a very short time, not causing contention. If there is contention, it could be because the lock is held for too long, so that a spin lock is not appropriate in the user case found with metrics. |
I think we can scope it down to a single member. The measured hot path is entirely behind one lock: All four |
I agree, the metrics recording path should use a std::mutex. The complexity of the base2 histogram aggregation and last value aggregation (it has a system call to read the clock) may trigger the 1ms sleep of the spinlock. Please take a look at #4317 which documents the current SpinLockMutex usage and proposes which cases to switch to std::mutex. |
…pen-telemetry#4245) On Windows the SpinLockMutex final back-off sleep_for(1ms) is rounded up to the ~15.6 ms timer quantum, so a waiter contending on a hot instrument stalls a full quantum and inflates tail latency. Switch only SyncMetricStorage::attribute_hashmap_lock_ to std::mutex; the API SpinLockMutex and all other SDK locks are unchanged.
a328718 to
029bc50
Compare
|
Thanks for your work and patience on this issue. Please check CI (format, IWYU) and merge conflicts |
lalitb
left a comment
There was a problem hiding this comment.
LGTM, Thanks @abhinandan-codes. Check the Ci, and also rebase to resolve the merge conflict.
dbarker
left a comment
There was a problem hiding this comment.
Thanks again for highlighting this issue! Switching to std::mutex in the sync metric storage (and elsewhere in the SDK) is necessary for synchronization consistency on all platforms and deployments.
Problem
On Windows,
SpinLockMutex::lock()falls through tostd::this_thread::sleep_for(std::chrono::milliseconds(1))as its finalback-off step. Windows rounds sleep durations up to the current system timer
resolution, which defaults to ~15.6 ms. So a thread that loses the lock race
does not sleep ~1 ms — it parks for a full timer quantum (~15.6 ms).
Under sustained contention on a single instrument (for example a hot histogram
recorded from many threads via
SyncMetricStorage, which holds a per-streamSpinLockMutex), this produces a large tail latency: the medianRecord()is afew microseconds but p99 jumps to ~15 ms.
Root cause
sleep_for(1ms)is not 1 ms on Windows. The intended sub-millisecondincremental back-off becomes a ~15.6 ms stall whenever a waiter reaches the
sleep phase.
Fix
Exploring options — see comment below for the two approaches and benchmarks; open to maintainer preference.
Measurements
Windows 11, 6 threads hammering one histogram at ~2000 rec/s, default 15.6 ms
timer:
sleep_for)Notes
surface).
timeBeginPeriod(1)mitigates the symptom globally but is a process-wide sideeffect, not a library-level fix.