Skip to content

AppUpdatePresenter.showNotice can double-post the flexible update notice (no already-shown guard) #25666

@jkmassel

Description

@jkmassel

Pre-existing and independent of #25643 (surfaced while reviewing it). Filing so the invariant is defended at the source even after the immediate trigger is removed.

Problem

AppUpdatePresenter guards one update path against double-presentation but not the other:

  • showBlockingUpdate(using:) bails if a BlockingUpdateViewController is already on top (AppUpdatePresenter.swift:34-39). ✅
  • showNotice(using:) — the flexible/optional update — just calls ActionDispatcher.dispatch(NoticeAction.post(notice)) with no such guard (AppUpdatePresenter.swift:12-31). ❌

So two checkForAppUpdates() runs in quick succession can each reach showNotice and queue two identical "update available" notices, shown back-to-back.

Why the existing throttles don't prevent it

AppUpdateCoordinator news up a fresh instance per call, so there's no instance-level dedup. The persisted guards — shouldFetchAppStoreInfo (last-fetched date, ~1 day) and shouldShowFlexibleUpdate (lastSeenFlexibleUpdateDate + interval) — are day-granularity frequency caps, not in-flight dedup, and they race: if two runs both read the dates as stale before either persists its update, both proceed to showNotice.

Relationship to #25643 / #25665

The concrete trigger today is #25643's double updateRemoteConfig() → double checkForAppUpdates() on cold launch; #25665 (a fetch rate limit) removes that trigger. But the missing guard is the real gap — any future path that calls checkForAppUpdates() twice, or two foregrounds racing, can still double-post. This issue is about defending the presentation, not the fetch, so it holds regardless of caller.

Proposed

Mirror the showBlockingUpdate guard on showNotice: don't post a flexible notice if an in-app-update notice is already showing/queued — e.g. tag the Notice and check the NoticeStore before posting, or a presenter/coordinator-level "already requested this run" flag.

Files

  • WordPress/Classes/Services/AppUpdate/AppUpdatePresenter.swiftshowNotice(using:) vs showBlockingUpdate(using:)
  • WordPress/Classes/Services/AppUpdate/AppUpdateCoordinator.swiftcheckForAppUpdates() and the day-granularity throttles

Metadata

Metadata

Assignees

No one assigned

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions