Skip to content

Conversation

JohnMcPMS
Copy link
Member

@JohnMcPMS JohnMcPMS commented Aug 11, 2025

Issue

When the package needs to be updated, the COM server does not currently shut down in response to the signals that it receives. This is because it only shuts down when all COM object references have been released. We need to be more proactive in termination as the process will be terminated shortly after we are notified either way.

Change

This change refactors the existing shutdown signaling code out from ExecutionContext.cpp into its own, public location. It then adds another system for coordinating complex shutdown that is triggered by the signaler.

Individual subsystems can register with the shutdown synchronizer with functions for:

  • Disable new work
  • Cancel current work
  • Wait for quiesce

and on shutdown initialization, all subsystems are called for each stage in order. When the final subsystem returns from waiting, the existing "all COM objects have been released" event is signaled and the process will exit.

Additionally, the shutdown signal handler waits for shutdown synchronization to complete in the window message function as after we return from that we are expected to be gone shortly after. While this will lead to some increased number of non-quiescing processes, we want to do the best that we can to cooperatively exit.

Note

Any actions being taken by callers outside of those described below will NOT block the process from exiting.

Package Management

Registers with synchronization on PackageManager creation. All of its functions lead back to the ContextOrchestrator which has been enlightened to handle the shutdown (and to operate as a contained set of instances for test purposes).

  • Disable new work causes the orchestrator to refuse to queue items with an exception for new items and a cancellation for existing ones.
  • Cancel current work causes all queued items to be cancelled, with different handling based on current status.
  • Wait for quiesce waits on an event for each queue that is signaled when the queue is empty.

Configuration

Registers with synchronization on ConfigurationStaticFunctionsShim creation (the IConfigurationStatics for OOP use). All of its functions are routed through IConfigurationStaticsInternals to a new shutdown handler. It tracks all of the async token objects used in the "background" sections of the ConfigurationProcessor functions. This tracking is performed automatically by new types that manage that tracking with the lifetime. It also enables non-async invocations to be cancelled by providing a dummy promise into the existing system.

  • Disable new work causes an error whenever a new async token object is created.
  • Cancel current work causes all tracked async tokens to have their Cancel function invoked.
  • Wait for quiesce waits until all async tokens are destroyed.

Validation

New tests are added for the ContextOrchestrator, the package management shutdown handling, and the configuration shutdown handling.

Microsoft Reviewers: Open in CodeFlow

This comment was marked as outdated.

yao-msft
yao-msft previously approved these changes Aug 11, 2025
@@ -968,6 +972,30 @@ Global
{9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x64.Build.0 = ReleaseStatic|Any CPU
{9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.ActiveCfg = ReleaseStatic|Any CPU
{9406322E-6272-487E-902A-9953889719EA}.ReleaseStatic|x86.Build.0 = ReleaseStatic|Any CPU
{7139ED6E-8FBC-0B61-3E3A-AA2A23CC4D6A}.Debug|ARM64.ActiveCfg = Debug|x64
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: through all the new entries, I think only debug/release x64/x86 need Build.0 entries, all other combinations should be set to "not build"

Copy link
Member Author

Choose a reason for hiding this comment

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

Agree on the configurations, disagree on removing from the ARM64 build (but updated things to make that work).


// Waits for running items to complete; waits up to full time out in *each* queue.
// Returns true to indicate all queues are empty before the timeout.
// Intended for test use.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should we wrap around AICLI_DISABLE_TESTES if it's just for test purpose?


// Waits until the empty queue event is signaled.
// Returns true to indicate the queue is empty before the timeout.
// Intended for tests.
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: should we wrap around AICLI_DISABLE_TESTES if it's just for test purpose?

/// <summary>
/// Gets the process for the server.
/// </summary>
public required Process Process { get; init; }
Copy link
Member

Choose a reason for hiding this comment

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

I didn't know required was a thing. That's cool

Comment on lines +217 to +222
bool ContextOrchestrator::WaitForRunningItems(DWORD timeoutMilliseconds)
{
std::lock_guard<std::mutex> lock{ m_queueLock };
for (const auto& queue : m_commandQueues)
{
if (!queue.second->WaitForEmptyQueue(timeoutMilliseconds))
Copy link
Member

Choose a reason for hiding this comment

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

Nit: I would expect this function to wait for all items at most timeoutMilliseconds, but it waits that long for each of them. This may be moot depending on how many command queues we have or how big the timeout is, though.

std::string OrchestratorQueue::GetStatusString()
{
std::ostringstream stream;
stream << m_commandName << '[' << m_allowedThreads << "]\n";
Copy link
Member

Choose a reason for hiding this comment

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

nit: endl

@JohnMcPMS JohnMcPMS merged commit d0096b3 into microsoft:master Aug 13, 2025
9 checks passed
@JohnMcPMS JohnMcPMS deleted the quiesce branch August 13, 2025 21:34
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants