public void TestNoBlockWithZeroWaiterCount()
        {
            PartialThreadBlocker inst = new PartialThreadBlocker();

            Assert.IsTrue(inst.Wait(1));
            Assert.IsTrue(inst.Wait(1));
        }
        public void TestBlockRequiredCount()
        {
            PartialThreadBlocker inst = new PartialThreadBlocker(4);
            Barrier startBar          = new Barrier(8 + 1);
            int     exitedCount       = 0;


            for (int i = 0; i < 8; i++)
            {
                Task.Run(() =>
                {
                    startBar.SignalAndWait();
                    inst.Wait();
                    Interlocked.Increment(ref exitedCount);
                });
            }

            startBar.SignalAndWait();
            Assert.IsTrue(SpinWait.SpinUntil(() => Volatile.Read(ref exitedCount) >= 4 && inst.RealWaiterCount >= 4, 4000));

            TimingAssert.AreEqual(5000, 8 - 4, () => Volatile.Read(ref exitedCount), "exitedCount != 8");
            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount, "ExpectedWaiterCount != 4");
            TimingAssert.AreEqual(5000, 4, () => inst.RealWaiterCount, "RealWaiterCount != 4");

            inst.SetExpectedWaiterCount(0);

            TimingAssert.AreEqual(5000, 8, () => Volatile.Read(ref exitedCount), "exitedCount != 8");
            TimingAssert.AreEqual(5000, 0, () => inst.ExpectedWaiterCount, "ExpectedWaiterCount != 0");
            TimingAssert.AreEqual(5000, 0, () => inst.RealWaiterCount, "RealWaiterCount != 0");
        }
        public void TestCancellationWork()
        {
            PartialThreadBlocker    inst   = new PartialThreadBlocker(4);
            CancellationTokenSource tokSrc = new CancellationTokenSource();
            int exitedCount = 0;

            Task.Run(() =>
            {
                try
                {
                    inst.Wait(tokSrc.Token);
                }
                catch (OperationCanceledException)
                {
                }
                Interlocked.Increment(ref exitedCount);
            });


            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 1, () => inst.RealWaiterCount);

            tokSrc.Cancel();

            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 0, () => inst.RealWaiterCount);
            TimingAssert.AreEqual(5000, 1, () => Volatile.Read(ref exitedCount));
        }
        public void TestSingleDecreaseWork()
        {
            PartialThreadBlocker inst = new PartialThreadBlocker(4);
            Barrier startBar          = new Barrier(1 + 1);
            int     exitedCount       = 0;

            Task.Run(() =>
            {
                startBar.SignalAndWait();
                inst.Wait();
                Interlocked.Increment(ref exitedCount);
            });

            startBar.SignalAndWait();

            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 1, () => inst.RealWaiterCount, "RealWaiterCount != 1");

            for (int i = 1; i <= 4; i++)
            {
                inst.SubstractExpectedWaiterCount(1);
                TimingAssert.AreEqual(5000, 4 - i, () => inst.ExpectedWaiterCount);
                TimingAssert.AreEqual(5000, i == 4 ? 0 : 1, () => inst.RealWaiterCount);
            }

            TimingAssert.AreEqual(5000, 1, () => Volatile.Read(ref exitedCount), "exitedCount != 1");
        }
        public void TestIncreaseWaiters()
        {
            PartialThreadBlocker inst        = new PartialThreadBlocker(4);
            Barrier startBar                 = new Barrier(8 + 1);
            int     exitedCount              = 0;
            int     somethingWork            = 0;
            CancellationTokenSource tokenSrc = new CancellationTokenSource();

            for (int i = 0; i < 8; i++)
            {
                Task.Run(() =>
                {
                    startBar.SignalAndWait();
                    while (!tokenSrc.IsCancellationRequested)
                    {
                        inst.Wait();
                        Interlocked.Increment(ref somethingWork);
                        Thread.Sleep(10);
                    }
                    Interlocked.Increment(ref exitedCount);
                });
            }

            startBar.SignalAndWait();

            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 4, () => inst.RealWaiterCount, "Real waiter count != 4 (can be caused by slow processing)");
            TimingAssert.IsTrue(5000, () => Volatile.Read(ref somethingWork) > 0);

            inst.SetExpectedWaiterCount(8);
            Assert.AreEqual(8, inst.ExpectedWaiterCount);

            TimingAssert.AreEqual(5000, 8, () => inst.RealWaiterCount);
            Interlocked.Exchange(ref somethingWork, 0);

            TimingAssert.IsTrue(5000, () => Volatile.Read(ref somethingWork) == 0);

            tokenSrc.Cancel();
            inst.SetExpectedWaiterCount(0);

            TimingAssert.AreEqual(5000, 8, () => Volatile.Read(ref exitedCount));
            TimingAssert.AreEqual(5000, 0, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(500, 0, () => inst.RealWaiterCount);
        }
        public void TestTimeoutWork()
        {
            PartialThreadBlocker inst = new PartialThreadBlocker(4);
            int  exitedCount          = 0;
            bool exitByTimeout        = false;

            Task.Run(() =>
            {
                Volatile.Write(ref exitByTimeout, !inst.Wait(1500));
                Interlocked.Increment(ref exitedCount);
            });


            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 1, () => inst.RealWaiterCount);


            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 0, () => inst.RealWaiterCount);
            TimingAssert.AreEqual(5000, 1, () => Volatile.Read(ref exitedCount));
            TimingAssert.IsTrue(5000, () => Volatile.Read(ref exitByTimeout));
        }
        public void TestCycledBlockRequiredCount()
        {
            PartialThreadBlocker inst        = new PartialThreadBlocker(4);
            Barrier startBar                 = new Barrier(8 + 1);
            int     exitedCount              = 0;
            int     somethingWork            = 0;
            CancellationTokenSource tokenSrc = new CancellationTokenSource();

            for (int i = 0; i < 8; i++)
            {
                Task.Run(() =>
                {
                    startBar.SignalAndWait();
                    while (!tokenSrc.IsCancellationRequested)
                    {
                        inst.Wait();
                        Interlocked.Increment(ref somethingWork);
                        Thread.Sleep(10);
                    }
                    Interlocked.Increment(ref exitedCount);
                });
            }

            startBar.SignalAndWait();

            TimingAssert.AreEqual(5000, 4, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 4, () => inst.RealWaiterCount);
            TimingAssert.IsTrue(5000, () => Volatile.Read(ref somethingWork) > 0);

            tokenSrc.Cancel();
            inst.SetExpectedWaiterCount(0);

            TimingAssert.AreEqual(5000, 8, () => Volatile.Read(ref exitedCount));
            TimingAssert.AreEqual(5000, 0, () => inst.ExpectedWaiterCount);
            TimingAssert.AreEqual(5000, 0, () => inst.RealWaiterCount);
        }
        public void TestFullCycle()
        {
            PartialThreadBlocker inst        = new PartialThreadBlocker(0);
            Barrier startBar                 = new Barrier(8 + 1);
            int     exitedCount              = 0;
            CancellationTokenSource tokenSrc = new CancellationTokenSource();

            Thread[] threads = new Thread[8];

            Func <int> sleepThCount = () =>
            {
                int result = 0;
                foreach (var th in threads)
                {
                    if ((th.ThreadState & ThreadState.WaitSleepJoin) != 0)
                    {
                        result++;
                    }
                }
                return(result);
            };

            for (int i = 0; i < threads.Length; i++)
            {
                threads[i] = new Thread(() =>
                {
                    startBar.SignalAndWait();
                    while (!tokenSrc.IsCancellationRequested)
                    {
                        inst.Wait();
                        SpinWaitHelper.SpinWait(1200);
                        Thread.Yield();
                    }
                    Interlocked.Increment(ref exitedCount);
                });

                threads[i].Start();
            }

            startBar.SignalAndWait();

            for (int i = 0; i < threads.Length; i++)
            {
                TimingAssert.AreEqual(5000, i, sleepThCount, "UP. Sleeping thread count != i");
                TimingAssert.AreEqual(5000, i, () => inst.RealWaiterCount, "UP. RealWaiterCount != i");
                TimingAssert.AreEqual(5000, i, () => inst.ExpectedWaiterCount, "UP. ExpectedWaiterCount != i");
                inst.AddExpectedWaiterCount(1);
                Thread.Sleep(10);
            }

            TimingAssert.AreEqual(5000, threads.Length, sleepThCount);
            TimingAssert.AreEqual(5000, threads.Length, () => inst.RealWaiterCount);
            TimingAssert.AreEqual(5000, threads.Length, () => inst.ExpectedWaiterCount);


            for (int i = threads.Length; i > 0; i--)
            {
                TimingAssert.AreEqual(5000, i, sleepThCount);
                TimingAssert.AreEqual(5000, i, () => inst.RealWaiterCount);
                TimingAssert.AreEqual(5000, i, () => inst.ExpectedWaiterCount);
                inst.SubstractExpectedWaiterCount(1);
                Thread.Sleep(10);
            }

            TimingAssert.AreEqual(5000, 0, sleepThCount, "Sleeping thread count != 0");
            TimingAssert.AreEqual(5000, 0, () => inst.RealWaiterCount, "RealWaiterCount != 0");
            TimingAssert.AreEqual(5000, 0, () => inst.ExpectedWaiterCount, "ExpectedWaiterCount != 0");


            tokenSrc.Cancel();

            TimingAssert.AreEqual(5000, threads.Length, () => Volatile.Read(ref exitedCount));
        }
        private void UniversalThreadProc(ThreadPrivateData privateData, CancellationToken token)
        {
            if (privateData == null)
            {
                throw new InvalidOperationException("privateData for Thread of ThreadPool can't be null");
            }


            ThreadPoolWorkItem currentWorkItem = null;
            int lastViewedActiveThreadCount    = 0;

            try
            {
                while (!token.IsCancellationRequested)
                {
                    if (!_extThreadBlocker.Wait(_noWorkItemTrimPeriod, token))
                    {
                        if (RequestDieSlot(_minThreadCount))
                        {
                            //Console.WriteLine("Thread exit due to staying deactivated");
                            break;
                        }
                        // Иначе активируемся
                        ActivateThread();
                    }

                    bool itemTaken = this.TryTakeWorkItemFromQueue(privateData, out currentWorkItem, 0, new CancellationToken(), false);
                    if (itemTaken == false)
                    {
                        lastViewedActiveThreadCount = this.ActiveThreadCount;
                        // this.ActiveThreadCount <= _reasonableThreadCount - возможна гонка, но нам не критично
                        if (lastViewedActiveThreadCount <= _reasonableThreadCount)
                        {
                            itemTaken = this.TryTakeWorkItemFromQueue(privateData, out currentWorkItem, _noWorkItemTrimPeriod, token, false);
                        }
                        else
                        {
                            itemTaken = this.TryTakeWorkItemFromQueue(privateData, out currentWorkItem, NoWorkItemPreventDeactivationPeriod, token, false);
                        }
                    }

                    if (itemTaken)
                    {
                        this.RunWorkItem(currentWorkItem);
                        currentWorkItem = null;

                        _throughoutTracker.RegisterExecution();
                        if (_wasSomeProcessByThreadsFlag == false)
                        {
                            _wasSomeProcessByThreadsFlag = true;
                        }
                    }
                    else if (!token.IsCancellationRequested)
                    {
                        if (lastViewedActiveThreadCount <= _reasonableThreadCount)
                        {
                            if (this.PrimaryThreadCount > _fastSpawnThreadCountLimit)
                            {
                                DeactivateThread(_fastSpawnThreadCountLimit);
                            }
                            else
                            {
                                DeactivateThread(_minThreadCount);
                            }
                        }
                        else
                        {
                            DeactivateThread(_reasonableThreadCount);
                        }

                        //Console.WriteLine("Thread self deactivation due to empty queue");
                    }
                }
            }
            catch (OperationCanceledException)
            {
                if (!token.IsCancellationRequested)
                {
                    throw;
                }
            }


            if (token.IsCancellationRequested)
            {
                if (LetFinishedProcess)
                {
                    while (this.TryTakeWorkItemFromQueue(privateData, out currentWorkItem))
                    {
                        this.RunWorkItem(currentWorkItem);
                    }
                }
                else
                {
                    while (this.TryTakeWorkItemFromQueue(privateData, out currentWorkItem))
                    {
                        this.CancelWorkItem(currentWorkItem);
                    }
                }
            }
        }