diff --git a/docfx/examples/IceRpc.RequestContext.Examples/RequestContextInterceptorExamples.cs b/docfx/examples/IceRpc.RequestContext.Examples/RequestContextInterceptorExamples.cs index 48262e7bd..655786f77 100644 --- a/docfx/examples/IceRpc.RequestContext.Examples/RequestContextInterceptorExamples.cs +++ b/docfx/examples/IceRpc.RequestContext.Examples/RequestContextInterceptorExamples.cs @@ -48,4 +48,28 @@ public static async Task UseRequestContext() #endregion } } + + public static void UpdateRequestContextInInterceptor() + { + #region UpdateRequestContextInInterceptor + // An interceptor that adds an entry to the request context. The appropriate pattern is + // copy-on-write: build a new dictionary and install a new feature, rather than mutating the + // existing Value in place (which would race with the outgoing encoding). + _ = new Pipeline() + .Use(next => new InlineInvoker((request, cancellationToken) => + { + IRequestContextFeature? feature = request.Features.Get(); + IDictionary current = + feature?.Value ?? new Dictionary(); + var updated = new Dictionary(current) + { + ["CorrelationId"] = Guid.NewGuid().ToString() + }; + request.Features = request.Features.With( + new RequestContextFeature(updated)); + return next.InvokeAsync(request, cancellationToken); + })) + .UseRequestContext(); + #endregion + } } diff --git a/src/IceRpc/Features/IRequestContextFeature.cs b/src/IceRpc/Features/IRequestContextFeature.cs index a8dff748b..6031971f0 100644 --- a/src/IceRpc/Features/IRequestContextFeature.cs +++ b/src/IceRpc/Features/IRequestContextFeature.cs @@ -4,6 +4,15 @@ namespace IceRpc.Features; /// Represents a feature that holds a of strings. This feature can be /// transmitted as a field with both ice and icerpc. +/// The outgoing request context is encoded lazily, when the request is sent. To avoid racing mutations +/// with encoding, an interceptor that needs to add or remove context entries should build a new dictionary and +/// install a new , rather than mutating in place. +/// +/// +/// The following code shows how to update the request context from an interceptor using copy-on-write. +/// +/// public interface IRequestContextFeature { /// Gets the value of this feature. diff --git a/src/IceRpc/Features/RequestContextFeature.cs b/src/IceRpc/Features/RequestContextFeature.cs index 20bb30bbf..0c038904a 100644 --- a/src/IceRpc/Features/RequestContextFeature.cs +++ b/src/IceRpc/Features/RequestContextFeature.cs @@ -24,8 +24,11 @@ public string this[string key] /// Constructs an empty writeable request context feature. public RequestContextFeature() => Value = new Dictionary(); - /// Constructs a request context feature with the specified dictionary. - /// The dictionary that the new feature will hold. + /// Constructs a request context feature wrapping the specified dictionary. + /// The dictionary that the new feature will hold. The feature stores the reference; it + /// does not copy. The caller must not mutate after constructing the feature, since + /// the request context is encoded lazily when the request is sent. See + /// for the recommended copy-on-write update pattern. public RequestContextFeature(IDictionary dictionary) => Value = dictionary; /// Adds a new entry to .