Skip to content

Conversation

@markushi
Copy link
Member

@markushi markushi commented Apr 11, 2025

📜 Description

A Baggage can be mutable, but we don't synchronize it's internal keyValues, thus if e.g. Baggage.setTraceId() and Baggage.toHeaderString() are called simultaneously, a ConcurrentModification may be thrown.

SpanContext seems to be holding and creating reference copies of Baggage objects, so multiple spans happening at the same time are suspect to trigger this faulty behavior.

💡 Motivation and Context

Fixes #4321

💚 How did you test it?

📝 Checklist

  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.

🔮 Next steps


@ApiStatus.Internal
public void set(final @NotNull String key, final @Nullable String value) {
set(key, value, false);
Copy link
Member Author

Choose a reason for hiding this comment

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

This was always called with force=false, so I just merged set(key, value, force) and set(key, value).

@github-actions
Copy link
Contributor

github-actions bot commented Apr 11, 2025

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 410.46 ms 456.88 ms 46.42 ms
Size 1.58 MiB 2.08 MiB 505.64 KiB

Previous results on branch: markushi/fix/synchronize-mutable-baggages

Startup times

Revision Plain With Sentry Diff
351306b 426.36 ms 460.65 ms 34.29 ms
c529988 428.26 ms 442.80 ms 14.54 ms
9ae6786 424.54 ms 443.76 ms 19.22 ms
75efa54 401.13 ms 456.45 ms 55.32 ms
2aa945a 429.48 ms 516.48 ms 87.00 ms

App size

Revision Plain With Sentry Diff
351306b 1.58 MiB 2.08 MiB 505.74 KiB
c529988 1.58 MiB 2.08 MiB 505.64 KiB
9ae6786 1.58 MiB 2.08 MiB 505.69 KiB
75efa54 1.58 MiB 2.08 MiB 505.68 KiB
2aa945a 1.58 MiB 2.08 MiB 505.63 KiB

if (value != null) {
final @NotNull String unknownKey = key.replaceFirst(SENTRY_BAGGAGE_PREFIX, "");
unknown.put(unknownKey, value);
synchronized (keyValues) {
Copy link
Member

Choose a reason for hiding this comment

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

We should use AutoClosableReentrantLock over synchronized as there was problems with virtual threads and synchronized before.


final Set<String> keys = new TreeSet<>(keyValues.keySet());
final Set<String> keys;
synchronized (keyValues) {
Copy link
Member

Choose a reason for hiding this comment

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

See below, replace with AutoClosableReentrantLock.

final boolean shouldFreeze,
final @NotNull ILogger logger) {
this.keyValues = keyValues;
// TODO should we deep-copy the keyValues here?
Copy link
Member

Choose a reason for hiding this comment

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

No, we sometimes want to write to a shared baggage.

final @NotNull ILogger logger) {
this.keyValues = keyValues;
// TODO should we deep-copy the keyValues here?
// if so we could optimize this by only synchronizing in case isMutable is true
Copy link
Member

Choose a reason for hiding this comment

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

There are cases where we forcefully overwrite the sample rate, even if mutable = false.

this.keyValues = keyValues;
// TODO should we deep-copy the keyValues here?
// if so we could optimize this by only synchronizing in case isMutable is true
this.keyValues = Collections.synchronizedMap(keyValues);
Copy link
Member

Choose a reason for hiding this comment

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

Instead of wrapping here, which will cause problems with virtual threads again due to using synchronized internally, could we revert this line and instead always use ConcurrentHashMap in place of Map in this class?

final @NotNull Map<String, String> keyValues;
@Nullable Double sampleRate;
@Nullable Double sampleRand;
private final @NotNull Map<String, String> keyValues;
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
private final @NotNull Map<String, String> keyValues;
private final @NotNull ConcurrentHashMap<String, String> keyValues;

we should also change this in all locations of this file, where we have Map as a param or create a new HashMap. e.g. ctors and fromHeader

if (copiedUnknown != null) {
this.unknown = copiedUnknown;
}
// TODO should this be a deep copy instead?
Copy link
Member

Choose a reason for hiding this comment

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

No, we need to share entries.

Copy link
Member

@adinauer adinauer left a comment

Choose a reason for hiding this comment

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

Did not mean to block this PR

@adinauer adinauer self-requested a review April 14, 2025 09:17
Copy link
Member

@adinauer adinauer left a comment

Choose a reason for hiding this comment

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

.

Copy link
Member

@adinauer adinauer left a comment

Choose a reason for hiding this comment

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

LGTM, thanks for fixing!

@markushi markushi enabled auto-merge (squash) April 14, 2025 12:06
@markushi markushi merged commit 34b5305 into main Apr 14, 2025
35 checks passed
@markushi markushi deleted the markushi/fix/synchronize-mutable-baggages branch April 14, 2025 12:29
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.

ConcurrentModificationException after upgrading to Agent 8.6.0 (from 8.0.0)

5 participants