Nicollas Braga
Nicollas Braga

Reputation: 821

Why we don't get a Deadlock here?

While code reviewing myself work, I spotted a very interesting piece of code where I can swear it would cause a deadlock, but I've tested it many times, with multiple threads, and could not get any.

it's puzzling me so hard that I decided to ask it here.
So the assumption is that the LOCK doesn't lock for the same thread, but I would like to confirm it.
Following the piece of code

public class SplitService : ISplitService
{
    private IRecordService recordService;

    public SplitService(IRecordService recordService)
    {
        this.recordService = recordService;
    }

    private ConcurrentQueue<Batch> _batches = new ConcurrentQueue<Batch>();

    public void Feed(Something r)
    {
        lock (this.recordService)
        {
            if (!this.recordService.CanAppend(r))
            {
                Flush();
            }
            this.recordService.Append(r);
        }           
    }

    public void Flush()
    {
        lock (this.recordService)
        {
            if (!this.recordService.Any()) return;

            var record = this.recordService.GetBatch();
            _batches.Enqueue(record);

            this.recordService.Clean();
        }           
    }

    public IEnumerable<Batch> Get()
    {
        while (_batches.Any())
        {
            if (_batches.TryDequeue(out Batch batch))
            {
                yield return batch;
            }
        }
    }
}

As you can notice, the method Feed locks into a object, and if the same returns false for CanAppend it call the Flush method that also try to lock the same object. So I would expect a deadlock there


Extrapolating a bit after understanding it, as Lock is recursive, we can assume that this will also work:

lock(locker){           
   Console.WriteLine("Hello World");
   await new Task(() => {
        lock(locker){                   
            Console.WriteLine("Hello World from locker");
        }
   });
}

Upvotes: 2

Views: 123

Answers (2)

Sean
Sean

Reputation: 62472

Monitor objects is C# are recursive, so you just have to remember to unlock them as many times as you lock them. For example, this is perfectly valid:

lock(someObject)
{
  lock(someObject)
  {
    lock(someObject)
    {
       Consolw.WriteLine("hello world")
    }
  }
}

It's important to realize that lock is only recursive once you've acquired the lock. If thread A has acquired the lock and then thread B tries to acquire the lock then B will block until thread A releases the lock.

Upvotes: 6

For a deadlock, you need 2 accessors and 2 resources. In this case there's only one resource, so everyone will wait patiently for it.

Upvotes: 3

Related Questions