Skip to content

fix(AppLifecycle): coalesce multi-file activation redirections into single event#6276

Open
MuyuanMS wants to merge 5 commits intomainfrom
user/muyuanli/demofix
Open

fix(AppLifecycle): coalesce multi-file activation redirections into single event#6276
MuyuanMS wants to merge 5 commits intomainfrom
user/muyuanli/demofix

Conversation

@MuyuanMS
Copy link
Contributor

@MuyuanMS MuyuanMS commented Mar 8, 2026

Summary

Fixes #5066App activation fires multiple times when launching multiple files

When a user selects N files and opens them with a WinAppSDK single-instanced app, N processes launch. N-1 processes redirect to the main instance via RedirectActivationToAsync(). Previously, the main instance fired N separate Activated events — each containing 1 file — instead of 1 event containing all N files.

Root Cause

ProcessRedirectionRequests() in AppInstance.cpp had a simple while-loop that dequeued each redirection request and fired m_activatedEvent independently for each one.

Fix

dev/AppLifecycle/FileActivatedEventArgs.h

  • Added a new constructor FileActivatedEventArgs(verb, IVector<IStorageItem>) for coalesced multi-file activations
  • Updated comments to clarify single-file vs. multi-file paths

dev/AppLifecycle/AppInstance.cpp

Rewrote ProcessRedirectionRequests() to:

  1. Categorize incoming requests as file activations vs. non-file activations
  2. Collect all file activations into a shared coalescedFiles vector
  3. Wait 150ms coalescing window for late-arriving redirections (other processes may not have enqueued yet)
  4. Merge all collected files into a single FileActivatedEventArgs
  5. Fire once — one Activated event with all files
  6. Signal cleanup events for all coalesced redirecting processes so they can exit

Non-file activations continue to fire immediately, preserving backward compatibility.

Testing

  • WindowsAppRuntime_DLL.vcxproj builds cleanly (x64 Release)
  • Manual verification: opening multiple files should now produce a single Activated event with all files in the Files collection

Notes

  • The 150ms coalescing window adds a small delay to single-file activations but ensures multi-file opens are properly coalesced
  • The branch also contains .github/ infrastructure files unrelated to this fix

MuyuanMS and others added 5 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
@benstevens48
Copy link

Please don't add a forced delay for file activation. The issue can be worked around (at least in packaged apps) by using the MultiSelectModel property of uap3:FileTypeAssociation in the appxmanifest and setting it to Player as follows

<Extensions>
  <uap3:Extension Category="windows.fileTypeAssociation">
    <uap3:FileTypeAssociation Name="my-extensions" MultiSelectModel="Player">
      ..

This means the app is only launched once (with multiple files provided to the file activation args).

If you are going to add a delay, please make this optional as start-up speed is very important to me.

@DrusTheAxe
Copy link
Member

Could single instance apps receive multiple files before? Wondering if this is a breaking change

(and if so, 2.0 is around the corner...)

activity.DequeueRedirectionRequest(id);
wil::unique_cotaskmem_string idString;
THROW_IF_FAILED(StringFromCLSID(id, &idString));
GUID id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: GUID id{};

ES.23: Prefer the {}-initializer syntax

If it was just a dumb struct then routine not to initialize at declaration but it's got objects e.g. std::wstring so CoalescedRequestInfo cri; memset(&cri, 0, sizeof(cri)); would be BadMojo(TM). So better to make all attributes initialized at constructor rather than just some

};

std::vector<CoalescedRequestInfo> fileRequestInfos;
auto coalescedFiles = winrt::single_threaded_vector<winrt::Windows::Storage::IStorageItem>();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NIT: auto coalescedFiles{ ... };

ES.23: Prefer the {}-initializer syntax

same comment applies throughout

// File activations are collected for coalescing; other types fire immediately.
auto dequeueAndProcess = [&]()
{
GUID id;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

GUID id{};

always initialize variables

same comment applies throughout

// ensures we capture all files from a single user action.
if (!fileRequestInfos.empty())
{
constexpr DWORD c_fileCoalescingWindowMs = 150;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Magic number

Why 150ms?

if (!fileRequestInfos.empty())
{
constexpr DWORD c_fileCoalescingWindowMs = 150;
if (WaitForSingleObject(m_innerActivated.get(), c_fileCoalescingWindowMs) == WAIT_OBJECT_0)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Errors are silently ignored

Add logging if a errors occur


// Signal cleanup events for all coalesced file activation requests so that the
// redirecting processes can exit.
for (const auto& req : fileRequestInfos)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't abbreviate, it makes code harder to understand and maintain

SUGESTION: for (const auto& fileRequestInfo : fileRequestInfos)

{
if (verb.empty())
{
throw std::invalid_argument("verb");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why throw? WIL THROW*() is used almost everywhere

And being a WinRT object throwing a C++ object will lose its data and other information except its equivalent HRESULT. If you e.g. replace this with

THROW_HR_MSG(E_INVALIDARG, "verb");

the WinRT caller receives the same HRESULT (E_INVALIDARG) but WIL logged a message first so the caller has that context available if running under a debugger, or ETW events are being captured, or telemetry captured and recorded this error.

same comment applies throughout

@@ -0,0 +1,201 @@
Apache License
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Apache License? Is that a problem for us?

Check with WinAppSDK core folks (@Scottj1s @alexlamtest etc)

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.

App activation fires multiple times when launching multiple files

3 participants