< Summary - CoreWCF Coverage — PR #1766

Information
Class: CoreWCF.Runtime.IOThreadScheduler
Assembly: CoreWCF.UnixDomainSocket
File(s): /home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/IOThreadScheduler.cs
Line coverage
0%
Covered lines: 0
Uncovered lines: 206
Coverable lines: 206
Total lines: 718
Line coverage: 0%
Branch coverage
0%
Covered branches: 0
Total branches: 96
Branch coverage: 0%
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Cyclomatic complexity NPath complexity Sequence coverage
Count(...)100%110%
CountNoIdle(...)100%110%
IncrementLo(...)100%110%
IsComplete(...)100%110%
.cctor()100%110%
.ctor(...)100%110%
ScheduleCallbackNoFlow(...)0%440%
ScheduleCallbackLowPriNoFlow(...)0%440%
ScheduleCallbackHelper(...)0%880%
ScheduleCallbackLowPriHelper(...)0%12120%
CompletionCallback(...)0%14140%
TryCoalesce(...)0%880%
Finalize()0%440%
Cleanup()0%220%
TryEnqueueWorkItem(...)0%14140%
DequeueWorkItem(...)0%12120%
.ctor()0%220%
IOCallback(...)100%110%
Callback()100%110%
InitThreadDebugData()100%110%
ClearThreadDebugData()100%110%
CallbackCore()0%440%
Post(...)100%110%
PostIOCP()100%110%
PostNewThread()100%110%
Cleanup()0%440%
Post(...)100%110%

File(s)

/home/runner/work/CoreWCF/CoreWCF/src/Common/src/CoreWCF/Runtime/IOThreadScheduler.cs

#LineLine coverage
 1// Licensed to the .NET Foundation under one or more agreements.
 2// The .NET Foundation licenses this file to you under the MIT license.
 3
 4using System;
 5using System.Diagnostics;
 6using System.Runtime.InteropServices;
 7using System.Runtime.Versioning;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11namespace CoreWCF.Runtime
 12{
 13    internal class IOThreadScheduler
 14    {
 15        // Do not increase the maximum capacity above 32k!  It must be a power of two, 0x8000 or less, in order to
 16        // work with the strategy for 'headTail'.
 17        private const int MaximumCapacity = 0x8000;
 18
 19        private static class Bits
 20        {
 21            public const int HiShift = 32 / 2;
 22
 23            public const int HiOne = 1 << HiShift;
 24            public const int LoHiBit = HiOne >> 1;
 25            public const int HiHiBit = LoHiBit << HiShift;
 26            public const int LoCountMask = LoHiBit - 1;
 27            public const int HiCountMask = LoCountMask << HiShift;
 28            public const int LoMask = LoCountMask | LoHiBit;
 29            public const int HiMask = HiCountMask | HiHiBit;
 30            public const int HiBits = LoHiBit | HiHiBit;
 31
 32            public static int Count(int slot)
 33            {
 034                return ((slot >> HiShift) - slot + 2 & LoMask) - 1;
 35            }
 36
 37            public static int CountNoIdle(int slot)
 38            {
 039                return (slot >> HiShift) - slot + 1 & LoMask;
 40            }
 41
 42            public static int IncrementLo(int slot)
 43            {
 044                return slot + 1 & LoMask | slot & HiMask;
 45            }
 46
 47            // This method is only valid if you already know that (gate & HiBits) != 0.
 48            public static bool IsComplete(int gate)
 49            {
 050                return (gate & HiMask) == gate << HiShift;
 51            }
 52        }
 53
 054        private static IOThreadScheduler s_current = new IOThreadScheduler(32, 32);
 055        private static SynchronizationContext s_syncContext = new IOThreadSchedulerSynchronizationContext();
 56        private static TaskScheduler s_IOTaskScheduler;
 57        private readonly ScheduledOverlapped _overlapped;
 58        private readonly Slot[] _slots;
 59        private readonly Slot[] _slotsLowPri;
 060        private static ThreadLocal<bool> s_isIoThread = new ThreadLocal<bool>();
 61
 62        // This field holds both the head (HiWord) and tail (LoWord) indices into the slot array.  This limits each
 63        // value to 64k.  In order to be able to distinguish wrapping the slot array (allowed) from wrapping the
 64        // indices relative to each other (not allowed), the size of the slot array is limited by an additional bit
 65        // to 32k.
 66        //
 67        // The HiWord (head) holds the index of the last slot to have been scheduled into.  The LoWord (tail) holds
 68        // the index of the next slot to be dispatched from.  When the queue is empty, the LoWord will be exactly
 69        // one slot ahead of the HiWord.  When the two are equal, the queue holds one item.
 70        //
 71        // When the tail is *two* slots ahead of the head (equivalent to a count of -1), that means the IOTS is
 72        // idle.  Hence, we start out headTail with a -2 (equivalent) in the head and zero in the tail.
 073        private int _headTail = -2 << Bits.HiShift;
 74
 75        // This field is the same except that it governs the low-priority work items.  It doesn't have a concept
 76        // of idle (-2) so starts empty (-1).
 077        private int _headTailLowPri = -1 << Bits.HiShift;
 78
 079        private IOThreadScheduler(int capacity, int capacityLowPri)
 80        {
 81            Fx.Assert(capacity > 0, "Capacity must be positive.");
 82            Fx.Assert(capacity <= 0x8000, "Capacity cannot exceed 32k.");
 83
 84            Fx.Assert(capacityLowPri > 0, "Low-priority capacity must be positive.");
 85            Fx.Assert(capacityLowPri <= 0x8000, "Low-priority capacity cannot exceed 32k.");
 86
 087            _slots = new Slot[capacity];
 88            Fx.Assert((_slots.Length & SlotMask) == 0, "Capacity must be a power of two.");
 89
 090            _slotsLowPri = new Slot[capacityLowPri];
 91            Fx.Assert((_slotsLowPri.Length & SlotMaskLowPri) == 0, "Low-priority capacity must be a power of two.");
 92
 093            _overlapped = new ScheduledOverlapped();
 094        }
 95
 96        public static TaskScheduler IOTaskScheduler
 97        {
 98            get
 99            {
 0100                if (s_IOTaskScheduler == null)
 101                {
 0102                    var savedCtx = SynchronizationContext.Current;
 0103                    SynchronizationContext.SetSynchronizationContext(s_syncContext);
 0104                    s_IOTaskScheduler = TaskScheduler.FromCurrentSynchronizationContext();
 0105                    SynchronizationContext.SetSynchronizationContext(savedCtx);
 106                }
 107
 0108                return s_IOTaskScheduler;
 109            }
 110        }
 111
 112        public static void ScheduleCallbackNoFlow(Action<object> callback, object state)
 113        {
 0114            if (callback == null)
 115            {
 0116                throw Fx.Exception.ArgumentNull(nameof(callback));
 117            }
 118
 0119            bool queued = false;
 0120            while (!queued)
 121            {
 0122                try { }
 123                finally
 124                {
 125                    // Called in a finally because it needs to run uninterrupted in order to maintain consistency.
 0126                    queued = s_current.ScheduleCallbackHelper(callback, state);
 0127                }
 128            }
 0129        }
 130
 131        public static void ScheduleCallbackLowPriNoFlow(Action<object> callback, object state)
 132        {
 0133            if (callback == null)
 134            {
 0135                throw Fx.Exception.ArgumentNull(nameof(callback));
 136            }
 137
 0138            bool queued = false;
 0139            while (!queued)
 140            {
 0141                try { }
 142                finally
 143                {
 144                    // Called in a finally because it needs to run uninterrupted in order to maintain consistency.
 0145                    queued = s_current.ScheduleCallbackLowPriHelper(callback, state);
 0146                }
 147            }
 0148        }
 149
 150        // Returns true if successfully scheduled, false otherwise.
 151        private bool ScheduleCallbackHelper(Action<object> callback, object state)
 152        {
 153            // See if there's a free slot.  Fortunately the overflow bit is simply lost.
 0154            int slot = Interlocked.Add(ref _headTail, Bits.HiOne);
 155
 156            // If this brings us to 'empty', then the IOTS used to be 'idle'.  Remember that, and increment
 157            // again.  This doesn't need to be in a loop, because until we call Post(), we can't go back to idle.
 0158            bool wasIdle = Bits.Count(slot) == 0;
 0159            if (wasIdle)
 160            {
 0161                slot = Interlocked.Add(ref _headTail, Bits.HiOne);
 162                Fx.Assert(Bits.Count(slot) != 0, "IOTS went idle when it shouldn't have.");
 163            }
 164
 165            // Check if we wrapped *around* to idle.
 0166            if (Bits.Count(slot) == -1)
 167            {
 168                // Since the capacity is limited to 32k, this means we wrapped the array at least twice.  That's bad
 169                // because headTail no longer knows how many work items we have - it looks like zero.  This can
 170                // only happen if 32k threads come through here while one is swapped out.
 0171                throw Fx.AssertAndThrowFatal("Head/Tail overflow!");
 172            }
 173
 0174            bool queued = _slots[slot >> Bits.HiShift & SlotMask].TryEnqueueWorkItem(callback, state, out bool wrapped);
 175
 0176            if (wrapped)
 177            {
 178                // Wrapped around the circular buffer.  Create a new, bigger IOThreadScheduler.
 0179                IOThreadScheduler next =
 0180                    new IOThreadScheduler(Math.Min(_slots.Length * 2, MaximumCapacity), _slotsLowPri.Length);
 0181                Interlocked.CompareExchange<IOThreadScheduler>(ref s_current, next, this);
 182            }
 183
 0184            if (wasIdle)
 185            {
 186                // It's our responsibility to kick off the overlapped.
 0187                _overlapped.Post(this);
 188            }
 189
 0190            return queued;
 191        }
 192
 193        // Returns true if successfully scheduled, false otherwise.
 194        private bool ScheduleCallbackLowPriHelper(Action<object> callback, object state)
 195        {
 196            // See if there's a free slot.  Fortunately the overflow bit is simply lost.
 0197            int slot = Interlocked.Add(ref _headTailLowPri, Bits.HiOne);
 198
 199            // If this is the first low-priority work item, make sure we're not idle.
 0200            bool wasIdle = false;
 0201            if (Bits.CountNoIdle(slot) == 1)
 202            {
 203                // Since Interlocked calls create a full thread barrier, this will read the value of headTail
 204                // at the time of the Interlocked.Add or later.  The invariant is that the IOTS is unidle at some
 205                // point after the Add.
 0206                int ht = _headTail;
 207
 0208                if (Bits.Count(ht) == -1)
 209                {
 210                    // Use a temporary local here to store the result of the Interlocked.CompareExchange.  This
 211                    // works around a codegen bug in the 32-bit JIT (TFS 749182).
 0212                    int interlockedResult = Interlocked.CompareExchange(ref _headTail, ht + Bits.HiOne, ht);
 0213                    if (ht == interlockedResult)
 214                    {
 0215                        wasIdle = true;
 216                    }
 217                }
 218            }
 219
 220            // Check if we wrapped *around* to empty.
 0221            if (Bits.CountNoIdle(slot) == 0)
 222            {
 223                // Since the capacity is limited to 32k, this means we wrapped the array at least twice.  That's bad
 224                // because headTail no longer knows how many work items we have - it looks like zero.  This can
 225                // only happen if 32k threads come through here while one is swapped out.
 0226                throw Fx.AssertAndThrowFatal("Low-priority Head/Tail overflow!");
 227            }
 228
 0229            bool queued = _slotsLowPri[slot >> Bits.HiShift & SlotMaskLowPri].TryEnqueueWorkItem(
 0230                callback, state, out bool wrapped);
 231
 0232            if (wrapped)
 233            {
 0234                IOThreadScheduler next =
 0235                    new IOThreadScheduler(_slots.Length, Math.Min(_slotsLowPri.Length * 2, MaximumCapacity));
 0236                Interlocked.CompareExchange<IOThreadScheduler>(ref s_current, next, this);
 237            }
 238
 0239            if (wasIdle)
 240            {
 241                // It's our responsibility to kick off the overlapped.
 0242                _overlapped.Post(this);
 243            }
 244
 0245            return queued;
 246        }
 247
 248        private void CompletionCallback(out Action<object> callback, out object state)
 249        {
 0250            int slot = _headTail;
 251            int slotLowPri;
 252            while (true)
 253            {
 254                Fx.Assert(Bits.Count(slot) != -1, "CompletionCallback called on idle IOTS!");
 255
 0256                bool wasEmpty = Bits.Count(slot) == 0;
 0257                if (wasEmpty)
 258                {
 259                    // We're about to set this to idle.  First check the low-priority queue.  This alone doesn't
 260                    // guarantee we service all the low-pri items - there hasn't even been an Interlocked yet.  But
 261                    // we take care of that later.
 0262                    slotLowPri = _headTailLowPri;
 0263                    while (Bits.CountNoIdle(slotLowPri) != 0)
 264                    {
 0265                        if (slotLowPri == (slotLowPri = Interlocked.CompareExchange(ref _headTailLowPri,
 0266                            Bits.IncrementLo(slotLowPri), slotLowPri)))
 267                        {
 0268                            _overlapped.Post(this);
 0269                            _slotsLowPri[slotLowPri & SlotMaskLowPri].DequeueWorkItem(out callback, out state);
 0270                            return;
 271                        }
 272                    }
 273                }
 274
 0275                if (slot == (slot = Interlocked.CompareExchange(ref _headTail, Bits.IncrementLo(slot), slot)))
 276                {
 0277                    if (!wasEmpty)
 278                    {
 0279                        _overlapped.Post(this);
 0280                        _slots[slot & SlotMask].DequeueWorkItem(out callback, out state);
 0281                        return;
 282                    }
 283
 284                    // We just set the IOThreadScheduler to idle.  Check if a low-priority item got added in the
 285                    // interim.
 286                    // Interlocked calls create a thread barrier, so this read will give us the value of
 287                    // headTailLowPri at the time of the interlocked that set us to idle, or later.  The invariant
 288                    // here is that either the low-priority queue was empty at some point after we set the IOTS to
 289                    // idle (so that the next enqueue will notice, and issue a Post), or that the IOTS was unidle at
 290                    // some point after we set it to idle (so that the next attempt to go idle will verify that the
 291                    // low-priority queue is empty).
 0292                    slotLowPri = _headTailLowPri;
 293
 0294                    if (Bits.CountNoIdle(slotLowPri) != 0)
 295                    {
 296                        // Whoops, go back from being idle (unless someone else already did).  If we go back, start
 297                        // over.  (We still owe a Post.)
 0298                        slot = Bits.IncrementLo(slot);
 0299                        if (slot == Interlocked.CompareExchange(ref _headTail, slot + Bits.HiOne, slot))
 300                        {
 0301                            slot += Bits.HiOne;
 0302                            continue;
 303                        }
 304
 305                        // We know that there's a low-priority work item.  But we also know that the IOThreadScheduler
 306                        // wasn't idle.  It's best to let it take care of itself, since according to this method, we
 307                        // just set the IOThreadScheduler to idle so shouldn't take on any tasks.
 308                    }
 309
 310                    break;
 311                }
 312            }
 313
 0314            callback = null;
 0315            state = null;
 0316            return;
 317        }
 318
 319        private bool TryCoalesce(out Action<object> callback, out object state)
 320        {
 0321            int slot = _headTail;
 322            int slotLowPri;
 323            while (true)
 324            {
 0325                if (Bits.Count(slot) > 0)
 326                {
 0327                    if (slot == (slot = Interlocked.CompareExchange(ref _headTail, Bits.IncrementLo(slot), slot)))
 328                    {
 0329                        _slots[slot & SlotMask].DequeueWorkItem(out callback, out state);
 0330                        return true;
 331                    }
 332                    continue;
 333                }
 334
 0335                slotLowPri = _headTailLowPri;
 0336                if (Bits.CountNoIdle(slotLowPri) > 0)
 337                {
 0338                    if (slotLowPri == (slotLowPri = Interlocked.CompareExchange(ref _headTailLowPri,
 0339                        Bits.IncrementLo(slotLowPri), slotLowPri)))
 340                    {
 0341                        _slotsLowPri[slotLowPri & SlotMaskLowPri].DequeueWorkItem(out callback, out state);
 0342                        return true;
 343                    }
 0344                    slot = _headTail;
 0345                    continue;
 346                }
 347
 348                break;
 349            }
 350
 0351            callback = null;
 0352            state = null;
 0353            return false;
 354        }
 355
 356        private int SlotMask
 357        {
 358            get
 359            {
 0360                return _slots.Length - 1;
 361            }
 362        }
 363
 364        private int SlotMaskLowPri
 365        {
 366            get
 367            {
 0368                return _slotsLowPri.Length - 1;
 369            }
 370        }
 371
 0372        public static bool IsRunningOnIOThread => s_isIoThread.IsValueCreated && s_isIoThread.Value;
 373
 374        //TODO, Dev10,607596 cannot apply security critical on finalizer
 375        //[Fx.Tag.SecurityNote(Critical = "touches slots, may be called outside of user context")]
 376        //[SecurityCritical]
 377        ~IOThreadScheduler()
 378        {
 379            // If the AppDomain is shutting down, we may still have pending ops.  The AppDomain shutdown will clean
 380            // everything up.
 0381            if (!Environment.HasShutdownStarted && !AppDomain.CurrentDomain.IsFinalizingForUnload())
 382            {
 383#if DEBUG
 384                DebugVerifyHeadTail();
 385#endif
 0386                Cleanup();
 387            }
 0388        }
 389
 390        private void Cleanup()
 391        {
 0392            if (_overlapped != null)
 393            {
 0394                _overlapped.Cleanup();
 395            }
 0396        }
 397
 398#if DEBUG
 399        private void DebugVerifyHeadTail()
 400        {
 401            if (_slots != null)
 402            {
 403                // The headTail value could technically be zero if the constructor was aborted early.  The
 404                // constructor wasn't aborted early if the slot array got created.
 405                Fx.Assert(Bits.Count(_headTail) == -1, "IOTS finalized while not idle.");
 406
 407                for (int i = 0; i < _slots.Length; i++)
 408                {
 409                    _slots[i].DebugVerifyEmpty();
 410                }
 411            }
 412
 413            if (_slotsLowPri != null)
 414            {
 415                Fx.Assert(Bits.CountNoIdle(_headTailLowPri) == 0, "IOTS finalized with low-priority items queued.");
 416
 417                for (int i = 0; i < _slotsLowPri.Length; i++)
 418                {
 419                    _slotsLowPri[i].DebugVerifyEmpty();
 420                }
 421            }
 422        }
 423#endif
 424
 425        // TryEnqueueWorkItem and DequeueWorkItem use the slot's 'gate' field for synchronization.  Because the
 426        // slot array is circular and there are no locks, we must assume that multiple threads can be entering each
 427        // method simultaneously.  If the first DequeueWorkItem occurs before the first TryEnqueueWorkItem, the
 428        // sequencing (and the enqueue) fails.
 429        //
 430        // The gate is a 32-bit int divided into four fields.  The bottom 15 bits (0x00007fff) are the count of
 431        // threads that have entered TryEnqueueWorkItem.  The first thread to enter is the one responsible for
 432        // filling the slot with work.  The 16th bit (0x00008000) is a flag indicating that the slot has been
 433        // successfully filled.  Only the first thread to enter TryEnqueueWorkItem can set this flag.  The
 434        // high-word (0x7fff0000) is the count of threads entering DequeueWorkItem.  The first thread to enter
 435        // is the one responsible for accepting (and eventually dispatching) the work in the slot.  The
 436        // high-bit (0x80000000) is a flag indicating that the slot has been successfully emptied.
 437        //
 438        // When the low-word and high-work counters are equal, and both bit flags have been set, the gate is considered
 439        // 'complete' and can be reset back to zero.  Any operation on the gate might bring it to this state.
 440        // It's the responsibility of the thread that brings the gate to a completed state to reset it to zero.
 441        // (It's possible that the gate will fall out of the completed state before it can be reset - that's ok,
 442        // the next time it becomes completed it can be reset.)
 443        //
 444        // It's unlikely either count will ever go higher than 2 or 3.
 445        //
 446        // The value of 'callback' has these properties:
 447        //   -  When the gate is zero, callback is null.
 448        //   -  When the low-word count is non-zero, but the 0x8000 bit is unset, callback is writable by the thread
 449        //      that incremented the low word to 1.  Its value is undefined for other threads.  The thread that
 450        //      sets callback is responsible for setting the 0x8000 bit when it's done.
 451        //   -  When the 0x8000 bit is set and the high-word count is zero, callback is valid.  (It may be null.)
 452        //   -  When the 0x8000 bit is set, the high-word count is non-zero, and the high bit is unset, callback is
 453        //      writable by the thread that incremented the high word to 1 *or* the thread that set the 0x8000 bit,
 454        //      whichever happened last.  That thread can read the value and set callback to null.  Its value is
 455        //      undefined for other threads.  The thread that clears the callback is responsible for setting the
 456        //      high bit.
 457        //   -  When the high bit is set, callback is null.
 458        //   -  It's illegal for the gate to be in a state that would satisfy more than one of these conditions.
 459        //   -  The state field follows the same rules as callback.
 460        private struct Slot
 461        {
 462            private int _gate;
 463            private Action<object> _callback;
 464            private object _state;
 465
 466            public bool TryEnqueueWorkItem(Action<object> callback, object state, out bool wrapped)
 467            {
 468                // Register our arrival and check the state of this slot.  If the slot was already full, we wrapped.
 0469                int gateSnapshot = Interlocked.Increment(ref _gate);
 0470                wrapped = (gateSnapshot & Bits.LoCountMask) != 1;
 0471                if (wrapped)
 472                {
 0473                    if ((gateSnapshot & Bits.LoHiBit) != 0 && Bits.IsComplete(gateSnapshot))
 474                    {
 0475                        Interlocked.CompareExchange(ref _gate, 0, gateSnapshot);
 476                    }
 0477                    return false;
 478                }
 479
 480                Fx.Assert(_callback == null, "Slot already has a work item.");
 481                Fx.Assert((gateSnapshot & Bits.HiBits) == 0, "Slot already marked.");
 482
 0483                _state = state;
 0484                _callback = callback;
 485
 486                // Set the special bit to show that the slot is filled.
 0487                gateSnapshot = Interlocked.Add(ref _gate, Bits.LoHiBit);
 488                Fx.Assert((gateSnapshot & Bits.HiBits) == Bits.LoHiBit, "Slot already empty.");
 489
 0490                if ((gateSnapshot & Bits.HiCountMask) == 0)
 491                {
 492                    // Good - no one has shown up looking for this work yet.
 0493                    return true;
 494                }
 495
 496                // Oops - someone already came looking for this work.  We have to abort and reschedule.
 0497                _state = null;
 0498                _callback = null;
 499
 500                // Indicate that the slot is clear.  We might be able to bypass setting the high bit.
 0501                if (gateSnapshot >> Bits.HiShift != (gateSnapshot & Bits.LoCountMask) ||
 0502                    Interlocked.CompareExchange(ref _gate, 0, gateSnapshot) != gateSnapshot)
 503                {
 0504                    gateSnapshot = Interlocked.Add(ref _gate, Bits.HiHiBit);
 0505                    if (Bits.IsComplete(gateSnapshot))
 506                    {
 0507                        Interlocked.CompareExchange(ref _gate, 0, gateSnapshot);
 508                    }
 509                }
 510
 0511                return false;
 512            }
 513
 514            public void DequeueWorkItem(out Action<object> callback, out object state)
 515            {
 516                // Stake our claim on the item.
 0517                int gateSnapshot = Interlocked.Add(ref _gate, Bits.HiOne);
 518
 0519                if ((gateSnapshot & Bits.LoHiBit) == 0)
 520                {
 521                    // Whoops, a race.  The work item hasn't made it in yet.  In this context, returning a null callback
 522                    // is treated like a degenerate work item (rather than an empty queue).  The enqueuing thread will
 523                    // notice this race and reschedule the real work in a new slot.  Do not reset the slot to zero,
 524                    // since it's still going to get enqueued into.  (The enqueueing thread will reset it.)
 0525                    callback = null;
 0526                    state = null;
 0527                    return;
 528                }
 529
 530                // If we're the first, we get to do the work.
 0531                if ((gateSnapshot & Bits.HiCountMask) == Bits.HiOne)
 532                {
 0533                    callback = _callback;
 0534                    state = _state;
 0535                    _state = null;
 0536                    _callback = null;
 537
 538                    // Indicate that the slot is clear.
 539                    // We should be able to bypass setting the high-bit in the common case.
 0540                    if ((gateSnapshot & Bits.LoCountMask) != 1 ||
 0541                        Interlocked.CompareExchange(ref _gate, 0, gateSnapshot) != gateSnapshot)
 542                    {
 0543                        gateSnapshot = Interlocked.Add(ref _gate, Bits.HiHiBit);
 0544                        if (Bits.IsComplete(gateSnapshot))
 545                        {
 0546                            Interlocked.CompareExchange(ref _gate, 0, gateSnapshot);
 547                        }
 548                    }
 549                }
 550                else
 551                {
 0552                    callback = null;
 0553                    state = null;
 554
 555                    // If we're the last, we get to reset the slot.
 0556                    if (Bits.IsComplete(gateSnapshot))
 557                    {
 0558                        Interlocked.CompareExchange(ref _gate, 0, gateSnapshot);
 559                    }
 560                }
 0561            }
 562
 563#if DEBUG
 564            public void DebugVerifyEmpty()
 565            {
 566                Fx.Assert(_gate == 0, "Finalized with unfinished slot.");
 567                Fx.Assert(_callback == null, "Finalized with leaked callback.");
 568                Fx.Assert(_state == null, "Finalized with leaked state.");
 569            }
 570#endif
 571        }
 572
 573        // A note about the IOThreadScheduler and the ScheduledOverlapped references:
 574        // Although for each scheduler we have a single instance of overlapped, we cannot point to the scheduler from th
 575        // overlapped, through the entire lifetime of the overlapped. This is because the ScheduledOverlapped is pinned
 576        // and if it has a reference to the IOTS, it would be rooted and the finalizer will never get called.
 577        // Therefore, we are passing the reference, when we post a pending callback and reset it, once the callback was
 578        // invoked; during that time the scheduler is rooted but in that time we don't want that it would be collected
 579        // by the GC anyway.
 580        private unsafe class ScheduledOverlapped
 581        {
 582            private readonly NativeOverlapped* _nativeOverlapped;
 583            private IOThreadScheduler _scheduler;
 584            private readonly Action _postDelegate;
 585
 0586            public ScheduledOverlapped()
 587            {
 0588                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
 589                {
 0590                    _nativeOverlapped = (new Overlapped()).UnsafePack(
 0591                        Fx.ThunkCallback(new IOCompletionCallback(IOCallback)), null);
 0592                    _postDelegate = PostIOCP;
 593                }
 594                else
 595                {
 0596                    _postDelegate = PostNewThread;
 597                }
 0598            }
 599
 600            private void IOCallback(uint errorCode, uint numBytes, NativeOverlapped* nativeOverlapped)
 601            {
 0602                Callback();
 0603            }
 604
 605            private void Callback()
 606            {
 607                try
 608                {
 609                    InitThreadDebugData();
 0610                    CallbackCore();
 0611                }
 612                finally
 613                {
 614                    ClearThreadDebugData();
 0615                }
 0616            }
 617
 618            [Conditional("DEBUG")]
 619            private static void InitThreadDebugData()
 620            {
 0621                s_isIoThread.Value = true;
 0622                Thread.CurrentThread.Name = "IOThreadScheduler.IOCallback";
 0623            }
 624
 625            [Conditional("DEBUG")]
 626            private static void ClearThreadDebugData()
 627            {
 0628                s_isIoThread.Value = false;
 0629            }
 630
 631            private void CallbackCore()
 632            {
 633                // Unhook the IOThreadScheduler ASAP to prevent it from leaking.
 0634                IOThreadScheduler iots = _scheduler;
 0635                _scheduler = null;
 636                Fx.Assert(iots != null, "Overlapped completed without a scheduler.");
 637
 638                Action<object> callback;
 639                object state;
 0640                try { }
 641                finally
 642                {
 643                    // Called in a finally because it needs to run uninterrupted in order to maintain consistency.
 0644                    iots.CompletionCallback(out callback, out state);
 0645                }
 646
 0647                bool found = true;
 0648                while (found)
 649                {
 650                    // The callback can be null if synchronization misses result in unusable slots.  Keep going onto
 651                    // the next slot in such cases until there are no more slots.
 0652                    if (callback != null)
 653                    {
 0654                        callback(state);
 655                    }
 656
 0657                    try { }
 658                    finally
 659                    {
 660                        // Called in a finally because it needs to run uninterrupted in order to maintain consistency.
 0661                        found = iots.TryCoalesce(out callback, out state);
 0662                    }
 663                }
 0664            }
 665
 666            public void Post(IOThreadScheduler iots)
 667            {
 668                Fx.Assert(_scheduler == null, "Post called on an overlapped that is already posted.");
 669                Fx.Assert(iots != null, "Post called with a null scheduler.");
 670
 0671                _scheduler = iots;
 0672                _postDelegate();
 0673            }
 674
 675            [SupportedOSPlatform("windows")]
 676            private void PostIOCP()
 677            {
 0678                ThreadPool.UnsafeQueueNativeOverlapped(_nativeOverlapped);
 0679            }
 680
 681            private void PostNewThread()
 682            {
 683                // The waiter thread used on non-Windows platforms must be a background thread,
 684                // otherwise it keeps the process alive until WaitAny returns, which can take
 685                // up to the longest IOThreadTimer due time (default session idle timeout is
 686                // measured in minutes). Background threads are torn down by the runtime when
 687                // the process exits, mirroring the Windows IOCP behavior where the equivalent
 688                // work runs on thread-pool threads (which are background by default).
 0689                var thread = new Thread(new ThreadStart(Callback))
 0690                {
 0691                    IsBackground = true,
 0692                };
 0693                thread.Start();
 0694            }
 695
 696            public void Cleanup()
 697            {
 0698                if (_scheduler != null)
 699                {
 0700                    throw Fx.AssertAndThrowFatal("Cleanup called on an overlapped that is in-flight.");
 701                }
 702
 0703                if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
 704                {
 0705                    Overlapped.Free(_nativeOverlapped);
 706                }
 0707            }
 708        }
 709
 710        private class IOThreadSchedulerSynchronizationContext : SynchronizationContext
 711        {
 712            public override void Post(SendOrPostCallback d, object state)
 713            {
 0714                ScheduleCallbackNoFlow((s) => d(s), state);
 0715            }
 716        }
 717    }
 718}