Preserving CurrentGrainReference inside async telemetry wrappers in Orleans 9.1.2 (.NET 9)
- Dominant language
- C#
- Stars
- 10.9k
- Forks
- 2.1k
- Avg merge
- 13h 56m
- Merged PRs (30d)
- 351
Description
Hi Team,
We are using Orleans SDK 9.1.2 on .NET 9 and implementing observability via a generic WithSpanAsync telemetry wrapper (OpenTelemetry). This wrapper wraps arbitrary grain code to capture spans and record attributes.
Issue:
- Grain service client calls inside the wrapper fail with NullReferenceException because CurrentGrainReference is null.
- Wrapping async grain code in the telemetry span seems to lose the Orleans grain context, so calls like GetGrainService(CurrentGrainReference.GrainId) break.
``` C#
public class GrainServiceClient(IServiceProvider serviceProvider) : GrainServiceClient(serviceProvider), IServiceClient
{
// For convenience when implementing methods, you can define a property which gets the relevant Service
private IPayPacketOutboxService GrainService => GetGrainService(CurrentGrainReference.GrainId);
}
```
```C#
public async Task ProcessBatchAsync(string shard, CancellationToken cancellationToken)
=> await tracer.WithSpanAsync(
async (span, ct) =>
{
span.SetAttribute(ObservabilityAttribute.Operation, "batch.process");
IList batchIds = await payPacketOutboxServiceClient.GetPendingPayPacketOutboxBatchIdsAsync(shard, ct).ConfigureAwait(false);
},
SpanKind.Internal,
cancellationToken: cancellationToken);
```
We have tried:
- Using Task.Factory.StartNew inside the wrapper
- Capturing the grain ID manually and passing it
- Using IGrainContextAccessor.GrainContext
Goal:
- Wrap any grain method in a telemetry span (async/await)
- Preserve CurrentGrainReference automatically
- Avoid manually passing GrainId or context everywhere
Questions:
- In Orleans 9.1.2, what is the recommended pattern for preserving grain context across async wrappers like telemetry spans?
- How can I wrap grain code for observability without breaking grain service client calls?
Any guidance or examples would be greatly appreciated.
Thanks!
Contributor guide
Research direction
Start with the WithSpanAsync wrapper and the CurrentGrainReference access in GrainServiceClient, then compare behavior with IGrainContextAccessor.GrainContext. Reproduce ProcessBatchAsync on Orleans 9.1.2 and .NET 9 to determine whether the async wrapper preserves grain context; done requires a documented supported pattern or a confirmed framework fix that avoids manual GrainId passing.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- distributed-systems
- Issue type
- Bug
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Stale
- Clarity
- Needs clarification
- Newbie friendliness
- 25/100