runtime: Freeze/lock of dotnet process in net 5 mac

Description

Hello,

I have a lock/freeze on dotnet using Threads, BlockingCollection and a while loop. If I put wait in the last loop, it will not freeze/lock This lock happened in the latest stable Visual Studio on Mac : 8.10.10 (build 8)

Reproduction Steps

using System;
using System.Collections.Concurrent;
using System.Linq;
using System.Threading;

namespace multithread
{
    public class HighPriorityFile
    {
        public int Index { get; private set; }
        public bool Processed { get; set; }
        public HighPriorityFile(int index)
        {
            Index = index;
            Processed = false;
        }
    }

    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("Hello World!");

            // prepare the thing to do in multithread
            int npages = 10;
            var pages = new HighPriorityFile[npages];
            for (var pagesCounter = 0; pagesCounter < npages; pagesCounter++)
            {
                pages[pagesCounter] = new HighPriorityFile(pagesCounter);
            }
            
            // put indexes in a BlockingCollection
            var pagesToConvert = new BlockingCollection<int>(new ConcurrentQueue<int>(pages.Select(p=>p.Index)));
            pagesToConvert.CompleteAdding();

            var threadsConvertCount = 2;
            var threadsConvert = new Thread[threadsConvertCount];

            for (var convertCount = 0; convertCount < threadsConvertCount; convertCount++)
            {
                // start the threads
                threadsConvert[convertCount] = new Thread(() =>
                {
                    while (pagesToConvert.TryTake(out int pageIndex))
                    {
                        // simulate work to do...
                        Thread.Sleep(1000);

                        pages[pageIndex].Processed = true;
                        Console.WriteLine($"Convert page {pageIndex} - ${Thread.CurrentThread.Name}");
                    }
                }
                )
                { 
                    Name=$"Thread_FileConverter {convertCount}"
                };
                threadsConvert[convertCount].Start();
            }


            var i = 0;


            // this loop is responsible for the lock/freeze
            // ok this way to do is not a best practice, but it shouldn't lock/freeze
            // if you run it, it will lock/freeze.
            // if you put a breakpoint in the first line in the while, and do a step by step it will go to the end
            // if you uncomment the Thread.Sleep(1), and run, il will go to the end.
            i = 0;
            while (i < npages)
            {
                if (pages[i].Processed)
                {
                    Console.WriteLine($"Create page {pages[i].Index}");
                    pages[i] = null;
                    i++;
                }
                else
                {
                    //Thread.Sleep(1);
                }
            }


            for (i = 0; i < threadsConvertCount; i++)
                threadsConvert[i].Join();

            

            Console.WriteLine("finished");
        }
    }
}

Expected behavior

the process should go to the end and write in the console log Like this : Hello World! Convert page 1 - $Thread_FileConverter 0 Convert page 2 - $Thread_FileConverter 3 Convert page 0 - $Thread_FileConverter 1 Convert page 3 - $Thread_FileConverter 2 Create page 0 Create page 1 Create page 2 Create page 3 Convert page 4 - $Thread_FileConverter 0 Convert page 6 - $Thread_FileConverter 1 Convert page 5 - $Thread_FileConverter 3 Convert page 7 - $Thread_FileConverter 2 Create page 4 Create page 5 Create page 6 Create page 7 Convert page 8 - $Thread_FileConverter 0 Create page 8 Convert page 9 - $Thread_FileConverter 1 Create page 9 finished

Actual behavior

this code lock/freeze the dotnet process run at 100% of cpu, break in visual studio do nothing, and stop keep the 100%cpu dotnet process running

Regression?

No response

Known Workarounds

put a thread.sleep in the last while resolve the freeze/lock

Configuration

=== Visual Studio Community 2019 for Mac ===

Version 8.10.10 (build 8) Installation UUID: a3923c5c-dae0-47f1-a41f-fef3c1dd9e5e GTK+ 2.24.23 (Raleigh theme ) Xamarin.Mac 6.18.0.23 (d16-6 / 088c73638)

Package version: 612000140

=== Mono Framework MDK ===

Runtime: Mono 6.12.0.140 (2020-02/51d876a041e) (64-bit) Package version: 612000140

=== Roslyn (Language Service) ===

3.10.0-4.21269.26+029847714208ebe49668667c60ea5b0a294e0fcb

=== NuGet ===

Version: 5.9.0.7134

=== .NET Core SDK ===

SDK: /usr/local/share/dotnet/sdk/5.0.401/Sdks SDK Versions: 5.0.401 5.0.400 3.1.413 3.1.402 MSBuild SDKs: /Applications/Visual Studio.app/Contents/Resources/lib/monodevelop/bin/MSBuild/Current/bin/Sdks

=== .NET Core Runtime ===

Runtime: /usr/local/share/dotnet/dotnet Runtime Versions: 5.0.10 5.0.9 3.1.19 3.1.8 2.1.23 2.1.22

=== Updater ===

Version: 11

=== Apple Developer Tools ===

Xcode 11.6 (16141) Build 11E708

=== Build Information ===

Release ID: 810100008 Git revision: a3ff4b6e658e1f94623e1f3ed34ca94ed4fe78d8 Build date: 2021-09-23 19:50:51-04 Build branch: release-8.10

=== Operating System ===

Mac OS X 10.15.6

Other information

No response

About this issue

  • Original URL
  • State: closed
  • Created 3 years ago
  • Reactions: 1
  • Comments: 19 (12 by maintainers)

Most upvoted comments

is there a canonical way to do this?

From skimming the thread, the request is basically a way to process a sequence in parallel but consume the results serially in the original order as they’re available?

That necessitates a reordering buffer that can track completed results by original index and yield the next one in the original order when it’s available. Both PLINQ and the Dataflow library include such support.

With PLINQ, it might look like:

IEnumerable<int> output = from i in ParallelEnumerable.Range(0, 100).AsOrdered().WithMergeOptions(ParallelMergeOptions.NotBuffered).WithDegreeOfParallelism(2)
                          select Process(i);

foreach (int item in output)
{
    Console.WriteLine(item);
}

static int Process(int input)
{
    // Simulate some work
    Thread.Sleep(Random.Shared.Next(1, 1000));
    return input * 2;
}

With Dataflow, it might look like:

using System.Threading.Tasks.Dataflow;

var transform = new TransformBlock<int, int>(input => Process(input), new ExecutionDataflowBlockOptions { MaxDegreeOfParallelism = 2 });

for (int i = 0; i < 100; i++)
{
    transform.Post(i);
}

await foreach (int item in transform.ReceiveAllAsync())
{
    Console.WriteLine(item);
}

static int Process(int input)
{
    // Simulate some work
    Thread.Sleep(Random.Shared.Next(1, 1000));
    return input * 2;
}

@echesakovMSFT PTAL. cc @dotnet/jit-contrib (corrected echesakov with echesakovMSFT)