static DynamicJsonValue ToJson(GCMemoryInfo info)
 {
     return(new DynamicJsonValue
     {
         [nameof(info.Compacted)] = info.Compacted,
         [nameof(info.Concurrent)] = info.Concurrent,
         [nameof(info.FinalizationPendingCount)] = info.FinalizationPendingCount,
         [nameof(info.FragmentedBytes)] = info.FragmentedBytes,
         [nameof(info.Generation)] = info.Generation,
         [nameof(info.GenerationInfo)] = new DynamicJsonArray(info.GenerationInfo.ToArray().Select(x => new DynamicJsonValue
         {
             [nameof(x.FragmentationAfterBytes)] = x.FragmentationAfterBytes,
             [nameof(x.FragmentationBeforeBytes)] = x.FragmentationBeforeBytes,
             [nameof(x.SizeAfterBytes)] = x.SizeAfterBytes,
             [nameof(x.SizeBeforeBytes)] = x.SizeBeforeBytes
         })),
         [nameof(info.HeapSizeBytes)] = info.HeapSizeBytes,
         [nameof(info.HighMemoryLoadThresholdBytes)] = info.HighMemoryLoadThresholdBytes,
         [nameof(info.Index)] = info.Index,
         [nameof(info.MemoryLoadBytes)] = info.MemoryLoadBytes,
         [nameof(info.PauseDurations)] = new DynamicJsonArray(info.PauseDurations.ToArray().Cast <object>()),
         [nameof(info.PauseTimePercentage)] = info.PauseTimePercentage,
         [nameof(info.PinnedObjectsCount)] = info.PinnedObjectsCount,
         [nameof(info.PromotedBytes)] = info.PromotedBytes,
         [nameof(info.TotalAvailableMemoryBytes)] = info.TotalAvailableMemoryBytes,
         [nameof(info.TotalCommittedBytes)] = info.TotalCommittedBytes
     });
 }
    public static int Main()
    {
        Stopwatch sw = Stopwatch.StartNew();

        GC.Collect();
        sw.Stop();
        TimeSpan     elapsed            = sw.Elapsed;
        TimeSpan     totalPauseDuration = GC.GetTotalPauseDuration();
        GCMemoryInfo memoryInfo         = GC.GetGCMemoryInfo();
        TimeSpan     lastGcDuration     = memoryInfo.PauseDurations[0];

        // These conditions assume the only GC in the process
        // is the one we just triggered. This makes the test incompatible
        // with any changes that might introduce extra GCs.

        if (TimeSpan.Zero < totalPauseDuration &&
            totalPauseDuration <= elapsed &&
            lastGcDuration == totalPauseDuration)
        {
            return(100);
        }
        else
        {
            return(101);
        }
    }
Exemple #3
0
        async Task <IndexViewModel> GetIndexViewModel()
        {
            var          indexViewModel = new IndexViewModel();
            GCMemoryInfo gcInfo         = GC.GetGCMemoryInfo();

            indexViewModel.TotalAvailableMemory = GetInBestUnit(gcInfo.TotalAvailableMemoryBytes);
            indexViewModel.HostName             = Dns.GetHostName();
            indexViewModel.IpList = await Dns.GetHostAddressesAsync(indexViewModel.HostName);

            indexViewModel.cGroup = RuntimeInformation.OSDescription.StartsWith("Linux") &&
                                    Directory.Exists("/sys/fs/cgroup/memory") &&
                                    Directory.Exists("/sys/fs/cgroup/cpu");

            _logger.LogInformation($"running in cgroup? : {indexViewModel.cGroup}");

            if (indexViewModel.cGroup)
            {
                string usage     = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.usage_in_bytes")[0];
                string limit     = System.IO.File.ReadAllLines("/sys/fs/cgroup/memory/memory.limit_in_bytes")[0];
                string cpuQuota  = System.IO.File.ReadAllLines("/sys/fs/cgroup/cpu/cpu.cfs_quota_us")[0];
                string cpuPeriod = System.IO.File.ReadAllLines("/sys/fs/cgroup/cpu/cpu.cfs_period_us")[0];
                indexViewModel.CpuShares   = System.IO.File.ReadAllLines("/sys/fs/cgroup/cpu/cpu.shares")[0];
                indexViewModel.MemoryUsage = GetInBestUnit(long.Parse(usage));
                indexViewModel.MemoryLimit = GetInBestUnit(long.Parse(limit));
                indexViewModel.CpuLimit    = GetCpuLimit(long.Parse(cpuQuota), long.Parse(cpuPeriod));
            }

            return(indexViewModel);
        }
        protected override int GetCurrentPressure()
        {
            // Try to refresh GC stats if they haven't been updated since our last check.
            int ccount = GC.CollectionCount(0);

            if (ccount == lastGCCount)
            {
                GC.Collect(0, GCCollectionMode.Optimized); // A quick, ephemeral Gen 0 collection
                ccount = GC.CollectionCount(0);
            }
            lastGCCount = ccount;

            // Get stats from GC.
            GCMemoryInfo memInfo = GC.GetGCMemoryInfo();

            if (memInfo.TotalAvailableMemoryBytes >= memInfo.MemoryLoadBytes)
            {
                int memoryLoad = (int)((float)memInfo.MemoryLoadBytes * 100.0 / (float)memInfo.TotalAvailableMemoryBytes);
                return(Math.Max(1, memoryLoad));
            }

            // It's possible the load was legitimately higher than "available". In that case, return 100.
            // Otherwise, return 0 to minimize impact because something was unexpected.
            return((memInfo.MemoryLoadBytes > 0) ? 100 : 0);
        }
Exemple #5
0
        public static void ViewMemoryAtMoment(string moment)
        {
            long         threadMemory = GC.GetAllocatedBytesForCurrentThread();
            GCMemoryInfo gcInfo       = GC.GetGCMemoryInfo();
            long         totalMemory  = GC.GetTotalMemory(false);

            Console.WriteLine("[Memory][{0}] allocatedbytesAtThread = {1}, totalMemory = {2}", moment, threadMemory, totalMemory);
        }
    public static bool PrintGCMemoryInfo(GCKind kind)
    {
        if (!fPrintInfo)
        {
            return(true);
        }

        GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(kind);

        Console.WriteLine("last recorded {0} GC#{1}, collected gen{2}, concurrent: {3}, compact: {4}, promoted {5:N0} bytes",
                          kind, memoryInfo.Index, memoryInfo.Generation, memoryInfo.Concurrent, memoryInfo.Compacted, memoryInfo.PromotedBytes);
        Console.WriteLine("GC pause: {0:N0}, {1:N0}",
                          memoryInfo.PauseDurations[0].TotalMilliseconds,
                          memoryInfo.PauseDurations[1].TotalMilliseconds);
        Console.WriteLine("Total committed {0:N0}, % Pause time in GC: {1}",
                          memoryInfo.TotalCommittedBytes, memoryInfo.PauseTimePercentage);
        Console.WriteLine("This GC observed {0:N0} pinned objects and {1:N0} objects ready for finalization",
                          memoryInfo.PinnedObjectsCount, memoryInfo.FinalizationPendingCount);
        int numGenerations = memoryInfo.GenerationInfo.Length;

        Console.WriteLine("there are {0} generations", numGenerations);
        for (int i = 0; i < numGenerations; i++)
        {
            Console.WriteLine("gen#{0}, size {1:N0}->{2:N0}, frag {3:N0}->{4:N0}", i,
                              memoryInfo.GenerationInfo[i].SizeBeforeBytes, memoryInfo.GenerationInfo[i].SizeAfterBytes,
                              memoryInfo.GenerationInfo[i].FragmentationBeforeBytes, memoryInfo.GenerationInfo[i].FragmentationAfterBytes);
        }

        if (kind == GCKind.Ephemeral)
        {
            if (memoryInfo.Generation == GC.MaxGeneration)
            {
                Console.WriteLine("FAILED: GC#{0} is supposed to be an ephemeral GC but condemned max gen", memoryInfo.Index);
                return(false);
            }
        }
        else if (kind == GCKind.FullBlocking)
        {
            if ((memoryInfo.Generation != GC.MaxGeneration) || memoryInfo.Concurrent)
            {
                Console.WriteLine("FAILED: GC#{0} is supposed to be a full blocking GC but gen is {1}, concurrent {2}",
                                  memoryInfo.Index, memoryInfo.Generation, memoryInfo.Concurrent);
                return(false);
            }
        }
        else if (kind == GCKind.Background)
        {
            if ((memoryInfo.Generation != GC.MaxGeneration) || !(memoryInfo.Concurrent))
            {
                Console.WriteLine("FAILED: GC#{0} is supposed to be a BGC but gen is {1}, concurrent {2}",
                                  memoryInfo.Index, memoryInfo.Generation, memoryInfo.Concurrent);
                return(false);
            }
        }

        return(true);
    }
        private bool OnGen2GCCallback()
        {
            // Gen 2 GCs may be very infrequent in some cases. If it becomes an issue, consider updating the memory usage more
            // frequently. The memory usage is only used for fallback purposes in blocking adjustment, so an artificially higher
            // memory usage may cause blocking adjustment to fall back to slower adjustments sooner than necessary.
            GCMemoryInfo gcMemoryInfo = GC.GetGCMemoryInfo();

            _memoryLimitBytes = gcMemoryInfo.HighMemoryLoadThresholdBytes;
            _memoryUsageBytes = Math.Min(gcMemoryInfo.MemoryLoadBytes, gcMemoryInfo.HighMemoryLoadThresholdBytes);
            return(true); // continue receiving gen 2 GC callbacks
        }
        private static long GetDefaultMaxPoolSizeBytes()
        {
#if NETCOREAPP3_1_OR_GREATER
            // On 64 bit .NET Core 3.1+, set the pool size to a portion of the total available memory.
            // There is a bug in GC.GetGCMemoryInfo() on .NET 5 + 32 bit, making TotalAvailableMemoryBytes unreliable:
            // https://github.com/dotnet/runtime/issues/55126#issuecomment-876779327
            if (Environment.Is64BitProcess || !RuntimeInformation.FrameworkDescription.StartsWith(".NET 5.0"))
            {
                GCMemoryInfo info = GC.GetGCMemoryInfo();
                return(info.TotalAvailableMemoryBytes / 8);
            }
#endif

            // Stick to a conservative value of 128 Megabytes on other platforms and 32 bit .NET 5.0:
            return(128 * OneMegabyte);
        }
        private static MemoryPressure GetMemoryPressure()
        {
            const double HighPressureThreshold   = .90;     // Percent of GC memory pressure threshold we consider "high"
            const double MediumPressureThreshold = .70;     // Percent of GC memory pressure threshold we consider "medium"

            GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();

            if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * HighPressureThreshold)
            {
                return(MemoryPressure.High);
            }
            else if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * MediumPressureThreshold)
            {
                return(MemoryPressure.Medium);
            }
            return(MemoryPressure.Low);
        }
Exemple #10
0
        public static void GetGCMemoryInfo()
        {
            RemoteExecutor.Invoke(() =>
            {
                // Allows to update the value returned by GC.GetGCMemoryInfo
                GC.Collect();

                GCMemoryInfo memoryInfo1 = GC.GetGCMemoryInfo();

                Assert.InRange(memoryInfo1.HighMemoryLoadThresholdBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.MemoryLoadBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.TotalAvailableMemoryBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.HeapSizeBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.FragmentedBytes, 0, long.MaxValue);

                GCHandle[] gch = new GCHandle[64 * 1024];
                for (int i = 0; i < gch.Length * 2; ++i)
                {
                    byte[] arr = new byte[64];
                    if (i % 2 == 0)
                    {
                        gch[i / 2] = GCHandle.Alloc(arr, GCHandleType.Pinned);
                    }
                }

                // Allows to update the value returned by GC.GetGCMemoryInfo
                GC.Collect();

                GCMemoryInfo memoryInfo2 = GC.GetGCMemoryInfo();

                Assert.Equal(memoryInfo2.HighMemoryLoadThresholdBytes, memoryInfo1.HighMemoryLoadThresholdBytes);
                Assert.InRange(memoryInfo2.MemoryLoadBytes, memoryInfo1.MemoryLoadBytes, long.MaxValue);
                Assert.Equal(memoryInfo2.TotalAvailableMemoryBytes, memoryInfo1.TotalAvailableMemoryBytes);
                Assert.InRange(memoryInfo2.HeapSizeBytes, memoryInfo1.HeapSizeBytes + 1, long.MaxValue);
                Assert.InRange(memoryInfo2.FragmentedBytes, memoryInfo1.FragmentedBytes + 1, long.MaxValue);
            }).Dispose();
        }
Exemple #11
0
        [ConditionalFact(typeof(PlatformDetection), nameof(PlatformDetection.IsNotArmProcess))] // [ActiveIssue(37378)]
        public static void GetGCMemoryInfo()
        {
            RemoteExecutor.Invoke(() =>
            {
                // Allows to update the value returned by GC.GetGCMemoryInfo
                GC.Collect();

                GCMemoryInfo memoryInfo1 = GC.GetGCMemoryInfo();

                Assert.InRange(memoryInfo1.HighMemoryLoadThresholdBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.MemoryLoadBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.TotalAvailableMemoryBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.HeapSizeBytes, 1, long.MaxValue);
                Assert.InRange(memoryInfo1.FragmentedBytes, 0, long.MaxValue);

                GCHandle[] gch = new GCHandle[64 * 1024];
                for (int i = 0; i < gch.Length * 2; ++i)
                {
                    byte[] arr = new byte[64];
                    if (i % 2 == 0)
                    {
                        gch[i / 2] = GCHandle.Alloc(arr, GCHandleType.Pinned);
                    }
                }

                // Allows to update the value returned by GC.GetGCMemoryInfo
                GC.Collect();

                GCMemoryInfo memoryInfo2 = GC.GetGCMemoryInfo();

                string scenario = null;
                try
                {
                    scenario = nameof(memoryInfo2.HighMemoryLoadThresholdBytes);
                    Assert.Equal(memoryInfo2.HighMemoryLoadThresholdBytes, memoryInfo1.HighMemoryLoadThresholdBytes);

                    // Even though we have allocated, the overall load may decrease or increase depending what other processes are doing.
                    // It cannot go above total available though.
                    scenario = nameof(memoryInfo2.MemoryLoadBytes);
                    Assert.InRange(memoryInfo2.MemoryLoadBytes, 1, memoryInfo1.TotalAvailableMemoryBytes);

                    scenario = nameof(memoryInfo2.TotalAvailableMemoryBytes);
                    Assert.Equal(memoryInfo2.TotalAvailableMemoryBytes, memoryInfo1.TotalAvailableMemoryBytes);

                    scenario = nameof(memoryInfo2.HeapSizeBytes);
                    Assert.InRange(memoryInfo2.HeapSizeBytes, memoryInfo1.HeapSizeBytes + 1, long.MaxValue);

                    scenario = nameof(memoryInfo2.FragmentedBytes);
                    Assert.InRange(memoryInfo2.FragmentedBytes, memoryInfo1.FragmentedBytes + 1, long.MaxValue);

                    scenario = null;
                }
                finally
                {
                    if (scenario != null)
                    {
                        System.Console.WriteLine("FAILED: " + scenario);
                    }
                }
            }).Dispose();
        }
    public static int Main()
    {
        // We will keep executing the test in case of a failure to see if we have multiple failures.
        bool isTestSucceeded = true;

        try
        {
            GCMemoryInfo memoryInfoInvalid = GC.GetGCMemoryInfo((GCKind)(-1));
        }
        catch (Exception e)
        {
            if (e is ArgumentOutOfRangeException)
            {
                Console.WriteLine("caught arg exception as expected: {0}", e);
            }
            else
            {
                isTestSucceeded = false;
            }
        }

        while (GC.CollectionCount(0) == 0)
        {
            MakeTemporarySOHAllocations();
        }

        if (!PrintGCMemoryInfo(GCKind.Ephemeral))
        {
            isTestSucceeded = false;
        }
        GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo(GCKind.Ephemeral);

        if (memoryInfo.GenerationInfo[0].SizeBeforeBytes < memoryInfo.GenerationInfo[0].SizeAfterBytes)
        {
            Console.WriteLine("Allocated only temp objects yet gen0 size didn't shrink! {0}->{1}",
                              memoryInfo.GenerationInfo[0].SizeBeforeBytes, memoryInfo.GenerationInfo[0].SizeAfterBytes);

            isTestSucceeded = false;
        }

        List <byte[]> listByteArray = new List <byte[]>();

        listByteArray.Add(new byte[3 * 1024 * 1024]);
        listByteArray.Add(new byte[4 * 1024 * 1024]);

        GC.Collect();
        GC.Collect();

        GCMemoryInfo memoryInfoLastNGC2  = GC.GetGCMemoryInfo(GCKind.FullBlocking);
        GCMemoryInfo memoryInfoLastAnyGC = GC.GetGCMemoryInfo();

        if (memoryInfoLastNGC2.Index != memoryInfoLastAnyGC.Index)
        {
            Console.WriteLine("FAILED: last GC#{0} should be NGC2 but was not", memoryInfoLastAnyGC.Index);
            isTestSucceeded = false;
        }

        object obj = MakeLongLivedSOHAllocations();

        GC.Collect(1);
        GC.Collect();
        GC.Collect(2, GCCollectionMode.Default, false);
        if (!PrintGCMemoryInfo(GCKind.Any))
        {
            isTestSucceeded = false;
        }
        long lastNGC2Index      = GC.GetGCMemoryInfo(GCKind.FullBlocking).Index;
        long lastEphemeralIndex = GC.GetGCMemoryInfo(GCKind.Ephemeral).Index;

        GC.Collect();
        if (!PrintGCMemoryInfo(GCKind.Any))
        {
            isTestSucceeded = false;
        }

        GC.Collect(2, GCCollectionMode.Default, false);
        if (!PrintGCMemoryInfo(GCKind.Any))
        {
            isTestSucceeded = false;
        }
        if (!PrintGCMemoryInfo(GCKind.FullBlocking))
        {
            isTestSucceeded = false;
        }
        if (!PrintGCMemoryInfo(GCKind.Background))
        {
            isTestSucceeded = false;
        }
        if (!PrintGCMemoryInfo(GCKind.Ephemeral))
        {
            isTestSucceeded = false;
        }

        long currentNGC2Index      = GC.GetGCMemoryInfo(GCKind.FullBlocking).Index;
        long currentEphemeralIndex = GC.GetGCMemoryInfo(GCKind.Ephemeral).Index;

        if (lastNGC2Index >= currentNGC2Index)
        {
            Console.WriteLine("FAILED: We did an additional NGC2, yet last NGC2 index is {0} and current is {1}",
                              lastNGC2Index, currentNGC2Index);
            isTestSucceeded = false;
        }

        if (lastEphemeralIndex != currentEphemeralIndex)
        {
            Console.WriteLine("FAILED: No ephemeral GCs happened so far, yet last eph index is {0} and current is {1}",
                              lastEphemeralIndex, currentEphemeralIndex);
            isTestSucceeded = false;
        }

        Console.WriteLine("listByteArray has {0} elements, obj is of type {1}",
                          listByteArray.Count, obj.GetType());
        Console.WriteLine("test {0}", (isTestSucceeded ? "succeeded" : "failed"));
        return(isTestSucceeded ? 100 : 1);
    }
        public static double GetAvailableMemoryGiB()
        {
            GCMemoryInfo gcMemoryInfo = GC.GetGCMemoryInfo();

            return(FromBytesToGiB(gcMemoryInfo.TotalAvailableMemoryBytes - gcMemoryInfo.MemoryLoadBytes));
        }
Exemple #14
0
 private Block(int id, GCMemoryInfo i = default) => _id = id;
        private bool ConcurrentPurge()
        {
            const int    LowAfterMilliseconds    = 180 * 1000; // Trim after 60 seconds for low pressure.
            const int    MediumAfterMilliseconds = 90 * 1000;  // Trim after 60 seconds for medium pressure.
            const double HighPressureThreshold   = .90;        // Percent of GC memory pressure threshold we consider "high".
            const double MediumPressureThreshold = .70;        // Percent of GC memory pressure threshold we consider "medium".

            int currentMilliseconds = Environment.TickCount;

            for (int j = 0; j < PurgeAttempts; j++)
            {
                // The callback was called before finishing the previous purge.
                if ((state & IS_PURGING) != 0)
                {
                    return(true);
                }

                if (state == IS_DISPOSED_OR_DISPOSING)
                {
                    return(false);
                }

                MassiveWriteBegin();
                {
                    if (state == IS_DISPOSED_OR_DISPOSING)
                    {
                        WriteEnd();
                        return(false);
                    }

                    if (holdersCount == 0)
                    {
                        WriteEnd();
                        return(true);
                    }

                    int trimMilliseconds;

#if NET5_0_OR_GREATER
                    GCMemoryInfo memoryInfo = GC.GetGCMemoryInfo();

                    if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * HighPressureThreshold)
                    {
                        trimMilliseconds = 0;
                    }
                    else if (memoryInfo.MemoryLoadBytes >= memoryInfo.HighMemoryLoadThresholdBytes * MediumPressureThreshold)
                    {
                        trimMilliseconds = MediumAfterMilliseconds;
                    }
                    else
                    {
                        trimMilliseconds = LowAfterMilliseconds;
                    }
#else
                    trimMilliseconds = 0;
#endif

                    if (millisecondsTimeStamp == 0)
                    {
                        millisecondsTimeStamp = currentMilliseconds;
                    }

                    if ((currentMilliseconds - millisecondsTimeStamp) <= trimMilliseconds)
                    {
                        WriteEnd();
                        return(true);
                    }

                    state = IS_PURGING;

                    Debug.Assert(holders is not null, "Impossible state, since holders initialization happens at the same time AutoPurger is instantiated.");
                    if (holdersCount > 1)
                    {
                        purgingIndex += (int)(Parallel.For(0, holdersCount, options ??= new(), autoPurgeAction ??= new Action <int, ParallelLoopState>((index, loop) =>
                        {
                            if (loop.ShouldExitCurrentIteration)
                            {
                                return;
                            }

                            if ((state & IS_CANCELATION_REQUESTED) != 0)
                            {
                                loop.Stop();
                            }

                            Utils.ExpectAssignableType <InvokersHolder>(holders[(purgingIndex + index) % holdersCount]).Purge();
                        })).LowestBreakIteration ?? 0);
                    }
                    else if (holdersCount == 1)
                    {
                        Utils.ExpectAssignableType <InvokersHolder>(holders[0]).Purge();
                    }

                    bool isCancelationRequested = (state & IS_CANCELATION_REQUESTED) != 0;

                    int holdersCountOld        = holdersCount;
                    int holdersCount_          = holdersCountOld;
                    InvariantObject[] holders_ = holders;
                    // TODO: This loop could actually be done without locking the whole event manager,
                    // as long as the `holders` array is locked.
                    for (int i = 0; i < holdersCount_; i++)
                    {
                        InvokersHolder holder = Utils.ExpectAssignableType <InvokersHolder>(holders_[i].Value);
                        if (holder.RemoveIfEmpty())
                        {
                            holdersPerType.Remove(holder.GetType());
                            managersPerType[holder.GetEventType()].Remove(holder);

                            while (true)
                            {
                                if (--holdersCount_ == i)
                                {
                                    goto end;
                                }
                                holder = Utils.ExpectAssignableType <InvokersHolder>(holders_[holdersCount_].Value);
                                if (!holder.RemoveIfEmpty())
                                {
                                    break;
                                }
                                holdersPerType.Remove(holder.GetType());
                                managersPerType[holder.GetEventType()].Remove(holder);
                            }
                            holders_[i] = holders_[i + 1];
                        }
                    }
end:
                    if (!ArrayUtils.TryShrink(ref holders_, holdersCount_) && holdersCount_ != holdersCountOld)
                    {
                        Array.Clear(holders_, holdersCount_, holdersCountOld - holdersCount_);
                    }
                    holders      = holders_;
                    holdersCount = holdersCount_;

                    Lock(ref stateLock);
                    {
                        state = 0;
                    }
                    Unlock(ref stateLock);

                    WriteEnd();

                    if (isCancelationRequested && Thread.Yield())
                    {
                        continue;
                    }

                    return(true);
                }
            }

            Debug.Fail("Impossible state.");

            return(true);
        }