Skip to content

Document the no-copy contract on RequestContextFeature#4542

Open
pepone wants to merge 2 commits intoicerpc:mainfrom
pepone:snapshot-request-context
Open

Document the no-copy contract on RequestContextFeature#4542
pepone wants to merge 2 commits intoicerpc:mainfrom
pepone:snapshot-request-context

Conversation

@pepone
Copy link
Copy Markdown
Member

@pepone pepone commented Apr 23, 2026

The outgoing request context is encoded lazily, when the request is sent. If the caller continues mutating the dictionary it handed to RequestContextFeature after install, those mutations can race with the encoding and change what gets written on the wire (#4454).

Original approach was to snapshot the dictionary in the constructor. Per review (the snapshot would also drop a custom key comparer if one was supplied, and forces an allocation on every construction), pivot to document the no-copy contract instead and put the snapshot responsibility on the caller:

  • Tighten the RequestContextFeature(IDictionary) doc comment to state that the feature stores the reference (no copy), and the caller must not mutate the dictionary after handing it over.
  • Add <remarks> and a copy-on-write <example> on IRequestContextFeature showing how an interceptor that needs to add or remove entries should build a fresh dictionary and install a new feature, rather than mutating Value in place.

Fixes #4454

The outgoing request context is encoded lazily, when the request is sent.
If the feature shared the caller's dictionary reference, a concurrent
mutation of that dictionary (e.g. a loop that reuses the same dictionary
across requests) could change what gets written on the wire.

Have the constructor take a snapshot, and document the copy-on-write
pattern for interceptors that need to add or remove context entries.

Fixes icerpc#4454
Copilot AI review requested due to automatic review settings April 23, 2026 08:52
Copy link
Copy Markdown

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.

Pull request overview

Fixes a concurrency/correctness hazard where outbound request context encoding could observe later mutations of a caller-provided dictionary by snapshotting the dictionary when constructing RequestContextFeature.

Changes:

  • Make RequestContextFeature(IDictionary<string,string>) create a defensive copy of the source dictionary.
  • Document the recommended copy-on-write update pattern for request context in IRequestContextFeature (with a docfx-linked example).
  • Add a regression test ensuring the feature is isolated from subsequent mutations of the source dictionary.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.

File Description
tests/IceRpc.RequestContext.Tests/RequestContextInterceptorTests.cs Adds a regression test asserting the feature doesn’t reflect later source dictionary mutations.
src/IceRpc/Features/RequestContextFeature.cs Updates ctor to snapshot/copy the provided dictionary.
src/IceRpc/Features/IRequestContextFeature.cs Adds remarks and docfx example reference describing copy-on-write usage.
docfx/examples/IceRpc.RequestContext.Examples/RequestContextInterceptorExamples.cs Adds the referenced docfx example region demonstrating copy-on-write updates in an interceptor.

Comment thread src/IceRpc/Features/RequestContextFeature.cs Outdated
/// <summary>Constructs a request context feature with a copy of the specified dictionary.</summary>
/// <param name="dictionary">The source dictionary. Its entries are copied into the new feature so that later
/// mutations of <paramref name="dictionary"/> do not affect this feature.</param>
public RequestContextFeature(IDictionary<string, string> dictionary) =>
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

I'm not 100% sure of adding this defensive copy, maybe the better fix is to clearly document this ..., if you are going to modify just pass a copy.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Yes, I put off reviewing this because I also wasn't sure about making this copy 100% of the time...
I think it'd be better to put this responsibility on the user, instead of making a copy.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Reworked this PR it just update the docs, and includes an example for the correct mutation pattern.

Per review, putting the snapshot responsibility on the caller is
preferable to making the feature copy on every construction (the copy
would also drop a custom key comparer if the caller passed one). Revert
the constructor to wrap the dictionary by reference and tighten the doc
comment to flag the contract: callers must not mutate the dictionary
after handing it to the feature, since the request context is encoded
lazily. The IRequestContextFeature remarks and docfx example continue
to demonstrate the copy-on-write update pattern.

Drop the regression test that asserted isolation (we are intentionally
not isolated now), and discard the unused Pipeline local in the docfx
example so it builds with TreatWarningsAsErrors.
@pepone pepone changed the title Copy source dictionary in RequestContextFeature constructor Document the no-copy contract on RequestContextFeature Apr 27, 2026
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.

[Audit-Low] outbound request context is encoded from a live mutable d…

3 participants