Skip to content

fix(AppLifecycle): deduplicate file activations from multi-process Shell launches#6278

Open
MuyuanMS wants to merge 9 commits intomainfrom
fix/5066-file-activation-dedup
Open

fix(AppLifecycle): deduplicate file activations from multi-process Shell launches#6278
MuyuanMS wants to merge 9 commits intomainfrom
fix/5066-file-activation-dedup

Conversation

@MuyuanMS
Copy link
Contributor

@MuyuanMS MuyuanMS commented Mar 9, 2026

fix(AppLifecycle): Deduplicate file activations from multi-process Shell launches\n\n## Problem\n\nWhen a user selects multiple files in Windows Explorer and opens them with a packaged Win32 app using the Windows App SDK single-instance pattern (FindOrRegisterForKey), the app receives N separate Activated events (one per file), each containing all N files.\n\nThis happens because:\n1. Shell behavior: For Win32 packaged apps, the Shell launches N separate processes (one per file)\n2. Platform API: Each process's GetActivatedEventArgs() returns the full set of N files\n3. Single-instance redirection: All N processes redirect to the key owner with identical activation args\n4. Result: N × N redundancy — N identical activations, each with all N files\n\nIn UWP, the Shell batched files into a single activation. This regression was reported in #5066.\n\n## Solution\n\nAdd a fingerprint-based deduplication mechanism in AppInstance that suppresses redundant file activations:\n\n1. ComputeFileActivationFingerprint() — Computes a size_t hash from the activation verb + file paths. Returns 0 for non-file activations (no dedup applied).\n\n2. IsDuplicateFileActivation() — Checks if a fingerprint matches the last seen fingerprint within a 2-second window. Uses SRWLOCK for thread safety between the main thread and the activation watcher threadpool.\n\n3. GetActivatedEventArgs() — Records the initial activation fingerprint for the current instance, so that subsequent identical redirected activations are recognized as duplicates.\n\n4. ProcessRedirectionRequests() — Checks each redirected activation against the dedup table before firing the Activated event. Always signals the cleanup event so redirecting processes can exit cleanly.\n\n## Design Decisions\n\n- 2-second dedup window: All N processes launch within milliseconds of each other, so 2 seconds is more than sufficient. Short enough to allow legitimate repeated activations.\n- Hash-based fingerprint: Uses std::hash<std::wstring> (available via <string> in pch.h) for compact storage. No additional STL includes needed.\n- SRWLOCK: Lightweight in-process synchronization, consistent with Win32 primitives used elsewhere in the codebase.\n- Best-effort: If fingerprinting fails (e.g., QI fails on COM-proxied activation data), the code falls back to delivering the activation (no regression).\n- Non-file activations unaffected: Only ExtendedActivationKind::File activations are deduplicated.\n\n## Files Changed\n\n| File | Change |\n|------|--------|\n| dev/AppLifecycle/AppInstance.h | Add dedup members (SRWLOCK, fingerprint, timestamp) and method declarations |\n| dev/AppLifecycle/AppInstance.cpp | Implement ComputeFileActivationFingerprint(), IsDuplicateFileActivation(), modify GetActivatedEventArgs() and ProcessRedirectionRequests() |\n| dev/AppLifecycle/FileActivatedEventArgs.h | Revert to original single-file constructor (remove unused multi-file constructor) |\n\n## Testing\n\n- Build verified: BuildAll.ps1 -Platform x64 -Configuration Release → exit code 0\n- Manual test scenario: Select 5+ files in Explorer → Open with single-instance packaged app → Should receive 1 activation instead of 5\n\nFixes #5066"

MuyuanMS and others added 8 commits March 4, 2026 18:50
Port agents and skills from PowerToys for automated PR and issue
workflows, generalized for any repository:

Agents (8): ReviewPR, FixPR, TriagePR, ReviewIssue, PlanIssue,
  FixIssue, IssueToPR, ReviewTheReview

Skills (10): pr-review (13-dimension analysis), pr-fix, pr-rework
  (iterative review-fix-build loops), pr-triage, issue-review,
  issue-fix, issue-review-review, issue-to-pr-cycle,
  continuous-issue-triage, parallel-job-orchestrator

Key generalizations:
- Auto-detect owner/repo via gh CLI (Get-RepoSlug helper)
- Build commands use existing BuildAll.ps1
- Worktree scripts use existing worktree-manager skill
- Review dimension 10 rewritten for C++/WinRT + WIL patterns
- All 13 review dimensions updated with WinAppSDK-specific checks
- No hardcoded repo references; fully portable

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Bump AppLifecycleContract from v2 to v3 and expose a public constructor on AppActivationArguments(ExtendedActivationKind, IInspectable) so callers can create instances directly for activation redirection scenarios without relying on internal factory methods.

Fixes #6075
…ingle event

When a user opens multiple files at once, each file triggers a separate
process that redirects to the main single-instanced app. Previously,
each redirection fired a separate Activated event, causing the app to
receive N individual activations instead of one event with all N files.

This change:
- Adds a multi-file constructor to FileActivatedEventArgs that accepts
  a pre-built IVector<IStorageItem> for efficient in-process merging.
- Rewrites AppInstance::ProcessRedirectionRequests() to collect all
  pending file activation redirections, wait a brief coalescing window
  (150ms) for late-arriving redirections, and fire a single merged
  Activated event containing all files.
- Non-file activation types continue to fire immediately as before.
- All coalesced redirection cleanup events are properly signaled so
  redirecting processes can exit normally.

Fixes #5066
Phase C-D was a vague single-line instruction ('loop until resolved')
which LLMs interpreted as a single Review+Fix pass. Now replaced with
an explicit 4-step iterative loop:

1. REVIEW (invoke ReviewPR)
2. EVALUATE (check 0 medium+ findings → exit)
3. FIX (invoke FixPR)
4. BUILD (verify compilation → back to step 1)

Loop exits on: clean PR, max iterations (3), or no progress.
Also updated FixPR agent to mandate re-review after fixes.

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…re opened simultaneously

When a user selects multiple files in Windows Explorer and opens them with
a Windows App SDK app, the Shell may launch multiple instances (one per
file). With the AppInstance.FindOrRegisterForKey pattern, all but the first
instance redirect their activation to the key holder. Previously, every
redirected activation fired a separate Activated event, even when all
activations carried the same set of files.

This change adds fingerprint-based deduplication to
AppInstance.ProcessRedirectionRequests(). A fingerprint is computed from
the activation kind, verb, and sorted file paths. Redirected activations
whose fingerprint matches either the initial activation (within a 5-second
window) or a previously processed redirection in the same batch are
suppressed. The cleanup event is always signaled so redirecting processes
exit cleanly.

Fixes #5066
…ell launches

When the Windows Shell opens multiple selected files with a packaged Win32 app,
it launches N separate processes (one per file). Each process receives the full
file list in its activation args via the platform API. With single-instance
redirection (FindOrRegisterForKey), this results in N identical Activated events
on the key owner.

This change adds a fingerprint-based deduplication mechanism that:
1. Computes a hash of verb + file paths for each file activation
2. Records the initial activation fingerprint in GetActivatedEventArgs()
3. Suppresses redirected activations matching a recent fingerprint (2s window)
4. Always signals the cleanup event so redirecting processes can exit cleanly

The fix reduces N identical activations to 1, matching UWP behavior.

Files changed:
- dev/AppLifecycle/AppInstance.h: Add dedup members and method declarations
- dev/AppLifecycle/AppInstance.cpp: Implement fingerprint computation and dedup
- dev/AppLifecycle/FileActivatedEventArgs.h: Revert to original single-file ctor

Fixes #5066
…, hash comment

- Wrap SRWLOCK acquire/release in wil::scope_exit for RAII safety
- Normalize file paths to lowercase before hashing (NTFS is case-insensitive)
- Add comment documenting std::hash collision tradeoff on x86 (32-bit size_t)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant