5

I'm experimenting with threading in C#, and I've created the following class as a result. I've tried to avoid any cases of race conditions, yet a deadlock occurs on use.

The class uses two different locks, one spinlock for straightforward operations, and additionally a Monitor lock to wait in case no object is ready. I originally used EventWaitHandle, but discovered that race conditions were inevitable due to WaitOne/Set precedence.

Note that Monitor.Pulse could not precede Monitor.Wait, so what else could cause a deadlock? In the case where 5 threads use a TestPool class with a capacity of 4, the deadlock always occurs at SpinLock at an irregular moment.

internal class TestPool<T> where T : class
{
    private int capacity;
    private int unitPos;
    private int waitUnitPos;
    private int waitCount;
    private int lockState;
    private object lockObj;
    private T[] units;
    private Func<T> unitFactory;

    public TestPool(int capacity, Func<T> unitFactory)
    {
        this.lockObj = new object();
        this.unitFactory = unitFactory;

        Init(capacity);
    }

    public T Fetch()
    {
        T unit;

        Lock();
        unit = (unitPos != capacity) ? units[unitPos++] : Wait();
        Unlock();

        return unit;
    }

    public void Store(T unit)
    {
        Lock();

        if (waitCount == 0)
        {
            units[--unitPos] = unit;
        }
        else
        {
            Pulse(unit);
        }

        Unlock();
    }

    private T Wait()
    {
        waitCount++;

        lock (lockObj)
        {
            Unlock();
            Monitor.Wait(lockObj);
            Lock();

            return units[--waitUnitPos];
        }
    }

    private void Pulse(T unit)
    {
        waitCount--;
        units[waitUnitPos++] = unit;

        lock (lockObj)
        {
            Monitor.Pulse(lockObj);
        }
    }

    private void Lock()
    {
        if (Interlocked.CompareExchange(ref lockState, 1, 0) != 0)
        {
            SpinLock();
        }
    }

    private void SpinLock()
    {
        SpinWait spinWait = new SpinWait();

        do
        {
            spinWait.SpinOnce();
        }
        while (Interlocked.CompareExchange(ref lockState, 1, 0) != 0);
    }

    private void Unlock()
    {
        Interlocked.Exchange(ref lockState, 0);
    }

    private void Init(int capacity)
    {
        T[] tx = new T[capacity];

        for (int i = 0; i < capacity; i++)
        {
            tx[i] = unitFactory.Invoke();
        }

        units = tx;
        this.capacity = capacity;
    }
}
Tcqqp
  • 61
  • 2
  • Spinlocks vs Mutex http://stackoverflow.com/questions/5869825/when-should-one-use-a-spinlock-instead-of-mutex Not saying this will resolve your issue, but something to look into. – JNYRanger Mar 03 '15 at 20:32
  • Are you using SetCapacity outside of the ctor? – usr Mar 03 '15 at 20:51
  • @usr - It is intended to, but I will derive a private version of it to skip locking. Note that any safeguards are intentionally absent. – Tcqqp Mar 03 '15 at 20:55
  • @usr - I've removed it to simplify the code. – Tcqqp Mar 03 '15 at 21:06

1 Answers1

0

Fixed it. I had to place the following code outside the Monitor lock.

Lock();

return units[--waitUnitPos];
Tcqqp
  • 61
  • 2