The Wiert Corner – irregular stream of stuff

Jeroen W. Pluimers on .NET, C#, Delphi, databases, and personal interests

  • My badges

  • Twitter Updates

  • My Flickr Stream

  • Pages

  • All categories

  • Enter your email address to subscribe to this blog and receive notifications of new posts by email.

    Join 1,860 other subscribers

In potentially multi-threaded .NET Console applications, ensure `Console.EnsureInitialized` gets called for at least output, and potentially for input

Posted by jpluimers on 2024/07/02

An interesting issue at [Wayback/Archive] Khalid ⚡️: “I just used #JetBrainsRider to find a deadlock scenario in #dotnet that I would not have guessed would deadlock. The Console needs to be initialized since the initialization uses a lock the first time. Using it in parallel tasks causes deadlocks. #dotnet This is excellent tooling!…” – Mastodon.

https://i0.wp.com/web.archive.org/web/20240626133154if_/https%3A//files.mastodon.social/media_attachments/files/112/683/097/702/613/855/original/3c11b46a77d3a1fd.png

[Wayback/Archive] 3c11b46a77d3a1fd.png (2800×1610)

It boils down to a non-public Console.EnsureInitialized method being called from multiple threads causes deadlocks. So far, it looks it can only be called as part of referring to Console.In or Console.Out.

I could only find one potentially related bug, which is [Wayback/Archive] NativeRuntimeEventSource behaving poorly in conjunction with other providers · Issue #88011 · dotnet/runtime · GitHub and being worked on, for .NET 9 or later:

Consider this repro:
using System.Diagnostics.Tracing;

using var _ = new MyListener();
while (true)
{
    new List<int>();
}

internal class MyListener : EventListener
{
    protected override void OnEventSourceCreated(EventSource eventSource)
    {
        if (eventSource.Name is "Microsoft-Windows-DotNETRuntime"

                                // Uncomment either or both of these and everything still works fine
                             //or "System.Buffers.ArrayPoolEventSource"
                             //or "System.Diagnostics.Eventing.FrameworkEventSource"

                                // Uncomment this, and no events are output
                             //or "System.Runtime"

                                // Uncomment this, and the app stack overflows
                             //or "System.Threading.Tasks.TplEventSource"
                             )
        {
            EnableEvents(eventSource, EventLevel.Informational, EventKeywords.All);
        }
    }

    protected override void OnEventWritten(EventWrittenEventArgs eventData) =>
        Console.WriteLine(eventData.EventName);
}
Run it, and it correctly spews lots of GC-related events to the console.
Uncomment either or both of the ArrayPoolEventSource and/or FrameworkEventSource lines, and everything still outputs as expected.
But if you uncomment the System.Runtime line, no events get output at all.
And if you uncomment the TplEventSource line, the app crashes.

Enabling Microsoft-Windows-DotNETRuntime and System.Runtime causes a deadlock because one thread is holding the AppContext lock and trying to call Console.Out.EnsureInitialized, and the other is in Console.Out.EnsureInitialized trying to get the AppContext lock

This is a recurring theme for EventSource – we call out to user code at times that is hard to reason about and potentially dangerous. This particular example could be avoided by calling Console.Write before trying to initialize any EventSources, but we see this type of issue repeatedly.
I don’t think we’ll be able to address this in 8 but longer term we should consider anything we can do to avoid these types of deadlocks.

The stack overflow for TPLEventSource is due to the jitting of a method in Console.EnsureInitialized triggering a jit, which triggers an ActivityID to be created and an event to be fired, then calls in to the eventsource and when it tries to log to the console it triggers a new jit for the same method because the previous jit is blocked waiting for the previous event
This stack is repeated until the overflow. The method we are trying to jit is System.Text.EncodingHelper.GetSupportedConsoleEncoding(Int32)

Moving this to 9

This means it won’t be fixed before .NET Version 9 (and maybe even later).

At first I thought that [Wayback/Archive] System.Text Exception cannot be caught in NativeAOT when Reflection is disabled, but is solved with a Console.WriteLine() · Issue #100888 · dotnet/runtime · GitHub was related, but it also reproduces without using Async. However, it does emphasise that the current Console.EnsureInitialised is brittle..

Queries:

The fix is to send output to the console before the other threads start:

[Wayback/Archive] 97c21f382f1d0f26.png (1552×572)

--jeroen

Leave a comment

This site uses Akismet to reduce spam. Learn how your comment data is processed.