Skip to content

COM Install/Download thread logic issue #5365

@luke-patchblox

Description

@luke-patchblox

Brief description of your issue

There is a threading issue with C# (maybe C++ but I did not test this) COM commands on machines with 1 CPU Core. If executing a function like InstallPackageAsync, the program will permanently hang until manual user termination. In my experience, it appears to get stuck waiting for an event within the code below: (This is inside of PackageManager.cpp within the Microsoft.Management.Deployment project)

            while (!completionEventFired)
            {
                DWORD dwEvent = WaitForMultipleObjects(
                    _countof(operationEvents) /* number of events */,
                    operationEvents /* event array */,
                    FALSE /* bWaitAll, FALSE to wake on any event */,
                    INFINITE /* wait until operation completion */);

                switch (dwEvent)
                {
                    // operationEvents[0] was signaled, progress
                case WAIT_OBJECT_0 + 0:
                    // The report_progress call will hang when making callbacks to suspended processes so it's important that this is now on a background thread.
                    // Progress events are not queued - some will be missed if multiple progress events are fired from the ComContext to the callback 
                    // while the report_progress call is hung\in progress.
                    // Duplicate progress events can be fired if another progress event comes from the ComContext to the callback after the listener
                    // has been awaked, but before it has gotten the installProgress.
                    report_progress(operationProgress);
                    break;

                    // operationEvents[1] was signaled, operation completed
                case WAIT_OBJECT_0 + 1:
                    completionEventFired = true;
                    break;

                    // Return value is invalid.
                default:
                    THROW_LAST_ERROR();
                }
            }

I believe the culprit is in the threading logic specified inside of the constructor of ContextOrchestrator

    ContextOrchestrator::ContextOrchestrator()
    {
        ProgressCallback progress;
        m_installingWriteableSource = Repository::Source(Repository::PredefinedSource::Installing);
        m_installingWriteableSource.Open(progress);

        // Decide how many threads to use for each command.
        // We always allow only one install at a time.
        // For download, if we can find the number of supported concurrent threads,
        // use that as the maximum (up to 3); otherwise use a single thread.
        const auto supportedConcurrentThreads = std::thread::hardware_concurrency();
        const UINT32 maxDownloadThreads = 3;
        const UINT32 operationThreads = 1;
        const UINT32 downloadThreads = std::min(supportedConcurrentThreads ? supportedConcurrentThreads - 1 : 1, maxDownloadThreads);

        AddCommandQueue(COMDownloadCommand::CommandName, downloadThreads);
        AddCommandQueue(OperationCommandQueueName, operationThreads);
    }

On the Azure VM box I was testing on, which is a DS1 v2 (1vCPU and 3.5gb RAM), I was indefinitely hanging. However, on a different box with >1 vCPU's I was not getting the issue.

When debugging into the constructor, I noticed that supportedConcurrentThreads would return 1 (due to 1 vCPU) and consequently, downloadThreads would be equal to 0 (therefore causing an indefinite hang).

I think the case of supportedConcurrentThreads = 0 is handled (defaults to 1), but I did not test it so I cannot confirm with 100% certainty that this is the case.

To test a solution, I just changed the code to a hacky solution like so:

const auto supportedConcurrentThreads = std::thread::hardware_concurrency();
        const UINT32 maxDownloadThreads = 3;
        const UINT32 operationThreads = 1;
        UINT32 downloadThreads = std::min(supportedConcurrentThreads ? supportedConcurrentThreads - 1 : 1, maxDownloadThreads);
if(downloadThreads == 0)
    downloadThreads = 3; 

Note: I think ContextOrchestrator is just used for COM and as such I noted this as a COM issue. I tried to debug winget.exe and did not hit the code at all which is what led me to believe this is a COM issue.

Additional Note: I was able to do an UninstallPackageAsync without anything indefinite hang issues (reinforces my belief that downloadThreads = 0 is the culprit).

Steps to reproduce

Use an VM with only 1 vCPU and use C# InProc COM APIs (specifically something like InstallPackageAsync or DownloadPackageAsync).

Expected behavior

Download/Install COM behavior should execute.

Actual behavior

Download/Install COM behavior perpetually hangs.

Environment

Windows Package Manager v1.10.340
Copyright (c) Microsoft Corporation. All rights reserved.

Windows: Windows.Desktop v10.0.26100.3476
System Architecture: X64
Package: Microsoft.DesktopAppInstaller v1.25.340.0

Winget Directories
-----------------------------------------------------------------------------------------------------------------------
Logs                               %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\Diag…
User Settings                      %LOCALAPPDATA%\Packages\Microsoft.DesktopAppInstaller_8wekyb3d8bbwe\LocalState\sett…
Portable Links Directory (User)    %LOCALAPPDATA%\Microsoft\WinGet\Links
Portable Links Directory (Machine) C:\Program Files\WinGet\Links
Portable Package Root (User)       %LOCALAPPDATA%\Microsoft\WinGet\Packages
Portable Package Root              C:\Program Files\WinGet\Packages
Portable Package Root (x86)        C:\Program Files (x86)\WinGet\Packages
Installer Downloads                %USERPROFILE%\Downloads
Configuration Modules              %LOCALAPPDATA%\Microsoft\WinGet\Configuration\Modules

Links
---------------------------------------------------------------------------
Privacy Statement   https://aka.ms/winget-privacy
License Agreement   https://aka.ms/winget-license
Third Party Notices https://aka.ms/winget-3rdPartyNotice
Homepage            https://aka.ms/winget
Windows Store Terms https://www.microsoft.com/en-us/storedocs/terms-of-sale

Admin Setting                             State
--------------------------------------------------
LocalManifestFiles                        Disabled
BypassCertificatePinningForMicrosoftStore Disabled
InstallerHashOverride                     Disabled
LocalArchiveMalwareScanOverride           Disabled
ProxyCommandLineOptions                   Disabled
DefaultProxy                              Disabled

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area-COM-APIIssue related to COM APIIssue-BugIt either shouldn't be doing this or needs an investigation.

    Type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions