Skip to content

Commit 61230e6

Browse files
iancooperclaude
andcommitted
docs: tighten code comments and add comment guidance to code style
Remove issue-number references and reviewer-facing context from the inline and XML doc comments added for the consumer-pump observability change, and narrow the remaining comments to explaining the code itself. Add a Comments section to .agent_instructions/code_style.md: no issue/PR links in comments, comments explain the why of non-obvious code (not the what or the change history), reviewer rationale belongs in the ADR, keep comments concise. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent f0f7809 commit 61230e6

9 files changed

Lines changed: 26 additions & 49 deletions

File tree

.agent_instructions/code_style.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,4 +36,9 @@
3636
- As we support multiple schedulers, these should use their own assembly.
3737
- As we support multiple locking providers, these should use their own assembly.
3838
- Default to a class per source file approach, unless one class clearly exists as the details of another.
39+
- Comments
40+
- Do not put links or references to issues or PRs (e.g. `(issue #4089)`, `#4207`) in code or XML doc comments. The traceability lives in git history and the ADR, not in the source.
41+
- A comment is for code that needs explanation — typically the *why* behind a non-obvious decision. Do not narrate *what* the code does, restate the code, or describe the change relative to how the code used to be.
42+
- Inline comments are not notes to a reviewer. Context about the fix, the alternatives considered, the previous behaviour, or the history of a change is reviewer-facing rationale and belongs in the ADR, not in an inline comment. A future maintainer reading the code only needs what helps them understand the code as it now stands.
43+
- Keep comments concise. Prefer one tight line over a paragraph.
3944

src/Paramore.Brighter.ServiceActivator/Proactor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ private async Task EventLoop()
153153
// receive span covers only the broker call so its Duration reflects broker latency, not dispatch
154154
Activity? receiveSpan = null;
155155
Message? message = null;
156-
// the header is serialized once for the receive span and reused by the process span (issue #4089)
156+
// serialized once on the receive span and reused by the process span
157157
string? headerJson = null;
158158
try
159159
{
@@ -162,7 +162,6 @@ private async Task EventLoop()
162162
receiveSpan = Tracer?.CreateReceiveSpan(Channel.RoutingKey, MessagingSystem.InternalBus, InstrumentationOptions);
163163
message = await Channel.ReceiveAsync(TimeOut);
164164
headerJson = Tracer?.EnrichReceiveSpan(receiveSpan, message, InstrumentationOptions);
165-
// propagate consumer baggage once per message, independent of how many spans we create
166165
Tracer?.PropagateConsumerContext(message);
167166
}
168167
catch (ChannelFailureException ex) when (ex.InnerException is BrokenCircuitException)

src/Paramore.Brighter.ServiceActivator/Reactor.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ public void Run()
112112
// receive span covers only the broker call so its Duration reflects broker latency, not dispatch
113113
Activity? receiveSpan = null;
114114
Message? message = null;
115-
// the header is serialized once for the receive span and reused by the process span (issue #4089)
115+
// serialized once on the receive span and reused by the process span
116116
string? headerJson = null;
117117
try
118118
{
@@ -121,7 +121,6 @@ public void Run()
121121
receiveSpan = Tracer?.CreateReceiveSpan(Channel.RoutingKey, MessagingSystem.InternalBus, InstrumentationOptions);
122122
message = Channel.Receive(TimeOut);
123123
headerJson = Tracer?.EnrichReceiveSpan(receiveSpan, message, InstrumentationOptions);
124-
// propagate consumer baggage once per message, independent of how many spans we create
125124
Tracer?.PropagateConsumerContext(message);
126125
}
127126
catch (ChannelFailureException ex) when (ex.InnerException is BrokenCircuitException)

src/Paramore.Brighter/Observability/BrighterTracer.cs

Lines changed: 5 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -189,8 +189,7 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
189189
tags.Add(BrighterSemanticConventions.MessageId, message.Id.Value);
190190
tags.Add(BrighterSemanticConventions.MessageType, message.Header.MessageType.ToString());
191191
tags.Add(BrighterSemanticConventions.MessageBodySize, message.Body.Memory.Length);
192-
//reuse the header serialized once for the receive span (issue #4089); when this span is created without a
193-
//preceding receive span (none threaded in) we serialize the as-received header here instead
192+
//reuse the header the receive span already serialized; serialize here only when there was no receive span
194193
tags.Add(BrighterSemanticConventions.MessageHeaders,
195194
serializedHeader ?? JsonSerializer.Serialize(message.Header, JsonSerialisationOptions.Options));
196195
tags.Add(BrighterSemanticConventions.ConversationId, message.Header.CorrelationId.Value);
@@ -214,9 +213,6 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
214213

215214
try
216215
{
217-
//baggage propagation (correlationId, SetBaggage) is no longer done here: it is a consumer-context concern,
218-
//done once per message by the pump via PropagateConsumerContext, not per span. TraceStateString is
219-
//per-activity so it stays here.
220216
activity.TraceStateString = traceState;
221217

222218
Activity.Current = activity;
@@ -225,8 +221,7 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
225221
}
226222
catch (Exception ex)
227223
{
228-
//the activity has already been started; if enriching it throws the caller never receives it and so
229-
//cannot end it via its try/finally, so we must end it here to avoid leaking a started activity
224+
//the activity is already started; end it here so a throw during enrichment cannot leak it past the caller
230225
activity.SetStatus(ActivityStatusCode.Error, ex.Message);
231226
EndSpan(activity);
232227
throw;
@@ -305,8 +300,7 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
305300
span.AddTag(BrighterSemanticConventions.MessageId, message.Id.Value);
306301
span.AddTag(BrighterSemanticConventions.MessageType, message.Header.MessageType.ToString());
307302
span.AddTag(BrighterSemanticConventions.MessageBodySize, message.Body.Memory.Length);
308-
//serialize the as-received header once; the caller threads this string into CreateSpan(Process, ...) so the
309-
//process span reuses it rather than serializing the header a second time per message (issue #4089)
303+
//returned so the process span can reuse it instead of serializing the header again
310304
serializedHeader = JsonSerializer.Serialize(message.Header, JsonSerialisationOptions.Options);
311305
span.AddTag(BrighterSemanticConventions.MessageHeaders, serializedHeader);
312306
span.AddTag(BrighterSemanticConventions.ConversationId, message.Header.CorrelationId.Value);
@@ -315,9 +309,7 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
315309
if (options.HasFlag(InstrumentationOptions.RequestBody))
316310
span.AddTag(BrighterSemanticConventions.MessageBody, message.Body.Value);
317311

318-
// Propagate the producer's tracestate onto the receive span. Done here (not in CreateReceiveSpan) because this value
319-
// comes from the message and isn't known until the broker call returns. TraceStateString is per-activity; consumer
320-
// baggage propagation is a separate, once-per-message concern handled by PropagateConsumerContext.
312+
// not set in CreateReceiveSpan because the tracestate comes from the message, unknown until the broker call returns
321313
if (!string.IsNullOrEmpty(message.Header.TraceState?.Value))
322314
span.TraceStateString = message.Header.TraceState!.Value;
323315

@@ -327,8 +319,7 @@ public void AddExceptionToSpan(Activity? span, IEnumerable<Exception> exceptions
327319
/// <summary>
328320
/// Propagates the producer's baggage onto the consumer side for a received message: lifts the message's
329321
/// <see cref="MessageHeader.CorrelationId"/> into its <see cref="MessageHeader.Baggage"/> and sets it as the ambient
330-
/// OpenTelemetry baggage for the current execution context. Called once per received message by the pump — independent
331-
/// of how many spans (receive, process) are created — so the propagation runs exactly once rather than per span.
322+
/// OpenTelemetry baggage for the current execution context. Called once per received message by the pump.
332323
/// </summary>
333324
/// <param name="message">The <see cref="Message"/> that was received</param>
334325
public void PropagateConsumerContext(Message message)

src/Paramore.Brighter/Observability/IAmABrighterTracer.cs

Lines changed: 5 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -45,9 +45,8 @@ public interface IAmABrighterTracer : IDisposable
4545
/// <param name="message">What is the <see cref="Message"/> that we received; if they have a traceparentid we will use that as a parent for this trace</param>
4646
/// <param name="messagingSystem">What is the messaging system that we are receiving a message from</param>
4747
/// <param name="options">The <see cref="InstrumentationOptions"/> for how deep should the instrumentation go</param>
48-
/// <param name="serializedHeader">The header serialized once by <see cref="EnrichReceiveSpan"/> for the receive span,
49-
/// threaded in so the process span reuses it instead of serializing the header a second time per message (issue #4089);
50-
/// when null (no preceding receive span) the header is serialized here</param>
48+
/// <param name="serializedHeader">The header already serialized by <see cref="EnrichReceiveSpan"/>, reused here instead
49+
/// of serializing it again; when null (no preceding receive span) the header is serialized by this method</param>
5150
/// <returns></returns>
5251
Activity? CreateSpan(
5352
MessagePumpSpanOperation operation,
@@ -78,8 +77,8 @@ public interface IAmABrighterTracer : IDisposable
7877
/// <param name="span">The receive span to enrich; no-op if null</param>
7978
/// <param name="message">The <see cref="Message"/> that was received</param>
8079
/// <param name="options">The <see cref="InstrumentationOptions"/> for how deep should the instrumentation go</param>
81-
/// <returns>The header serialized once for this message (so it can be threaded into <see cref="CreateSpan(MessagePumpSpanOperation,Message,MessagingSystem,InstrumentationOptions,string)"/>
82-
/// and not serialized again), or null if there was no span to enrich or messaging instrumentation is disabled</returns>
80+
/// <returns>The serialized header, to pass to <see cref="CreateSpan(MessagePumpSpanOperation,Message,MessagingSystem,InstrumentationOptions,string)"/>
81+
/// so it is not serialized again; null if there was no span to enrich or messaging instrumentation is disabled</returns>
8382
string? EnrichReceiveSpan(
8483
Activity? span,
8584
Message message,
@@ -88,8 +87,7 @@ public interface IAmABrighterTracer : IDisposable
8887
/// <summary>
8988
/// Propagates the producer's baggage onto the consumer side for a received message: lifts the message's
9089
/// <see cref="MessageHeader.CorrelationId"/> into its <see cref="MessageHeader.Baggage"/> and sets it as the ambient
91-
/// OpenTelemetry baggage for the current execution context. Called once per received message by the pump so the
92-
/// propagation runs exactly once, independent of how many spans (receive, process) are created.
90+
/// OpenTelemetry baggage for the current execution context. Called once per received message by the pump.
9391
/// </summary>
9492
/// <param name="message">The <see cref="Message"/> that was received</param>
9593
void PropagateConsumerContext(Message message);

tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_Has_A_Malformed_Correlation_Id_The_Pump_Continues.cs

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -85,8 +85,7 @@ public MalformedCorrelationIdPumpObservabilityTests()
8585
Channel = channel, TimeOut = TimeSpan.FromMilliseconds(5000)
8686
};
8787

88-
//a serviceable message whose correlation id is rejected by W3C Baggage validation: propagating it as baggage
89-
//throws, but observability must never tear down the pump - the message should still be dispatched
88+
//"bad=value" is rejected by W3C Baggage validation, so propagating it as baggage throws
9089
var message = new Message(
9190
new MessageHeader(_myEvent.Id, _routingKey, MessageType.MT_EVENT, correlationId: new Id("bad=value")),
9291
new MessageBody(JsonSerializer.Serialize(_myEvent, JsonSerialisationOptions.Options))
@@ -100,10 +99,10 @@ public MalformedCorrelationIdPumpObservabilityTests()
10099
[Fact]
101100
public void When_a_message_has_a_malformed_correlation_id_the_pump_continues()
102101
{
103-
//Act - a malformed correlation id makes baggage propagation throw; the pump must not propagate that out
102+
//Act
104103
var exception = Record.Exception(() => _messagePump.Run());
105104

106-
//Assert - the pump ran to its QUIT message without tearing down, and the poisoned message was still dispatched
105+
//Assert - the pump reached its QUIT message and still dispatched the poisoned message
107106
Assert.Null(exception);
108107
Assert.True(_receivedMessages.ContainsKey(nameof(MyEventHandler)));
109108
Assert.Equal(MessagePumpStatus.MP_STOPPED, _messagePump.Status);

tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_Is_Dispatched_The_Header_Is_Serialized_Once.cs

Lines changed: 1 addition & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,7 +103,6 @@ public MessageHeaderSerializationObservabilityTests()
103103
Channel = channel, TimeOut = TimeSpan.FromMilliseconds(5000)
104104
};
105105

106-
//a single serviceable message (MT_EVENT) is the steady-state hot path the perf regression affects
107106
_message = new Message(
108107
new MessageHeader(_myEvent.Id, _routingKey, MessageType.MT_EVENT, replyTo: new RoutingKey("io.paramorebrighter.myevent")),
109108
new MessageBody(JsonSerializer.Serialize(_myEvent, JsonSerialisationOptions.Options))
@@ -122,7 +121,6 @@ public void When_a_message_is_dispatched_the_header_is_serialized_once_for_both_
122121
_traceProvider.ForceFlush();
123122

124123
// Assert
125-
//a serviceable message produces both a receive span (broker-call timing) and a process span (dispatch timing)
126124
var receiveSpan = _exportedActivities.FirstOrDefault(a =>
127125
a.DisplayName == $"{_message.Header.Topic} {MessagePumpSpanOperation.Receive.ToSpanName()}"
128126
&& a.TagObjects.Any(to => to is { Value: not null, Key: BrighterSemanticConventions.MessageType } && Enum.Parse<MessageType>(to.Value.ToString() ?? string.Empty) == MessageType.MT_EVENT));
@@ -133,17 +131,13 @@ public void When_a_message_is_dispatched_the_header_is_serialized_once_for_both_
133131
&& a.TagObjects.Any(to => to is { Value: not null, Key: BrighterSemanticConventions.MessageType } && Enum.Parse<MessageType>(to.Value.ToString() ?? string.Empty) == MessageType.MT_EVENT));
134132
Assert.NotNull(processSpan);
135133

136-
//both spans must still carry the serialized header with the correct value (diagnostic contract preserved)
137134
var expectedHeaderJson = JsonSerializer.Serialize(_message.Header, JsonSerialisationOptions.Options);
138135
var receiveHeaderJson = receiveSpan!.TagObjects.Single(to => to.Key == BrighterSemanticConventions.MessageHeaders).Value;
139136
var processHeaderJson = processSpan!.TagObjects.Single(to => to.Key == BrighterSemanticConventions.MessageHeaders).Value;
140137
Assert.Equal(expectedHeaderJson, receiveHeaderJson);
141138
Assert.Equal(expectedHeaderJson, processHeaderJson);
142139

143-
//the header must be serialized only ONCE per serviceable message: both spans share the SAME string instance.
144-
//Today the receive span (EnrichReceiveSpan) and the process span (CreateSpan) each call
145-
//JsonSerializer.Serialize(message.Header, ...) independently, producing two distinct (never-interned)
146-
//string instances - so this reference-equality assertion fails until the serialization is computed once and reused.
140+
//both spans share one serialized header (Assert.Same: a never-interned string, so equal references prove reuse)
147141
Assert.Same(receiveHeaderJson, processHeaderJson);
148142
}
149143
}

tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_A_Message_With_A_Correlation_Id_Is_Dispatched_Both_Spans_Share_One_Header.cs

Lines changed: 2 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -103,8 +103,6 @@ public MessageHeaderCorrelationIdObservabilityTests()
103103
Channel = channel, TimeOut = TimeSpan.FromMilliseconds(5000)
104104
};
105105

106-
//a serviceable message carrying a correlation id - the value the reviewer noted could be dropped from the
107-
//process span when it diverged from the receive span (issue #4089 review, point 1)
108106
_message = new Message(
109107
new MessageHeader(_myEvent.Id, _routingKey, MessageType.MT_EVENT, correlationId: new Id(CorrelationId),
110108
replyTo: new RoutingKey("io.paramorebrighter.myevent")),
@@ -137,17 +135,14 @@ public void When_a_message_with_a_correlation_id_is_dispatched_both_spans_share_
137135
var receiveHeaderJson = (string?)receiveSpan!.TagObjects.Single(to => to.Key == BrighterSemanticConventions.MessageHeaders).Value;
138136
var processHeaderJson = (string?)processSpan!.TagObjects.Single(to => to.Key == BrighterSemanticConventions.MessageHeaders).Value;
139137

140-
//the correlation id is conveyed by the serialized header (as the top-level CorrelationId field) so it is NOT
141-
//lost from either span; it also rides on the dedicated ConversationId tag below
138+
//the correlation id is carried in the serialized header as the top-level CorrelationId field
142139
Assert.Contains(CorrelationId, receiveHeaderJson);
143140
Assert.Contains(CorrelationId, processHeaderJson);
144141

145-
//both spans must carry the IDENTICAL serialized header (the as-received header), serialized exactly once and
146-
//shared - so the process span never diverges from the receive span (issue #4089 review, point 1)
142+
//both spans share one serialized header (Assert.Same: a never-interned string, so equal references prove reuse)
147143
Assert.Equal(receiveHeaderJson, processHeaderJson);
148144
Assert.Same(receiveHeaderJson, processHeaderJson);
149145

150-
//the correlation id is also exposed as the dedicated conversation id tag on both spans
151146
Assert.Equal(CorrelationId, receiveSpan.TagObjects.Single(to => to.Key == BrighterSemanticConventions.ConversationId).Value);
152147
Assert.Equal(CorrelationId, processSpan.TagObjects.Single(to => to.Key == BrighterSemanticConventions.ConversationId).Value);
153148
}

tests/Paramore.Brighter.Core.Tests/Observability/MessageDispatch/When_Creating_A_Process_Span_Baggage_Propagation_Is_Decoupled.cs

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -51,25 +51,22 @@ public CreateProcessSpanBaggageDecoupledObservabilityTests()
5151
[Fact]
5252
public void When_creating_a_process_span_baggage_propagation_is_decoupled()
5353
{
54-
//Arrange - a correlation id whose value is rejected by Baggage validation. Baggage propagation is no longer done
55-
//inside CreateSpan (it moved to PropagateConsumerContext, called once per message by the pump), so creating the
56-
//process span must NOT touch baggage and therefore must NOT throw - the started activity can never leak this way.
54+
//"bad=value" is rejected by Baggage validation; CreateSpan no longer propagates baggage, so it must not throw
5755
var message = new Message(
5856
new MessageHeader(Id.Random(), _routingKey, MessageType.MT_EVENT, correlationId: new Id("bad=value")),
5957
new MessageBody("{}"));
6058

6159
//Act
6260
var processSpan = _tracer.CreateSpan(MessagePumpSpanOperation.Process, message, MessagingSystem.InternalBus);
6361

64-
//Assert - the process span was created normally and, once ended, is exported (not leaked)
62+
//Assert
6563
Assert.NotNull(processSpan);
6664
_tracer.EndSpan(processSpan);
6765
_traceProvider.ForceFlush();
6866
Assert.Contains(_exportedActivities, a =>
6967
a.DisplayName == $"{_routingKey} {MessagePumpSpanOperation.Process.ToSpanName()}");
7068

71-
//the malformed correlation id is rejected only when baggage is actually propagated - now the responsibility of
72-
//PropagateConsumerContext - so that is the single place the validation surfaces
69+
//baggage propagation is the single place the malformed value is now rejected
7370
Assert.Throws<ArgumentException>(() => _tracer.PropagateConsumerContext(message));
7471
}
7572

0 commit comments

Comments
 (0)