Replace the old dispatcher+queue IdentityManager with a Actor#387
Open
Replace the old dispatcher+queue IdentityManager with a Actor#387
Conversation
superwall/src/test/java/com/superwall/sdk/identity/IdentityActorIntegrationTest.kt
Outdated
Show resolved
Hide resolved
superwall/src/main/java/com/superwall/sdk/identity/IdentityManagerActor.kt
Outdated
Show resolved
Hide resolved
that serializes all identity mutations through a single mutex.
- IdentityState: immutable data class with pure reducers (Updates) and
async side-effecting actions (Actions) via IdentityContext
- IdentityPersistenceInterceptor: diff-based storage writes on state change
- SdkContext: cross-slice bridge so identity can call ConfigManager
without a direct dependency
- Reset flow gates identity readiness during cleanup, matching iOS
- mergeAttributes aligned with iOS (no null filtering in nested collections)
- Startup repair widened to handle empty stored attributes
- Tracking/notify paths use enrichedAttributes consistently
Includes pure reducer unit tests and SequentialActor integration tests
covering concurrency, rapid identify/reset interleaving, and persistence.
d13d6a6 to
4fc59da
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
IdentityState: immutable data class with pure reducers (Updates) and async side-effecting actions (Actions) via IdentityContext
Includes pure reducer unit tests and SequentialActor integration tests
covering concurrency, rapid identify/reset interleaving, and persistence.
Changes in this pull request
Checklist
CHANGELOG.mdfor any breaking changes, enhancements, or bug fixes.ktlintin the main directory and fixed any issues.Greptile Summary
This PR replaces the old single-threaded-executor +
runBlockingIdentityManagerwith a clean actor-model implementation: an immutableIdentityStatedata class, pureUpdatesreducers, asyncActions, aSequentialActorbacked by aMutex, and a diff-basedIdentityPersistenceInterceptor. The reset flow is now gated on identity readiness (matching iOS), andmergeAttributesdrops null-filtering from nested collections for iOS parity.Key changes:
IdentityState+ sealedUpdates/Actionsreplace all mutable fields and ad-hoc coroutine launches in the old managerSequentialActorwith re-entrant mutex serializes all identity transitions without blocking threadsIdentityPersistenceInterceptorperforms diff-based storage writes, eliminating redundant I/OSdkContextbridge decouples the identity slice fromConfigManagerdirectlyFullResetgates paywall presentation (Pending → Ready) around the cleanup windowSequentialActorintegration tests for concurrency and persistenceIssues found:
IdentityManager.ktline 46:override val track: suspend (Trackable) -> Unit = { trackEvent(it as TrackableSuperwallEvent) }— the interface declaresTrackablebut the implementation hard-casts toTrackableSuperwallEvent. This will throwClassCastExceptionat runtime if the contract is ever exercised with a plainTrackable.Thread.sleep()insiderunTestto wait for fire-and-forgeteffectactions; this creates timing-sensitive tests that can flake on slow CI.Identify's user-switch branch ("sostorage.reset()doesn't wipe the new IDs") is factually incorrect —LocalStorage.reset()only clearsconfirmedAssignments/didTrackFirstSeen, not identity fields.Confidence Score: 4/5
Trackablecast inIdentityManager; all other findings are non-blocking style improvements.it as TrackableSuperwallEventcast inIdentityManager.tracksilently violates theTrackablecontract and will throw at runtime if the abstraction is exercised as declared. All current callers passInternalSuperwallEvent(aTrackableSuperwallEvent) so it works today, but it is a latent bug one refactor away from surfacing. The P2 findings (flaky sleep-based tests, misleading comment) are clean-up items that don't block merge.superwall/src/main/java/com/superwall/sdk/identity/IdentityManager.kt— the unsafe cast on line 46 needs to be resolved before merge.Important Files Changed
IdentityState(immutable data class), pureUpdatesreducers, and asyncActions. Well-structured; the misleading comment inIdentify's user-switch branch aboutstorage.reset()wiping identity fields is the only notable issue.override val track: suspend (Trackable) -> Unit = { trackEvent(it as TrackableSuperwallEvent) }— will throwClassCastExceptionif a non-TrackableSuperwallEventTrackableis ever dispatched.SequentialActorserializes all actions via aMutexwith correct re-entrancy detection usingCoroutineContextelements. Clean and correct implementation.immediateUntilusesStateFlow.firstafter firing the action — safe sinceStateFlow.firstchecks the current value before suspending.AppUserIdnull (delete vs write). Works safely underSequentialActor's mutex since no concurrent updates can occur between readingbeforeandafter.identityManager.reset()), whileduringIdentify=trueperforms only the non-identity cleanup. Correctly gates paywall presentation during the reset window, matching iOS behavior.mergeAttributes— removed null-filtering from nestedList/Mapvalues to align with iOS. Intentional behavior change; consumers must tolerate nulls in nested collections.Thread.sleep()insiderunTestblocks for timing synchronization makes tests fragile on slow CI.Sequence Diagram
sequenceDiagram participant Caller participant IdentityManager participant SequentialActor participant Action participant PersistenceInterceptor participant Storage participant SdkContext Note over SequentialActor: Mutex serializes all actions Caller->>IdentityManager: configure(neverCalledStaticConfig) IdentityManager->>SequentialActor: effect(Actions.Configure) SequentialActor->>Action: executeAction (mutex acquired) Action->>PersistenceInterceptor: update(Updates.Configure) PersistenceInterceptor->>Storage: write only changed fields Action->>SdkContext: fetchAssignments() [if needed] Action->>PersistenceInterceptor: update(Updates.AssignmentsCompleted) Note over SequentialActor: phase → Ready Caller->>IdentityManager: identify(userId) IdentityManager->>SequentialActor: effect(Actions.Identify) SequentialActor->>Action: executeAction (waits for mutex) alt switching users Action->>Caller: completeReset() [storage/config/paywall cleanup] Action->>SequentialActor: immediate(Reset) [re-entrant, skips mutex] end Action->>PersistenceInterceptor: update(Updates.Identify) PersistenceInterceptor->>Storage: write appUserId, attributes Action->>SequentialActor: immediate(IdentityChanged) [re-entrant] SequentialActor->>Action: effect(ResolveSeed) [queued, fire-and-forget] SequentialActor->>Action: effect(FetchAssignments) [queued] Caller->>IdentityManager: reset() IdentityManager->>SequentialActor: effect(Actions.FullReset) SequentialActor->>Action: executeAction (waits for mutex) Action->>PersistenceInterceptor: update(Updates.Reset) [phase=Pending, new aliasId/seed] PersistenceInterceptor->>Storage: write aliasId, seed, clear appUserId Action->>Caller: completeReset() [storage/config/paywall cleanup] Action->>PersistenceInterceptor: update(Updates.ResetComplete) Note over SequentialActor: phase → ReadyPrompt To Fix All With AI
Reviews (1): Last reviewed commit: "Replace the old dispatcher+queue Identit..." | Re-trigger Greptile