Skip to content

2.7.6#377

Merged
ianrumac merged 10 commits intomainfrom
develop
Mar 9, 2026
Merged

2.7.6#377
ianrumac merged 10 commits intomainfrom
develop

Conversation

@ianrumac
Copy link
Copy Markdown
Collaborator

@ianrumac ianrumac commented Mar 9, 2026

Changes in this pull request

  • Improve billing resillience and recovery
  • Fix concurrency issue with product loads on early starts
  • Fix concurrency issue with identity manager causing locks
  • Fix bug with params not being templated into second presentation

Checklist

  • All unit tests pass.
  • All UI tests pass.
  • Demo project builds and runs.
  • I added/updated tests or detailed why my change isn't tested.
  • I added an entry to the CHANGELOG.md for any breaking changes, enhancements, or bug fixes.
  • I have run ktlint in the main directory and fixed any issues.
  • I have updated the SDK documentation as well as the online docs.
  • I have reviewed the contributing guide

Greptile Summary

This PR addresses billing resilience, concurrency, and presentation correctness issues. Key improvements include:

  1. A ProductState sealed class + deferred-based coordination in StoreManager to prevent duplicate concurrent product fetches
  2. Product retry logic in PaywallRequestManager for paywalls whose products failed during preload
  3. A critical fix in PaywallViewState.ResetPresentationPreparations that corrects flag reset values for proper params templating on second presentation
  4. Billing cache improvements in GoogleBillingWrapper to only cache permanent BillingNotAvailable errors
  5. A timeout flow refactor in PaywallView to avoid NoSuchElementException

Sequence Diagram

sequenceDiagram
    participant Caller1
    participant Caller2
    participant StoreManager
    participant ProductsByFullId as productsByFullId (ConcurrentHashMap)
    participant Billing

    Note over Caller1,Billing: Concurrent product fetch with deferred coordination

    Caller1->>StoreManager: fetchOrAwaitProducts({"prod1"})
    StoreManager->>ProductsByFullId: computeIfAbsent("prod1") → Loading(deferred1)
    StoreManager->>Billing: awaitGetProducts({"prod1"})

    Caller2->>StoreManager: fetchOrAwaitProducts({"prod1"})
    StoreManager->>ProductsByFullId: computeIfAbsent("prod1") → Loading(deferred1) [existing]
    StoreManager->>StoreManager: add deferred1 to loading list

    Billing-->>StoreManager: {prod1: StoreProduct}
    StoreManager->>ProductsByFullId: put("prod1", Loaded(product))
    StoreManager->>StoreManager: deferred1.complete(product)
    StoreManager-->>Caller1: {prod1: product}
    StoreManager-->>Caller2: {prod1: product} (via awaited deferred)

    Note over Caller1,Billing: Retry after transient error

    Caller1->>StoreManager: fetchOrAwaitProducts({"prod1"})
    StoreManager->>ProductsByFullId: computeIfAbsent("prod1") → Error(e) [existing]
    StoreManager->>ProductsByFullId: put("prod1", Loading(deferred2)) [non-atomic replace!]
    StoreManager->>Billing: awaitGetProducts({"prod1"})
    Billing-->>StoreManager: {prod1: StoreProduct}
    StoreManager-->>Caller1: {prod1: product}
Loading

Last reviewed commit: 538a40e

Greptile also left 4 inline comments on this PR.

@ianrumac ianrumac merged commit 45f1613 into main Mar 9, 2026
2 of 3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant