New issue checklist
General information
Describe the bug
Thread Sanitizer reports a data race inside DependencyContainer.init on a plain Superwall.configure(apiKey:) call. Default SuperwallOptions, no PurchaseController, no app-side concurrency. 6/6 runs.
DependencyContainer.init constructs WebEntitlementRedeemer and passes itself as the factory. The redeemer's initialiser starts async work that calls back into makeIsContainerReady() on another thread while init is still assigning its own stored properties — so self escapes before initialisation completes.
Steps to reproduce
- New SwiftUI app, SuperwallKit 4.16.1 via SPM.
- Call
Superwall.configure(apiKey: "pk_…") at launch. Nothing else.
- Build with
-enableThreadSanitizer YES and run on an iOS 26.4 simulator.
TSan reports within a few seconds of launch.
Trace
WARNING: ThreadSanitizer: data race
Write of size 8 at 0x00032e426590 by main thread (mutexes: write M0):
#0 DependencyContainer.init(apiKey:purchaseController:options:) DependencyContainer.swift:122
#1 DependencyContainer.__allocating_init(apiKey:purchaseController:options:)
#2 Superwall.__allocating_init(apiKey:purchaseController:options:completion:) Superwall.swift:462
#3 static Superwall.configure(apiKey:purchaseController:options:completion:) Superwall.swift:695
Previous read of size 8 at 0x00032e426590 by thread T7:
#0 DependencyContainer.makeIsContainerReady() DependencyContainer.swift:648
#1 protocol witness for WebEntitlementFactory.makeIsContainerReady() in conformance DependencyContainer
#2 closure #1 in WebEntitlementRedeemer.init(network:storage:entitlementsInfo:delegate:
purchaseController:receiptManager:factory:notificationScheduler:
stripePendingPollIntervalNs:stripePendingPollTimeoutNs:superwall:)
WebEntitlementRedeemer.swift:120
Location is heap block of size 336 allocated by main thread:
#2 Superwall.__allocating_init(apiKey:purchaseController:options:completion:) Superwall.swift:462
Thread T7 is a GCD worker thread
SUMMARY: ThreadSanitizer: data race DependencyContainer.swift:122 in DependencyContainer.init(apiKey:purchaseController:options:)
Other information
I have not observed a user-visible failure from this — reporting it as a sanitizer-confirmed race with a clear mechanism, not as a crash. It seemed worth raising because it needs nothing from the integrator: any app that calls configure() and runs with TSan will hit it, which makes it noise in everyone else's sanitizer runs as well.
Two other races turned up in the same investigation and I've commented on the existing issues rather than duplicating here:
Happy to share the harness app or test a patch — all three reproductions are deterministic here.
New issue checklist
READMEand documentationGeneral information
Superwallversion: 4.16.1 (SPM)Describe the bug
Thread Sanitizer reports a data race inside
DependencyContainer.initon a plainSuperwall.configure(apiKey:)call. DefaultSuperwallOptions, noPurchaseController, no app-side concurrency. 6/6 runs.DependencyContainer.initconstructsWebEntitlementRedeemerand passes itself as the factory. The redeemer's initialiser starts async work that calls back intomakeIsContainerReady()on another thread whileinitis still assigning its own stored properties — soselfescapes before initialisation completes.Steps to reproduce
Superwall.configure(apiKey: "pk_…")at launch. Nothing else.-enableThreadSanitizer YESand run on an iOS 26.4 simulator.TSan reports within a few seconds of launch.
Trace
Other information
I have not observed a user-visible failure from this — reporting it as a sanitizer-confirmed race with a clear mechanism, not as a crash. It seemed worth raising because it needs nothing from the integrator: any app that calls
configure()and runs with TSan will hit it, which makes it noise in everyone else's sanitizer runs as well.Two other races turned up in the same investigation and I've commented on the existing issues rather than duplicating here:
previousRegisterTaskrace ininternallyRegister, reproduced; answers the "anything specific about how you call register" question (the answer is: from multiple threads).$subscriptionStatusexclusivity violation, closed in 2024 as unreproducible, still present on 4.16.1 at the same statement.Happy to share the harness app or test a patch — all three reproductions are deterministic here.