diff --git a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h index c74b90ef49..4d9be8aedf 100644 --- a/src/AppInstallerCLICore/Public/ShutdownMonitoring.h +++ b/src/AppInstallerCLICore/Public/ShutdownMonitoring.h @@ -29,6 +29,13 @@ namespace AppInstaller::ShutdownMonitoring // Add or remove the listener based on `enabled`. static void EnableListener(bool enabled, ICancellable* cancellable); + // Gets whether the signal handler is enabled. + static bool Enabled(); + + // Sets whether the signal handler is enabled; the default is true. + // When set to false, the signal handler instance will not create signal listeners when created. + static void Enabled(bool enabled); + #ifndef AICLI_DISABLE_TEST_HOOKS // Gets the window handle for the message window. HWND GetWindowHandle() const; diff --git a/src/AppInstallerCLICore/ShutdownMonitoring.cpp b/src/AppInstallerCLICore/ShutdownMonitoring.cpp index 8972c154c2..3450e13eb9 100644 --- a/src/AppInstallerCLICore/ShutdownMonitoring.cpp +++ b/src/AppInstallerCLICore/ShutdownMonitoring.cpp @@ -11,6 +11,8 @@ using namespace std::chrono_literals; namespace AppInstaller::ShutdownMonitoring { + static std::atomic_bool s_TerminationSignalHandlerEnabled = true; + std::shared_ptr TerminationSignalHandler::Instance() { struct Singleton : public WinRT::COMStaticStorageBase @@ -58,6 +60,16 @@ namespace AppInstaller::ShutdownMonitoring } } + bool TerminationSignalHandler::Enabled() + { + return s_TerminationSignalHandlerEnabled; + } + + void TerminationSignalHandler::Enabled(bool enabled) + { + s_TerminationSignalHandlerEnabled = enabled; + } + #ifndef AICLI_DISABLE_TEST_HOOKS HWND TerminationSignalHandler::GetWindowHandle() const { @@ -76,6 +88,12 @@ namespace AppInstaller::ShutdownMonitoring m_appShutdownEvent.create(); #endif + if (!s_TerminationSignalHandlerEnabled) + { + AICLI_LOG(CLI, Info, << "TerminationSignalHandler is disabled, skipping creation of signal listeners"); + return; + } + if (Runtime::IsRunningInPackagedContext()) { // Create package update listener diff --git a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs index c6bb2f7601..841f2656b8 100644 --- a/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs +++ b/src/AppInstallerCLIE2ETests/InprocTestbedTests.cs @@ -145,6 +145,29 @@ public void TypeName_Tests(bool freeCachedFactories, bool leakCOM, UnloadBehavio }); } + /// + /// Tests that disable the termination signal handling. + /// + /// Control whether the module should listen to termination signals. + /// Set the unload behavior for the test. + /// Sets the number of milliseconds to sleep between each work/test iteration. + [Test] + [TestCase(true, UnloadBehavior.Allow, 1000)] + [TestCase(true, UnloadBehavior.Never)] + [TestCase(false, UnloadBehavior.Allow, 1000)] + [TestCase(false, UnloadBehavior.Never)] + public void TerminationSignal_Tests(bool disableTerminationSignals, UnloadBehavior unloadBehavior, int? workTestSleep = null) + { + this.RunInprocTestbed(new TestbedParameters() + { + ActivationType = ActivationType.CoCreateInstance, + DisableTerminationSignals = disableTerminationSignals, + UnloadBehavior = unloadBehavior, + Iterations = 10, + WorkTestSleepInterval = workTestSleep, + }); + } + private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000) { string builtParameters = string.Empty; @@ -184,6 +207,11 @@ private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000 builtParameters += $"-work-test-sleep {parameters.WorkTestSleepInterval} "; } + if (parameters.DisableTerminationSignals) + { + builtParameters += $"-no-term "; + } + var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true); Assert.AreEqual(0, result.ExitCode); } @@ -206,6 +234,8 @@ private class TestbedParameters internal int? Iterations { get; init; } = null; internal int? WorkTestSleepInterval { get; init; } = null; + + internal bool DisableTerminationSignals { get; init; } = false; } } } diff --git a/src/ComInprocTestbed/PackageManager.cpp b/src/ComInprocTestbed/PackageManager.cpp index bb4e0dc98a..574ae4b1ed 100644 --- a/src/ComInprocTestbed/PackageManager.cpp +++ b/src/ComInprocTestbed/PackageManager.cpp @@ -143,6 +143,11 @@ void SetUnloadPreference(bool value) PackageManagerSettings settings; settings.CanUnloadPreference(value); } +void SetDisableTerminationSignals(bool value) +{ + PackageManagerSettings settings; + settings.TerminationSignalMonitoring(!value); +} bool DetectForSystem(const TestParameters& testParameters) { diff --git a/src/ComInprocTestbed/PackageManager.h b/src/ComInprocTestbed/PackageManager.h index f589d21371..0ad82e64dd 100644 --- a/src/ComInprocTestbed/PackageManager.h +++ b/src/ComInprocTestbed/PackageManager.h @@ -13,6 +13,9 @@ void InitializePackageManagerGlobals(); // Sets the module to prevent it from unloading. void SetUnloadPreference(bool value); +// Sets the module to prevent it from listening to termination signals. +void SetDisableTerminationSignals(bool value); + // Attempts to detect the target package as installed for the system. bool DetectForSystem(const TestParameters& testParameters); diff --git a/src/ComInprocTestbed/Tests.cpp b/src/ComInprocTestbed/Tests.cpp index 8244403f15..95244506af 100644 --- a/src/ComInprocTestbed/Tests.cpp +++ b/src/ComInprocTestbed/Tests.cpp @@ -137,6 +137,15 @@ namespace } } + return result; + } + + // Look for the set of termination signal monitoring objects that should be present after we have spun everything up. + // Returns true if all objects are found in the expected state. + bool SearchForTerminationSignalObjects(bool expectExist) + { + bool result = true; + // Shutdown monitoring window bool foundWindow = false; EnumWindows(CheckForWinGetWindow, reinterpret_cast(&foundWindow)); @@ -251,6 +260,10 @@ TestParameters::TestParameters(int argc, const char** argv) ADVANCE_ARG_PARAMETER WorkTestSleepInterval = atoi(argv[i]); } + else if ("-no-term"sv == argv[i]) + { + DisableTerminationSignals = true; + } } } @@ -290,8 +303,6 @@ bool TestParameters::InitializeTestState() const return false; } - InitializePackageManagerGlobals(); - if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior) { SetUnloadPreference(false); @@ -300,6 +311,18 @@ bool TestParameters::InitializeTestState() const return true; } +bool TestParameters::InitializeIterationState() const +{ + InitializePackageManagerGlobals(); + + if (DisableTerminationSignals) + { + SetDisableTerminationSignals(true); + } + + return true; +} + std::unique_ptr TestParameters::CreateTest() const { if ("unload_check"sv == TestToRun) @@ -435,7 +458,8 @@ bool UnloadAndCheckForLeaks::RunIterationTest() std::cout << "UnloadAndCheckForLeaks::RunIterationTest\n"; Snapshot beforeUnload; - if (!SearchForWellKnownObjects(true, beforeUnload)) + if (!SearchForWellKnownObjects(true, beforeUnload) || + !SearchForTerminationSignalObjects(!m_parameters.DisableTerminationSignals)) { return false; } @@ -445,7 +469,8 @@ bool UnloadAndCheckForLeaks::RunIterationTest() Snapshot afterUnload; m_iterationSnapshots.emplace_back(beforeUnload, afterUnload); - if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload)) + if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload) || + !SearchForTerminationSignalObjects(!m_parameters.UnloadExpected() && !m_parameters.DisableTerminationSignals)) { return false; } diff --git a/src/ComInprocTestbed/Tests.h b/src/ComInprocTestbed/Tests.h index 5e8afc0682..7de44c4af1 100644 --- a/src/ComInprocTestbed/Tests.h +++ b/src/ComInprocTestbed/Tests.h @@ -49,6 +49,8 @@ struct TestParameters bool InitializeTestState() const; + bool InitializeIterationState() const; + std::unique_ptr CreateTest() const; void UninitializeTestState() const; @@ -75,6 +77,7 @@ struct TestParameters ActivationType ActivationType = ActivationType::ClassName; bool SkipClearFactories = false; DWORD WorkTestSleepInterval = 0; + bool DisableTerminationSignals = false; }; // Captures a snapshot of current resource usage. diff --git a/src/ComInprocTestbed/main.cpp b/src/ComInprocTestbed/main.cpp index 50512a2e9a..1210ba0ae7 100644 --- a/src/ComInprocTestbed/main.cpp +++ b/src/ComInprocTestbed/main.cpp @@ -21,6 +21,11 @@ int main(int argc, const char** argv) try { std::cout << "Begin iteration " << (i + 1) << std::endl; + if (!testParameters.InitializeIterationState()) + { + return 2; + } + if (test && !test->RunIterationWork()) { return 3; diff --git a/src/Microsoft.Management.Deployment/CanUnload.cpp b/src/Microsoft.Management.Deployment/CanUnload.cpp index 20e53ebaf6..bc03b3d027 100644 --- a/src/Microsoft.Management.Deployment/CanUnload.cpp +++ b/src/Microsoft.Management.Deployment/CanUnload.cpp @@ -4,7 +4,7 @@ namespace winrt::Microsoft::Management::Deployment::implementation { - static bool s_canUnload = true; + static std::atomic_bool s_canUnload = true; void SetCanUnload(bool value) { diff --git a/src/Microsoft.Management.Deployment/PackageManager.idl b/src/Microsoft.Management.Deployment/PackageManager.idl index 5bb9f196f2..da4976c3ac 100644 --- a/src/Microsoft.Management.Deployment/PackageManager.idl +++ b/src/Microsoft.Management.Deployment/PackageManager.idl @@ -1696,12 +1696,19 @@ namespace Microsoft.Management.Deployment [contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)] { - // Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. - // This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if - // there are active objects at all times. If set to true it will allow the unload when there are no - // active objects. - // Defaults to true. + /// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not. + /// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if + /// there are active objects at all times. If set to true it will allow the unload when there are no + /// active objects. + /// Defaults to true. Boolean CanUnloadPreference{ get; set; }; + + /// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates) + /// and begin the process of cancelling active operations and preventing new ones. + /// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary. + /// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior. + /// Defaults to true. + Boolean TerminationSignalMonitoring{ get; set; }; } } diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp index 65d58d1314..2cd2ac00cb 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.cpp @@ -12,6 +12,7 @@ #include "PackageManagerSettings.g.cpp" #include "Helpers.h" #include "Public/CanUnload.h" +#include "Public/ShutdownMonitoring.h" #include #include @@ -65,7 +66,17 @@ namespace winrt::Microsoft::Management::Deployment::implementation void PackageManagerSettings::CanUnloadPreference(bool value) { - return SetCanUnload(value); + SetCanUnload(value); + } + + bool PackageManagerSettings::TerminationSignalMonitoring() const + { + return AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(); + } + + void PackageManagerSettings::TerminationSignalMonitoring(bool value) + { + AppInstaller::ShutdownMonitoring::TerminationSignalHandler::Enabled(value); } CoCreatableMicrosoftManagementDeploymentClass(PackageManagerSettings); diff --git a/src/Microsoft.Management.Deployment/PackageManagerSettings.h b/src/Microsoft.Management.Deployment/PackageManagerSettings.h index 10bed57ac6..e258953d7f 100644 --- a/src/Microsoft.Management.Deployment/PackageManagerSettings.h +++ b/src/Microsoft.Management.Deployment/PackageManagerSettings.h @@ -20,6 +20,8 @@ namespace winrt::Microsoft::Management::Deployment::implementation // Contract 28 bool CanUnloadPreference() const; void CanUnloadPreference(bool value); + bool TerminationSignalMonitoring() const; + void TerminationSignalMonitoring(bool value); }; }