Skip to content

Commit a1ee630

Browse files
perf(audience): move SyncConsentToBackend out of _initLock
Fires the fire-and-forget consent PUT after the lock releases. JSON serialisation, dictionary allocation, and Task.Run scheduling no longer block concurrent SetConsent / Identify / Reset callers. Why: the call's three args are already local snapshots and the control client it reads is not _initLock-protected anyway (Shutdown disposes it without the lock, relying on _shutdownCancellationSource for safety). Tests: all 181 audience unit tests pass. No test changes — Identity.Reset still runs before the PUT, so the revocation regression guard still holds. Addresses Cursor Bugbot comment on #700. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1 parent 08cfe25 commit a1ee630

1 file changed

Lines changed: 19 additions & 12 deletions

File tree

src/Packages/Audience/Runtime/ImmutableAudience.cs

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -360,16 +360,21 @@ public static void SetConsent(ConsentLevel level)
360360
{
361361
if (!_initialized) return;
362362

363-
// Serialise the transition under _initLock so the state swap and the
364-
// queue purge / downgrade are atomic against concurrent SetConsent
365-
// / Identify / Reset callers.
363+
// Serialise the state swap, consent persist, and queue purge / downgrade
364+
// under _initLock so concurrent SetConsent / Identify / Reset callers see
365+
// a consistent pair. The backend PUT is fire-and-forget and runs outside
366+
// the lock — its args are local snapshots and SyncConsentToBackend already
367+
// tolerates Shutdown racing _controlClient disposal via _shutdownCancellationSource.
368+
AudienceConfig config;
369+
string? anonymousIdForPut;
370+
366371
lock (_initLock)
367372
{
368373
if (!_initialized) return;
369374

370-
var config = _config;
375+
var currentConfig = _config;
371376
var queue = _queue;
372-
if (config == null) return;
377+
if (currentConfig == null) return;
373378

374379
var previousState = _state;
375380
var previous = previousState.Level;
@@ -378,9 +383,9 @@ public static void SetConsent(ConsentLevel level)
378383
// Snapshot the anonymousId BEFORE Identity.Reset (on downgrade to
379384
// None) wipes the file. The PUT audit trail needs it to record
380385
// whose consent changed.
381-
var anonymousIdForPut = previous == ConsentLevel.None
382-
? Identity.GetOrCreate(config.PersistentDataPath!, level)
383-
: Identity.Get(config.PersistentDataPath!);
386+
anonymousIdForPut = previous == ConsentLevel.None
387+
? Identity.GetOrCreate(currentConfig.PersistentDataPath!, level)
388+
: Identity.Get(currentConfig.PersistentDataPath!);
384389

385390
// Atomic swap: both Level and UserId publish together. The next
386391
// Track/Identify read observes the pair consistently.
@@ -391,28 +396,30 @@ public static void SetConsent(ConsentLevel level)
391396
try
392397
{
393398
// PersistentDataPath is validated non-null in Init; compiler can't propagate that.
394-
ConsentStore.Save(config.PersistentDataPath!, level);
399+
ConsentStore.Save(currentConfig.PersistentDataPath!, level);
395400
}
396401
catch (Exception ex) when (ex is IOException || ex is UnauthorizedAccessException)
397402
{
398403
Log.Warn($"SetConsent — failed to persist consent level: {ex.GetType().Name}: {ex.Message}. " +
399404
"In-memory level is updated but will revert on next launch.");
400-
NotifyErrorCallback(config.OnError, AudienceErrorCode.ConsentPersistFailed,
405+
NotifyErrorCallback(currentConfig.OnError, AudienceErrorCode.ConsentPersistFailed,
401406
$"Consent persist failed: {ex.Message}");
402407
}
403408

404409
if (level == ConsentLevel.None)
405410
{
406411
queue?.PurgeAll();
407-
Identity.Reset(config.PersistentDataPath!);
412+
Identity.Reset(currentConfig.PersistentDataPath!);
408413
}
409414
else if (previous == ConsentLevel.Full && level == ConsentLevel.Anonymous)
410415
{
411416
queue?.ApplyAnonymousDowngrade();
412417
}
413418

414-
SyncConsentToBackend(config, level, anonymousIdForPut);
419+
config = currentConfig;
415420
}
421+
422+
SyncConsentToBackend(config, level, anonymousIdForPut);
416423
}
417424

418425
// Fire-and-forget PUT /v1/audience/tracking-consent. Failures do not

0 commit comments

Comments
 (0)