Skip to content

Commit 20e5e86

Browse files
fix(audience-session): emit session_end before flipping _initialized in Shutdown
The two-phase refactor broke Shutdown_FiresSessionEnd. Phase 1 flipped _initialized to false, then Phase 2 called session.Dispose(). Session fires session_end through ImmutableAudience.Track, which guards on CanTrack() = _initialized && _consent.CanTrack(). With _initialized already false, Track dropped the event on the floor. Added Session.EmitEndAndSeal — an emit-only variant of End() that fires session_end and resets state but does not drain the heartbeat timer. Shutdown calls it under _initLock before the flag flip, so Track's gate still lets session_end through. Heartbeat timer drain continues to happen in Phase 2 via session.Dispose(); the second End() inside Dispose no-ops because EmitEndAndSeal already cleared _sessionId. Lock hold time is unchanged: EmitEndAndSeal takes Session._lock for microseconds to compute duration and reset state, then releases before calling SafeTrack → Track → Enqueue (which takes EventQueue's _drainLock, also microseconds). Nothing blocking runs under _initLock. Also drops the column-aligned capture block in Shutdown Phase 1 — dotnet-format rejects the extra whitespace, so one assignment per line. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 7f79159 commit 20e5e86

2 files changed

Lines changed: 45 additions & 7 deletions

File tree

src/Packages/Audience/Runtime/Core/Session.cs

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -193,6 +193,31 @@ internal void End()
193193
});
194194
}
195195

196+
// Emits session_end and seals the session without draining the heartbeat
197+
// timer. Use when the caller needs to fire session_end inside a short
198+
// gating lock (e.g. ImmutableAudience.Shutdown under _initLock while
199+
// _initialized is still true) and will drain + dispose the timer after
200+
// releasing the lock. Idempotent: a subsequent Dispose() → End() will
201+
// find _sessionId null and no-op the re-emission.
202+
internal void EmitEndAndSeal()
203+
{
204+
string sessionId;
205+
long duration;
206+
lock (_lock)
207+
{
208+
if (_disposed || _sessionId == null) return;
209+
sessionId = _sessionId!;
210+
duration = ComputeEngagedSecondsLocked();
211+
ResetSessionStateLocked();
212+
}
213+
214+
SafeTrack("session_end", new Dictionary<string, object>
215+
{
216+
["sessionId"] = sessionId,
217+
["durationSec"] = duration
218+
});
219+
}
220+
196221
public void Dispose()
197222
{
198223
lock (_lock)

src/Packages/Audience/Runtime/ImmutableAudience.cs

Lines changed: 20 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -547,16 +547,29 @@ public static void Shutdown()
547547
{
548548
if (!_initialized) return;
549549

550-
// Flip the gate first. Init / SetConsent / Reset acquiring after
550+
// Emit session_end inside the lock while _initialized is still
551+
// true so Track's CanTrack gate lets it through. Heartbeat timer
552+
// drain is deferred to Phase 2; the second emission from
553+
// session.Dispose → End() no-ops because EmitEndAndSeal reset
554+
// _sessionId.
555+
_session?.EmitEndAndSeal();
556+
557+
// Flip the gate. Init / SetConsent / Reset acquiring after
551558
// this see _initialized == false and return cleanly.
552559
_initialized = false;
553560

554-
session = _session; _session = null;
555-
timer = _sendTimer; _sendTimer = null;
556-
queue = _queue; _queue = null;
557-
transport = _transport; _transport = null;
558-
controlClient = _controlClient; _controlClient = null;
559-
cts = _shutdownCancellationSource; _shutdownCancellationSource = null;
561+
session = _session;
562+
_session = null;
563+
timer = _sendTimer;
564+
_sendTimer = null;
565+
queue = _queue;
566+
_queue = null;
567+
transport = _transport;
568+
_transport = null;
569+
controlClient = _controlClient;
570+
_controlClient = null;
571+
cts = _shutdownCancellationSource;
572+
_shutdownCancellationSource = null;
560573

561574
timeoutMs = _config?.ShutdownFlushTimeoutMs ?? 2_000;
562575

0 commit comments

Comments
 (0)