dotnet / dotnet/orleans

Preserving CurrentGrainReference inside async telemetry wrappers in Orleans 9.1.2 (.NET 9)

Open
#9,709 0 comments 1 reaction 0 assignees View on GitHub
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.