Skip to content

Commit 38e072e

Browse files
authored
Allow inproc callers to disable termination signal handlers (microsoft#5958)
## Change Allow an inproc caller to disable the termination signal handlers (CTRL+C, window messages, package updates). This is achieved by simply not creating any of those listeners when disabled. The rest of the system operates as normal, just with nothing able to trigger a shutdown signal. The caller is then responsible for cancelling any operations as necessary.
1 parent 620bb40 commit 38e072e

12 files changed

Lines changed: 127 additions & 11 deletions

File tree

src/AppInstallerCLICore/Public/ShutdownMonitoring.h

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,13 @@ namespace AppInstaller::ShutdownMonitoring
2929
// Add or remove the listener based on `enabled`.
3030
static void EnableListener(bool enabled, ICancellable* cancellable);
3131

32+
// Gets whether the signal handler is enabled.
33+
static bool Enabled();
34+
35+
// Sets whether the signal handler is enabled; the default is true.
36+
// When set to false, the signal handler instance will not create signal listeners when created.
37+
static void Enabled(bool enabled);
38+
3239
#ifndef AICLI_DISABLE_TEST_HOOKS
3340
// Gets the window handle for the message window.
3441
HWND GetWindowHandle() const;

src/AppInstallerCLICore/ShutdownMonitoring.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ using namespace std::chrono_literals;
1111

1212
namespace AppInstaller::ShutdownMonitoring
1313
{
14+
static std::atomic_bool s_TerminationSignalHandlerEnabled = true;
15+
1416
std::shared_ptr<TerminationSignalHandler> TerminationSignalHandler::Instance()
1517
{
1618
struct Singleton : public WinRT::COMStaticStorageBase<TerminationSignalHandler>
@@ -58,6 +60,16 @@ namespace AppInstaller::ShutdownMonitoring
5860
}
5961
}
6062

63+
bool TerminationSignalHandler::Enabled()
64+
{
65+
return s_TerminationSignalHandlerEnabled;
66+
}
67+
68+
void TerminationSignalHandler::Enabled(bool enabled)
69+
{
70+
s_TerminationSignalHandlerEnabled = enabled;
71+
}
72+
6173
#ifndef AICLI_DISABLE_TEST_HOOKS
6274
HWND TerminationSignalHandler::GetWindowHandle() const
6375
{
@@ -76,6 +88,12 @@ namespace AppInstaller::ShutdownMonitoring
7688
m_appShutdownEvent.create();
7789
#endif
7890

91+
if (!s_TerminationSignalHandlerEnabled)
92+
{
93+
AICLI_LOG(CLI, Info, << "TerminationSignalHandler is disabled, skipping creation of signal listeners");
94+
return;
95+
}
96+
7997
if (Runtime::IsRunningInPackagedContext())
8098
{
8199
// Create package update listener

src/AppInstallerCLIE2ETests/InprocTestbedTests.cs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,29 @@ public void TypeName_Tests(bool freeCachedFactories, bool leakCOM, UnloadBehavio
145145
});
146146
}
147147

148+
/// <summary>
149+
/// Tests that disable the termination signal handling.
150+
/// </summary>
151+
/// <param name="disableTerminationSignals">Control whether the module should listen to termination signals.</param>
152+
/// <param name="unloadBehavior">Set the unload behavior for the test.</param>
153+
/// <param name="workTestSleep">Sets the number of milliseconds to sleep between each work/test iteration.</param>
154+
[Test]
155+
[TestCase(true, UnloadBehavior.Allow, 1000)]
156+
[TestCase(true, UnloadBehavior.Never)]
157+
[TestCase(false, UnloadBehavior.Allow, 1000)]
158+
[TestCase(false, UnloadBehavior.Never)]
159+
public void TerminationSignal_Tests(bool disableTerminationSignals, UnloadBehavior unloadBehavior, int? workTestSleep = null)
160+
{
161+
this.RunInprocTestbed(new TestbedParameters()
162+
{
163+
ActivationType = ActivationType.CoCreateInstance,
164+
DisableTerminationSignals = disableTerminationSignals,
165+
UnloadBehavior = unloadBehavior,
166+
Iterations = 10,
167+
WorkTestSleepInterval = workTestSleep,
168+
});
169+
}
170+
148171
private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000)
149172
{
150173
string builtParameters = string.Empty;
@@ -184,6 +207,11 @@ private void RunInprocTestbed(TestbedParameters parameters, int timeout = 300000
184207
builtParameters += $"-work-test-sleep {parameters.WorkTestSleepInterval} ";
185208
}
186209

210+
if (parameters.DisableTerminationSignals)
211+
{
212+
builtParameters += $"-no-term ";
213+
}
214+
187215
var result = TestCommon.RunProcess(this.InprocTestbedPath, this.TargetPackageInformation, builtParameters, null, timeout, true);
188216
Assert.AreEqual(0, result.ExitCode);
189217
}
@@ -206,6 +234,8 @@ private class TestbedParameters
206234
internal int? Iterations { get; init; } = null;
207235

208236
internal int? WorkTestSleepInterval { get; init; } = null;
237+
238+
internal bool DisableTerminationSignals { get; init; } = false;
209239
}
210240
}
211241
}

src/ComInprocTestbed/PackageManager.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,11 @@ void SetUnloadPreference(bool value)
143143
PackageManagerSettings settings;
144144
settings.CanUnloadPreference(value);
145145
}
146+
void SetDisableTerminationSignals(bool value)
147+
{
148+
PackageManagerSettings settings;
149+
settings.TerminationSignalMonitoring(!value);
150+
}
146151

147152
bool DetectForSystem(const TestParameters& testParameters)
148153
{

src/ComInprocTestbed/PackageManager.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,9 @@ void InitializePackageManagerGlobals();
1313
// Sets the module to prevent it from unloading.
1414
void SetUnloadPreference(bool value);
1515

16+
// Sets the module to prevent it from listening to termination signals.
17+
void SetDisableTerminationSignals(bool value);
18+
1619
// Attempts to detect the target package as installed for the system.
1720
bool DetectForSystem(const TestParameters& testParameters);
1821

src/ComInprocTestbed/Tests.cpp

Lines changed: 29 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,15 @@ namespace
137137
}
138138
}
139139

140+
return result;
141+
}
142+
143+
// Look for the set of termination signal monitoring objects that should be present after we have spun everything up.
144+
// Returns true if all objects are found in the expected state.
145+
bool SearchForTerminationSignalObjects(bool expectExist)
146+
{
147+
bool result = true;
148+
140149
// Shutdown monitoring window
141150
bool foundWindow = false;
142151
EnumWindows(CheckForWinGetWindow, reinterpret_cast<LPARAM>(&foundWindow));
@@ -251,6 +260,10 @@ TestParameters::TestParameters(int argc, const char** argv)
251260
ADVANCE_ARG_PARAMETER
252261
WorkTestSleepInterval = atoi(argv[i]);
253262
}
263+
else if ("-no-term"sv == argv[i])
264+
{
265+
DisableTerminationSignals = true;
266+
}
254267
}
255268
}
256269

@@ -290,8 +303,6 @@ bool TestParameters::InitializeTestState() const
290303
return false;
291304
}
292305

293-
InitializePackageManagerGlobals();
294-
295306
if (UnloadBehavior::Never == UnloadBehavior || UnloadBehavior::AtUninitialize == UnloadBehavior)
296307
{
297308
SetUnloadPreference(false);
@@ -300,6 +311,18 @@ bool TestParameters::InitializeTestState() const
300311
return true;
301312
}
302313

314+
bool TestParameters::InitializeIterationState() const
315+
{
316+
InitializePackageManagerGlobals();
317+
318+
if (DisableTerminationSignals)
319+
{
320+
SetDisableTerminationSignals(true);
321+
}
322+
323+
return true;
324+
}
325+
303326
std::unique_ptr<ITest> TestParameters::CreateTest() const
304327
{
305328
if ("unload_check"sv == TestToRun)
@@ -435,7 +458,8 @@ bool UnloadAndCheckForLeaks::RunIterationTest()
435458
std::cout << "UnloadAndCheckForLeaks::RunIterationTest\n";
436459

437460
Snapshot beforeUnload;
438-
if (!SearchForWellKnownObjects(true, beforeUnload))
461+
if (!SearchForWellKnownObjects(true, beforeUnload) ||
462+
!SearchForTerminationSignalObjects(!m_parameters.DisableTerminationSignals))
439463
{
440464
return false;
441465
}
@@ -445,7 +469,8 @@ bool UnloadAndCheckForLeaks::RunIterationTest()
445469
Snapshot afterUnload;
446470
m_iterationSnapshots.emplace_back(beforeUnload, afterUnload);
447471

448-
if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload))
472+
if (!SearchForWellKnownObjects(!m_parameters.UnloadExpected(), afterUnload) ||
473+
!SearchForTerminationSignalObjects(!m_parameters.UnloadExpected() && !m_parameters.DisableTerminationSignals))
449474
{
450475
return false;
451476
}

src/ComInprocTestbed/Tests.h

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,8 @@ struct TestParameters
4949

5050
bool InitializeTestState() const;
5151

52+
bool InitializeIterationState() const;
53+
5254
std::unique_ptr<ITest> CreateTest() const;
5355

5456
void UninitializeTestState() const;
@@ -75,6 +77,7 @@ struct TestParameters
7577
ActivationType ActivationType = ActivationType::ClassName;
7678
bool SkipClearFactories = false;
7779
DWORD WorkTestSleepInterval = 0;
80+
bool DisableTerminationSignals = false;
7881
};
7982

8083
// Captures a snapshot of current resource usage.

src/ComInprocTestbed/main.cpp

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ int main(int argc, const char** argv) try
2121
{
2222
std::cout << "Begin iteration " << (i + 1) << std::endl;
2323

24+
if (!testParameters.InitializeIterationState())
25+
{
26+
return 2;
27+
}
28+
2429
if (test && !test->RunIterationWork())
2530
{
2631
return 3;

src/Microsoft.Management.Deployment/CanUnload.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
namespace winrt::Microsoft::Management::Deployment::implementation
66
{
7-
static bool s_canUnload = true;
7+
static std::atomic_bool s_canUnload = true;
88

99
void SetCanUnload(bool value)
1010
{

src/Microsoft.Management.Deployment/PackageManager.idl

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1696,12 +1696,19 @@ namespace Microsoft.Management.Deployment
16961696

16971697
[contract(Microsoft.Management.Deployment.WindowsPackageManagerContract, 28)]
16981698
{
1699-
// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not.
1700-
// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if
1701-
// there are active objects at all times. If set to true it will allow the unload when there are no
1702-
// active objects.
1703-
// Defaults to true.
1699+
/// Gets or sets a value indicating whether the caller would prefer the module to stay loaded or not.
1700+
/// This affects how the DllCanUnloadNow function called by COM behaves. If set to false it will act as if
1701+
/// there are active objects at all times. If set to true it will allow the unload when there are no
1702+
/// active objects.
1703+
/// Defaults to true.
17041704
Boolean CanUnloadPreference{ get; set; };
1705+
1706+
/// Gets or sets a value indicating whether the module should listen for termination signals (CTRL+C, window messages, package updates)
1707+
/// and begin the process of cancelling active operations and preventing new ones.
1708+
/// If set to false, the caller is responsible for handling these termination signals and cancelling active operations as necessary.
1709+
/// Set this to the desired state before any PackageManager operations. Changing it after the first operation for the process may have undefined behavior.
1710+
/// Defaults to true.
1711+
Boolean TerminationSignalMonitoring{ get; set; };
17051712
}
17061713
}
17071714

0 commit comments

Comments
 (0)