Skip to content

fix: include Data set via ITransactionTracer in SentryTransaction #4148

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 8 commits into
base: main
Choose a base branch
from

Conversation

Flash0ver
Copy link
Member

@Flash0ver Flash0ver commented Apr 28, 2025

fixes #4140

relates to #3936

Changes

  • remove backing store of SentryTransaction.Data
  • reuse Data of SentryContexts.Trace instead

Added links to discussions from the original PR (if I understood all the context correctly).
Added questions where I am uncertain whether it's the correct way of fixing the issue.

@Flash0ver Flash0ver self-assigned this Apr 28, 2025
@Flash0ver Flash0ver requested a review from Copilot April 28, 2025 09:51
Copy link

@Copilot Copilot AI left a comment

Choose a reason for hiding this comment

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

Copilot encountered an error and was unable to review this pull request. You can try again by re-requesting a review.


/// <inheritdoc />
public void SetData(string key, object? value) => _data[key] = value;
public void SetData(string key, object? value) => _contexts.Trace.SetData(key, value);
Copy link
Member Author

@Flash0ver Flash0ver Apr 28, 2025

Choose a reason for hiding this comment

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

question: concurrency

The now used

private Dictionary<string, object?> _data = new();

currently is a regular Dictionary`2.
Should this now become a ConcurrentDictionary`2?
Because it's also the private readonly ConcurrentDictionary<string, object?> _data = new(); that this PR removes from this type?

Or is that an indicator that this fix is technically not quite right?

Relates to #3936 (comment) and #3936 (comment)

Copy link
Member

Choose a reason for hiding this comment

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

SentryTransaction shouldn't be accessed concurrently since it represents "a point in time" instance that gets serialized to Sentry.

The TransactionTracer is potentially accessed concurrently (we set it onto the Scope which has concurrent access in both global mode (every ConfigureScope call access the same scope instance) or non global mode (since even though there's a single Scope instance per thread, we do access/read things in order to make clones for new threads).

Throwing Concurrent around is not always the solution. In this case for sure not since we want SentryTrasaction to be a snapshot, so we need a deep clone here. We don't want just a clone of the collection if the items in the collection are mutable. I wrote a note above about this.

If things on the context are immutable, things are easier though we could have data loss if we have concurrent updates without synchronization.


/// <inheritdoc />
public IReadOnlyDictionary<string, object?> Data => _data;

public IReadOnlyDictionary<string, object?> Data => _contexts.Trace.Data;
Copy link
Member Author

@Flash0ver Flash0ver Apr 28, 2025

Choose a reason for hiding this comment

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

question: reference Contexts in SentryTransaction vs copy Contexts to SentryTransaction

When creating a SentryTransaction from ITransactionTracer, we reference the SentryContexts:

Contexts = tracer.Contexts;

Should we instead copy the Contexts (via e.g. SentryContexts.Clone)?

Or is that an indicator that this fix here is not quite right, technically?
Should we maybe, rather than have SetData writing into SentryContexts.Trace.Data directly, instead when creating the SentryTransaction from the ITransactionTracer copy the ITransactionTracer.Data (backed by it's own Dictionary`2) over to the Context of the new SentryTransaction.
E.g. something like

class SentryTransaction
{
  public SentryTransaction(ITransactionTracer tracer) : this(tracer.Name, tracer.NameSource)
  {
    Contexts = tracer.Contexts;
    foreach (KeyValuePair<string, object?> data in tracer.Data)
    {
      Contexts.Trace.SetData(data.Key, data.Value);
    }
  }
}

or similar.
But that would have a side-effect on the Contexts of TransactionTracer, which I don't think is expected when creating a new SentryTransaction from TransactionTracer.
Should we perhaps indeed make a copy of the Trace and/or SentryContext?

Relates to #3936 (comment)

Comment on lines +26 to +29
Data: {
http.request.method: GET,
http.response.status_code: 200
}
Copy link
Member Author

Choose a reason for hiding this comment

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

note: from

@@ -244,6 +244,30 @@ public void SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject()

return o;
});

Assert.Contains($$"""
Copy link
Member Author

Choose a reason for hiding this comment

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

note: I kept this test similar to

public async Task SerializeObject_AllPropertiesSetToNonDefault_SerializesValidObject()

which also does a serialize/deserialize-roundtrip test
plus an additional "Contains-JSON" check on the serialized string


/// <inheritdoc />
public IReadOnlyDictionary<string, object?> Data => _data;

public IReadOnlyDictionary<string, object?> Data => _contexts.Trace.Data;
Copy link
Member

Choose a reason for hiding this comment

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

Note that if you're copying all objects from the collection while they are mutable and still on the scope, mutations done on the scope will be reflected on the transaction object resulting in possible inaccurate data being sent to Sentry, or potentially crashes when we read the data for serialization while the data is being mutated


/// <inheritdoc />
public void SetData(string key, object? value) => _data[key] = value;
public void SetData(string key, object? value) => _contexts.Trace.SetData(key, value);
Copy link
Member

Choose a reason for hiding this comment

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

SentryTransaction shouldn't be accessed concurrently since it represents "a point in time" instance that gets serialized to Sentry.

The TransactionTracer is potentially accessed concurrently (we set it onto the Scope which has concurrent access in both global mode (every ConfigureScope call access the same scope instance) or non global mode (since even though there's a single Scope instance per thread, we do access/read things in order to make clones for new threads).

Throwing Concurrent around is not always the solution. In this case for sure not since we want SentryTrasaction to be a snapshot, so we need a deep clone here. We don't want just a clone of the collection if the items in the collection are mutable. I wrote a note above about this.

If things on the context are immutable, things are easier though we could have data loss if we have concurrent updates without synchronization.

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.

TransactionTracer.SetData is not equivalent to TransactionTracer.Contexts.Trace.SetData
2 participants