Skip to content

Commit 67d360d

Browse files
Diagnostics: Fixes flaky Request_AuthorizationHeader_IsRedactedInEtwPayload CI failure
The test's CapturingEventListener relied on OnEventSourceCreated to call EnableEvents. Under parallel CI execution the base EventListener ctor can dispatch OnEventSourceCreated on the constructing thread before the derived class fields are initialized (and before any prior listener in the shared test process has released its hold on the singleton), so the callback would not reliably subscribe this listener to DocumentDBClient. Fix: resolve DocumentClientEventSource.Instance first, pass it into the listener, and call EnableEvents explicitly after base construction. Also filter OnEventWritten to the targeted EventSource and guard against the pre-initialization null-field path. Verified: the two DocumentClientEventSourceTests pass 5/5 consecutive runs locally. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 9a1ecd8 commit 67d360d

1 file changed

Lines changed: 38 additions & 11 deletions

File tree

Microsoft.Azure.Cosmos/tests/Microsoft.Azure.Cosmos.Tests/DocumentClientEventSourceTests.cs

Lines changed: 38 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -29,15 +29,20 @@ public class DocumentClientEventSourceTests
2929
[Owner("ntripician")]
3030
public void Request_AuthorizationHeader_IsRedactedInEtwPayload()
3131
{
32-
using CapturingEventListener listener = new CapturingEventListener();
32+
// Force creation of the EventSource singleton BEFORE the listener is
33+
// constructed so that EnableEvents below binds to a live source. Under
34+
// parallel CI test execution the OnEventSourceCreated callback on a
35+
// subclassed EventListener can race with the subclass field initializer,
36+
// so we avoid relying on it entirely and enable events explicitly.
37+
DocumentClientEventSource eventSource = DocumentClientEventSource.Instance;
38+
using CapturingEventListener listener = new CapturingEventListener(eventSource);
3339

3440
using HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://acct.documents.azure.com/dbs/db/colls/c/docs/id");
3541
Assert.IsTrue(
3642
requestMessage.Headers.TryAddWithoutValidation("authorization", SecretAuthHeaderValue),
3743
"Test setup failed: could not attach authorization header.");
3844

3945
Guid activityId = Guid.NewGuid();
40-
DocumentClientEventSource eventSource = DocumentClientEventSource.Instance;
4146
eventSource.Request(
4247
activityId: activityId,
4348
localId: Guid.NewGuid(),
@@ -88,12 +93,12 @@ public void Request_AuthorizationHeader_IsRedactedInEtwPayload()
8893
[Owner("ntripician")]
8994
public void Request_NoAuthorizationHeader_EmitsEmptyString()
9095
{
91-
using CapturingEventListener listener = new CapturingEventListener();
96+
DocumentClientEventSource eventSource = DocumentClientEventSource.Instance;
97+
using CapturingEventListener listener = new CapturingEventListener(eventSource);
9298

9399
using HttpRequestMessage requestMessage = new HttpRequestMessage(HttpMethod.Get, "https://acct.documents.azure.com/");
94100

95101
Guid activityId = Guid.NewGuid();
96-
DocumentClientEventSource eventSource = DocumentClientEventSource.Instance;
97102
eventSource.Request(
98103
activityId: activityId,
99104
localId: Guid.NewGuid(),
@@ -113,8 +118,24 @@ public void Request_NoAuthorizationHeader_EmitsEmptyString()
113118

114119
private sealed class CapturingEventListener : EventListener
115120
{
121+
// Initialize fields via field initializers so they are assigned before any
122+
// OnEventSourceCreated callback can fire from the base ctor. Under parallel
123+
// CI execution the base EventListener constructor dispatches
124+
// OnEventSourceCreated on the constructing thread while the derived ctor
125+
// has not completed, so any field assigned in the derived ctor body would
126+
// still be null at callback time.
116127
private readonly List<EventWrittenEventArgs> events = new List<EventWrittenEventArgs>();
117128
private readonly object sync = new object();
129+
private readonly EventSource targetEventSource;
130+
131+
public CapturingEventListener(EventSource target)
132+
{
133+
this.targetEventSource = target ?? throw new ArgumentNullException(nameof(target));
134+
135+
// Enable events explicitly after base construction so we do not race with
136+
// OnEventSourceCreated. Keyword 0x1 == DocumentClientEventSource.Keywords.HttpRequestAndResponse.
137+
this.EnableEvents(target, EventLevel.Verbose, (EventKeywords)1);
138+
}
118139

119140
public IReadOnlyList<EventWrittenEventArgs> Events
120141
{
@@ -127,17 +148,23 @@ public IReadOnlyList<EventWrittenEventArgs> Events
127148
}
128149
}
129150

130-
protected override void OnEventSourceCreated(EventSource eventSource)
151+
protected override void OnEventWritten(EventWrittenEventArgs eventData)
131152
{
132-
if (string.Equals(eventSource.Name, "DocumentDBClient", StringComparison.Ordinal))
153+
// Guard against events from sources other than the one we explicitly
154+
// targeted (the base EventListener receives all sources until filtered).
155+
if (this.targetEventSource != null && eventData.EventSource != this.targetEventSource)
133156
{
134-
// Keyword 0x1 == DocumentClientEventSource.Keywords.HttpRequestAndResponse
135-
this.EnableEvents(eventSource, EventLevel.Verbose, (EventKeywords)1);
157+
return;
158+
}
159+
160+
// this.events may still be null if OnEventWritten fires from the base
161+
// constructor before our field initializers run (observed under parallel
162+
// test execution in CI). Ignore those pre-initialization events.
163+
if (this.events == null)
164+
{
165+
return;
136166
}
137-
}
138167

139-
protected override void OnEventWritten(EventWrittenEventArgs eventData)
140-
{
141168
lock (this.sync)
142169
{
143170
this.events.Add(eventData);

0 commit comments

Comments
 (0)