Skip to content

Commit 57e990a

Browse files
test(audience-session): regression test proving _initLock is released before Phase 2
Locks a Shutdown inside its Phase 2 final flush via a BlockingHandler that never returns from SendAsync, then calls Reset on the main thread with a stopwatch. Pre-refactor, Reset would wait on _initLock for the full ShutdownFlushTimeoutMs (up to 10s in this test's setup). Post-refactor, Reset acquires the lock immediately, sees !_initialized, and returns in microseconds. Asserts Reset completes in under 500ms — generous margin to avoid CI flakes while still catching a regression that re-introduces a seconds-scale hold. Addresses the reviewer's concern from PR #699 and guards against future changes that move blocking work back inside the lock. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 0389487 commit 57e990a

1 file changed

Lines changed: 38 additions & 0 deletions

File tree

src/Packages/Audience/Tests/Runtime/ImmutableAudienceTests.cs

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -951,6 +951,44 @@ public void Track_AfterShutdown_IsIgnored()
951951
Assert.DoesNotThrow(() => ImmutableAudience.Track("should_not_crash"));
952952
}
953953

954+
[Test]
955+
public void Shutdown_ReleasesInitLock_BeforeBlockingTeardown()
956+
{
957+
// Hanging handler: the final flush inside Shutdown's Phase 2 will
958+
// block in transport.SendBatchAsync().Wait(timeoutMs). Pre-refactor,
959+
// _initLock was held across that wait — SetConsent / Reset on another
960+
// thread would be stranded for the full ShutdownFlushTimeoutMs.
961+
var handler = new BlockingHandler();
962+
var config = MakeConfig();
963+
config.HttpHandler = handler;
964+
config.ShutdownFlushTimeoutMs = 10_000;
965+
966+
ImmutableAudience.Init(config);
967+
ImmutableAudience.Track("ensure_nonempty_queue");
968+
ImmutableAudience.FlushQueueToDiskForTesting();
969+
970+
// Phase 1 flips _initialized and releases the lock; Phase 2 enters
971+
// the hanging final flush.
972+
var shutdown = Task.Run(() => ImmutableAudience.Shutdown());
973+
974+
Assert.IsTrue(handler.EnteredSendAsync.Wait(TimeSpan.FromSeconds(5)),
975+
"Shutdown should reach the hanging final flush inside Phase 2");
976+
977+
// Reset unconditionally acquires _initLock. If Phase 2 held the lock
978+
// this would block for ~10s; post-refactor the lock is free, Reset
979+
// sees !_initialized and returns in microseconds.
980+
var sw = System.Diagnostics.Stopwatch.StartNew();
981+
ImmutableAudience.Reset();
982+
sw.Stop();
983+
984+
Assert.Less(sw.ElapsedMilliseconds, 500,
985+
$"Reset must not block on Shutdown's Phase 2; took {sw.ElapsedMilliseconds}ms");
986+
987+
handler.Release.Set();
988+
Assert.IsTrue(shutdown.Wait(TimeSpan.FromSeconds(15)),
989+
"Shutdown should finish after handler release");
990+
}
991+
954992
// -----------------------------------------------------------------
955993
// Full -> Anonymous consent downgrade
956994
// -----------------------------------------------------------------

0 commit comments

Comments
 (0)