Skip to content

[SDK] Replace SpinLockMutex with std::mutex in the metrics library - #4416

Open
dbarker wants to merge 9 commits into
open-telemetry:mainfrom
dbarker:sdk_swap_spinlock_for_mutex_part_2
Open

[SDK] Replace SpinLockMutex with std::mutex in the metrics library#4416
dbarker wants to merge 9 commits into
open-telemetry:mainfrom
dbarker:sdk_swap_spinlock_for_mutex_part_2

Conversation

@dbarker

@dbarker dbarker commented Aug 11, 2026

Copy link
Copy Markdown
Member

Fixes #4317

Part 2 of #4317 to replace SpinLockMutex with std::mutex.

This PR replaces all use of the SpinLockMutex in the metrics sdk. The goal is to improve synchronization consistency across platforms and deployments by relying on the standard mutex.

Changes

  • Replaces SpinLockMutex with std::mutex in the metrics storage classes.

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

  • CHANGELOG.md updated for non-trivial changes
  • Unit tests have been added
  • Changes in public API reviewed

@codecov

codecov Bot commented Aug 12, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 83.10%. Comparing base (cd252b4) to head (b50241d).

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #4416      +/-   ##
==========================================
+ Coverage   83.09%   83.10%   +0.01%     
==========================================
  Files         519      519              
  Lines       20253    20253              
==========================================
+ Hits        16827    16829       +2     
+ Misses       3426     3424       -2     
Files with missing lines Coverage Δ
...telemetry/sdk/metrics/state/async_metric_storage.h 93.19% <100.00%> (ø)
...ntelemetry/sdk/metrics/state/sync_metric_storage.h 84.94% <ø> (ø)
sdk/src/metrics/state/sync_metric_storage.cc 93.69% <100.00%> (ø)
sdk/src/metrics/state/temporal_metric_storage.cc 100.00% <100.00%> (ø)

... and 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.


private:
mutable opentelemetry::common::SpinLockMutex lock_;
mutable std::mutex lock_;

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I've made the swap to std::mutex here also to be consistent throughout the SDK. This use case for sum aggregation could be a good use for a spinlock but testing showed it was ~10ns difference with the mutex in the uncontested case. This performance delta is not significant compared to the attribute hash map lookup.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

The attribute-map comparison makes sense for the unbound path, but the bound path skips that lookup. It now takes the BoundEntry mutex and then this mutex for every sum update, so the reported 10 ns may matter more there.

Could we capture before-and-after results for the existing bound and unbound counter benchmarks, including a contended case? Since #4317 originally kept this spinlock and requested benchmarks for part 2, I think we should have that evidence before changing this hot path.

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.

This could increase memory usage significantly. Measured with MSVC x64 but it should similar for Linux.

type before (bytes) after (bytes)
LongSumAggregation 40 112
LongLastValueAggregation 48 120
LongHistogramAggregation 136 208

The driver is that sizeof(std::mutex) is 80 bytes on MSVC and 40 on glibc, while sizeof(std::atomic<bool>) is 1. One aggregation object is allocated per time series, and the default cardinality limit is 2000. That alone is 2000 x 72 = ~144 KB extra per counter instrument. TemporalMetricStorage keeps a per-collector stash of previously reported aggregations, so a configuration with one reader roughly doubles it, reaching near 300 KB per instrument.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

@lalitb @ThomsonTan Thanks for pushing on performance and resource usage.

I've run the spinlock/std::mutex swap through the sync instrument recording benchmark from #4470 and pushed the results to a branch on my fork.

The results are as expected:

  • Unbound instruments are largely unaffected by the change (SyncMetricStorage already uses a mutex)
  • Bound instruments gain the expected context switch latency (up to 600 ns) under contention.

Looking into the code this contention in the Bound instrument case seems be at the BoundEntry::lock_ and not the lock at the aggregation level. A scenario I did not test was multiple threads recording to the same stream through bound and unbound instruments.

Benchmark Results:

Unbound sync instrument benchmarks (click to view)
Benchmark Threads SpinLockMutex (ns) std::mutex (ns) Delta
Counter/Drop 1 291 287 -1%
Counter/Drop 2 545 523 -4%
Counter/Drop 4 1222 1224 +0%
Counter/Sum 1 293 290 -1%
Counter/Sum 2 546 541 -1%
Counter/Sum 4 1372 1399 +2%
Histogram/Explicit 1 297 300 +1%
Histogram/Explicit 2 746 683 -8%
Histogram/Explicit 4 1759 1626 -8%
Histogram/Base2Expo 1 300 299 0%
Histogram/Base2Expo 2 588 672 +14%
Histogram/Base2Expo 4 1500 1607 +7%
Gauge/Drop 2 546 665 +22%
Gauge/Drop 4 1200 1375 +15%
Gauge/LastValue 1 293 304 +4%
Gauge/LastValue 2 564 702 +24%
Gauge/LastValue 4 1513 1649 +9%
Bound sync instrument benchmarks (click to view)
Benchmark Threads SpinLockMutex (ns) std::mutex (ns) Delta
BoundCounter/Drop 1 5.81 11.5 +98%
BoundCounter/Drop 2 34.8 68.9 +98%
BoundCounter/Drop 4 123 269 +119%
BoundCounter/Sum 1 10.7 20.9 +95%
BoundCounter/Sum 2 19.6 191 +875%
BoundCounter/Sum 4 73.4 417 +468%
BoundHistogram/Drop 1 6.19 11.8 +91%
BoundHistogram/Drop 2 32.6 70.7 +117%
BoundHistogram/Drop 4 155 239 +54%
BoundHistogram/Explicit 1 11.8 22.8 +93%
BoundHistogram/Explicit 2 23.6 409 +1634%
BoundHistogram/Explicit 4 196 546 +179%
BoundHistogram/Base2Expo 1 19.3 27.7 +44%
BoundHistogram/Base2Expo 2 32.8 485 +1379%
BoundHistogram/Base2Expo 4 67.1 674 +905%

Why swap to std::mutex regardless of the impact on Bound instrument benchmarks?
The SpinLockMutex implementation is problematic for several reasons:

  • It is not complete for all platforms with fast_yield still a TODO.
  • The sleep behavior is not standard for a spinlock and varies significantly across platforms (on Windows this may be 15+ ms).
  • We don't have the systems or tools available to benchmark performance (p95,p99) of SpinLockMutex in the various platforms and deployments of the SDK (on a single core, in a virtual machine, ARM devices, ...) so the impact of the bespoke SpinLockMutex is unknown.
  • std::mutex is optimized on many platforms and will not break down in pathological ways as the SpinLockMutex will under contention or with unexpected extension of critical sections.

Proposed next steps:

I propose we proceed to swap all storage level SpinLockMutex's to std::mutex and only keep SpinLockMutex at the aggregation level to save memory allocation (@ThomsonTan's concern) if they are truly never contended. If never contended then we may consider just removing them from the aggregation level.

Questions:

  • Is the aggregation lock always uncontended and still needed?
  • Is simultaneous recording to a metrics stream through Bound and Unbound instruments tested and supported?
  • Do you have any feedback on the proposed next steps?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

As discussed in SIG meeting, I propose to rollback changes to *_aggregation.h.

Locks for aggregate methods to investigate separately.

@dbarker
dbarker marked this pull request as ready for review August 12, 2026 16:14
@dbarker
dbarker requested a review from a team as a code owner August 12, 2026 16:14
Comment thread sdk/src/metrics/state/sync_metric_storage.cc
@dbarker dbarker added the discuss To discuss in SIG meeting label Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

discuss To discuss in SIG meeting

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Proposal: Replace SpinLockMutex with std::mutex where appropriate

4 participants