/// <summary>
        /// Retrieves active statements from the debuggee process.
        /// Shall only be called while in debug mode.
        /// Can be invoked on any thread.
        /// </summary>
        public Task <ImmutableArray <ActiveStatementDebugInfo> > GetActiveStatementsAsync(CancellationToken cancellationToken)
        {
            using (DebuggerComponent.ManagedEditAndContinueService())
            {
                // TODO: return empty outside of debug session.
                // https://github.com/dotnet/roslyn/issues/24325

                int unexpectedError = 0;
                var completion      = new TaskCompletionSource <ImmutableArray <ActiveStatementDebugInfo> >();
                var builders        = default(ArrayBuilder <ArrayBuilder <ActiveStatementDebugInfo> >);
                int pendingRuntimes = 0;
                int runtimeCount    = 0;

                var workList = DkmWorkList.Create(CompletionRoutine: _ =>
                {
                    completion.TrySetException(new InvalidOperationException($"Unexpected error enumerating active statements: 0x{unexpectedError:X8}"));
                });

                void CancelWork()
                {
                    if (builders != null)
                    {
                        FreeBuilders(builders);
                        builders = null;

                        // TODO: DkmWorkList.Cancel doesn't currently work when invoked on the completion callback.
                        // We continue execute all the queued callbacks -- they will be no-ops.
                        // See https://devdiv.visualstudio.com/DefaultCollection/DevDiv/_workitems/edit/562781.
                        //
                        // workList.Cancel();

                        // make sure we cancel with the token we received from the caller:
                        completion.TrySetCanceled(cancellationToken);
                    }
                }

                foreach (var process in DkmProcess.GetProcesses())
                {
                    foreach (var runtimeInstance in process.GetRuntimeInstances())
                    {
                        if (runtimeInstance.TagValue == DkmRuntimeInstance.Tag.ClrRuntimeInstance)
                        {
                            var clrRuntimeInstance = (DkmClrRuntimeInstance)runtimeInstance;

                            int runtimeIndex = runtimeCount;
                            runtimeCount++;

                            clrRuntimeInstance.GetActiveStatements(workList, activeStatementsResult =>
                            {
                                if (cancellationToken.IsCancellationRequested)
                                {
                                    CancelWork();
                                    return;
                                }

                                if (activeStatementsResult.ErrorCode != 0)
                                {
                                    unexpectedError = activeStatementsResult.ErrorCode;
                                    return;
                                }

                                // group active statement by instruction and aggregate flags and threads:
                                var instructionMap = PooledDictionary <ActiveInstructionId, (DkmInstructionSymbol Symbol, ArrayBuilder <Guid> Threads, int Index, ActiveStatementFlags Flags)> .GetInstance();

                                GroupActiveStatementsByInstructionId(instructionMap, activeStatementsResult.ActiveStatements);

                                int pendingStatements        = instructionMap.Count;
                                builders[runtimeIndex]       = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(pendingStatements);
                                builders[runtimeIndex].Count = pendingStatements;

                                foreach (var(instructionId, (symbol, threads, index, flags)) in instructionMap)
                                {
                                    var immutableThreads = threads.ToImmutableAndFree();

                                    symbol.GetSourcePosition(workList, DkmSourcePositionFlags.None, InspectionSession: null, sourcePositionResult =>
                                    {
                                        if (cancellationToken.IsCancellationRequested)
                                        {
                                            CancelWork();
                                            return;
                                        }

                                        int errorCode = sourcePositionResult.ErrorCode;
                                        if (errorCode != 0)
                                        {
                                            unexpectedError = errorCode;
                                        }

                                        DkmSourcePosition position;
                                        string documentNameOpt;
                                        LinePositionSpan span;
                                        if (errorCode == 0 && (position = sourcePositionResult.SourcePosition) != null)
                                        {
                                            documentNameOpt = position.DocumentName;
                                            span            = ToLinePositionSpan(position.TextSpan);
                                        }
                                        else
                                        {
                                            // The debugger can't determine source location for the active statement.
                                            // The PDB might not be available or the statement is in a method that doesn't have debug information.
                                            documentNameOpt = null;
                                            span            = default;
                                        }

                                        builders[runtimeIndex][index] = new ActiveStatementDebugInfo(
                                            instructionId,
                                            documentNameOpt,
                                            span,
                                            immutableThreads,
                                            flags);

                                        // the last active statement of the current runtime has been processed:
                                        if (Interlocked.Decrement(ref pendingStatements) == 0)
                                        {
                                            // the last active statement of the last runtime has been processed:
                                            if (Interlocked.Decrement(ref pendingRuntimes) == 0)
                                            {
                                                completion.TrySetResult(builders.ToFlattenedImmutableArrayAndFree());
                                            }
                                        }
                                    });
                                }

                                instructionMap.Free();
                            });
        /// <summary>
        /// Retrieves active statements from the debuggee process.
        /// Shall only be called while in debug mode.
        /// Can be invoked on any thread.
        /// </summary>
        public Task <ImmutableArray <ActiveStatementDebugInfo> > GetActiveStatementsAsync(CancellationToken cancellationToken)
        {
            using (DebuggerComponent.ManagedEditAndContinueService())
            {
                // TODO: return empty outside of debug session.
                // https://github.com/dotnet/roslyn/issues/24325

                var completion      = new TaskCompletionSource <ImmutableArray <ActiveStatementDebugInfo> >();
                var builders        = default(ArrayBuilder <ArrayBuilder <ActiveStatementDebugInfo> >);
                int pendingRuntimes = 0;
                int runtimeCount    = 0;

                // No exception should be thrown in case of errors on the debugger side.
                // The debugger is responsible to provide telemetry for error cases.
                // The callback should not be called, but it's there to guarantee that the task completes and a hang is avoided.
                var workList = DkmWorkList.Create(_ => { completion.TrySetResult(ImmutableArray <ActiveStatementDebugInfo> .Empty); });

                void CancelWork()
                {
                    if (builders != null)
                    {
                        FreeBuilders(builders);
                        builders = null;

                        workList.Cancel(blockOnCompletion: false);

                        // make sure we cancel with the token we received from the caller:
                        completion.TrySetCanceled(cancellationToken);
                    }
                }

                foreach (var process in DkmProcess.GetProcesses())
                {
                    foreach (var runtimeInstance in process.GetRuntimeInstances())
                    {
                        if (runtimeInstance.TagValue == DkmRuntimeInstance.Tag.ClrRuntimeInstance)
                        {
                            var clrRuntimeInstance = (DkmClrRuntimeInstance)runtimeInstance;

                            int runtimeIndex = runtimeCount;
                            runtimeCount++;

                            clrRuntimeInstance.GetActiveStatements(workList, activeStatementsResult =>
                            {
                                try
                                {
                                    if (cancellationToken.IsCancellationRequested)
                                    {
                                        CancelWork();
                                        return;
                                    }

                                    var localBuilders = builders;
                                    if (localBuilders == null) // e.g. cancelled
                                    {
                                        return;
                                    }

                                    if (activeStatementsResult.ErrorCode != 0)
                                    {
                                        localBuilders[runtimeIndex] = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(0);

                                        // the last active statement of the last runtime has been processed:
                                        if (Interlocked.Decrement(ref pendingRuntimes) == 0)
                                        {
                                            completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
                                        }

                                        return;
                                    }

                                    // group active statement by instruction and aggregate flags and threads:
                                    var instructionMap = PooledDictionary <ActiveInstructionId, (DkmInstructionSymbol Symbol, ArrayBuilder <Guid> Threads, int Index, ActiveStatementFlags Flags)> .GetInstance();

                                    GroupActiveStatementsByInstructionId(instructionMap, activeStatementsResult.ActiveStatements);

                                    int pendingStatements             = instructionMap.Count;
                                    localBuilders[runtimeIndex]       = ArrayBuilder <ActiveStatementDebugInfo> .GetInstance(pendingStatements);
                                    localBuilders[runtimeIndex].Count = pendingStatements;

                                    if (instructionMap.Count == 0)
                                    {
                                        if (Interlocked.Decrement(ref pendingRuntimes) == 0)
                                        {
                                            completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
                                        }

                                        return;
                                    }

                                    foreach (var(instructionId, (symbol, threads, index, flags)) in instructionMap)
                                    {
                                        var immutableThreads = threads.ToImmutableAndFree();

                                        symbol.GetSourcePosition(workList, DkmSourcePositionFlags.None, InspectionSession: null, sourcePositionResult =>
                                        {
                                            try
                                            {
                                                if (cancellationToken.IsCancellationRequested)
                                                {
                                                    CancelWork();
                                                    return;
                                                }

                                                DkmSourcePosition position;
                                                string documentNameOpt;
                                                LinePositionSpan span;
                                                if (sourcePositionResult.ErrorCode == 0 && (position = sourcePositionResult.SourcePosition) != null)
                                                {
                                                    documentNameOpt = position.DocumentName;
                                                    span            = ToLinePositionSpan(position.TextSpan);
                                                }
                                                else
                                                {
                                                    // The debugger can't determine source location for the active statement.
                                                    // The PDB might not be available or the statement is in a method that doesn't have debug information.
                                                    documentNameOpt = null;
                                                    span            = default;
                                                }

                                                localBuilders[runtimeIndex][index] = new ActiveStatementDebugInfo(
                                                    instructionId,
                                                    documentNameOpt,
                                                    span,
                                                    immutableThreads,
                                                    flags);

                                                // the last active statement of the current runtime has been processed:
                                                if (Interlocked.Decrement(ref pendingStatements) == 0)
                                                {
                                                    // the last active statement of the last runtime has been processed:
                                                    if (Interlocked.Decrement(ref pendingRuntimes) == 0)
                                                    {
                                                        completion.TrySetResult(localBuilders.ToFlattenedImmutableArrayAndFree());
                                                    }
                                                }
                                            }
                                            catch (Exception e)
                                            {
                                                completion.TrySetException(e);
                                            }
                                        });
                                    }

                                    instructionMap.Free();
                                }