private void WorkLoop()
        {
            var test = Interlocked.CompareExchange(ref workerState, -1, -1);

            //if (Interlocked.CompareExchange(ref workerState, -1, -1) != WORKER_INIT)
            if (test == WORKER_KILLED)
            {
                throw new InvalidOperationException($"This should not happen! {test}");
            }

            while (Interlocked.CompareExchange(ref workerState, 0, 0) != WORKER_KILLED)
            {
                ExpireTimeouts();
                long newDeadline = CalcNewDeadline();
                var  sleepTime   = newDeadline - time.Elapsed;
                // tick before sleep so that timeout added while sleeping
                // will be delayed by a tick to avoid early wake up.
                Sleep(NanoTime.ToMilliseconds(sleepTime));
                Interlocked.Increment(ref ticked);
            }

            unprocessedTimeouts = new HashSet <TimedCallback>();
            foreach (var slot in slots)
            {
                TimedCallback timeout = null;
                while ((timeout = slot.TryPop()) != null)
                {
                    unprocessedTimeouts.Add(timeout);
                }
            }
            stopBarier.Signal();
        }
        public void TestDelayTimeoutShouldNotLargerThanSingleTickDuration(int tickInterval, int timeout)
        {
            var  watch   = new ConcurrentStopwatch();
            var  barrier = new CountdownEvent(1);
            var  timer   = new HashedWheelTimer(interval: TimeSpan.FromMilliseconds(tickInterval));
            long elapsed = 0;

            watch.Start();
            timer.ScheduleTimeout(() =>
            {
                Interlocked.Exchange(ref elapsed, watch.Elapsed);
                barrier.Signal();
            }, TimeSpan.FromMilliseconds(timeout));

            Assert.True(barrier.Wait(tickInterval * 2 + timeout), $"Elapsed: {NanoTime.ToMilliseconds(elapsed)}, ticks interval: {tickInterval}, timeout: {timeout}.");
        }
        private void ScheduleTimeoutImpl(TimedCallback callback, long nanoseconds)
        {
            // TODO: Should always schedule to next tick
            var             differredTimeout = nanoseconds + TicksInterval;
            var             diff             = ToWheelTicks(differredTimeout);
            var             deadline         = NanoTime.ToMilliseconds(CalculateDeadline());
            var             due = diff + deadline;
            HashedWheelSlot slot;

            if (diff < WHEEL_SIZE)
            {
                var _ = (due & WHEEL_MASK);
                slot = slots[0, due & WHEEL_MASK];
            }
            else if (diff < 1 << (2 * WHEEL_BITS))
            {
                var _ = ((due >> WHEEL_BITS) & WHEEL_MASK);
                slot = slots[1, (due >> WHEEL_BITS) & WHEEL_MASK];
            }
            else if (diff < 1 << (3 * WHEEL_BITS))
            {
                var _ = ((due >> 2 * WHEEL_BITS) & WHEEL_MASK);
                slot = slots[2, (due >> 2 * WHEEL_BITS) & WHEEL_MASK];
            }
            else
            {
                if (diff > 0xffffffff)
                {
                    diff = 0xffffffff;
                    due  = NanoTime.ToMilliseconds(diff + deadline);
                }
                var _ = ((due >> 3 * WHEEL_BITS) & WHEEL_MASK);
                slot = slots[3, (due >> 3 * WHEEL_BITS) & WHEEL_MASK];
            }
            slot.Push(callback);
        }