< Summary

Information
Class: AmbientServices.Test.TestFifoTaskScheduler.MockCpuUsage
Assembly: AmbientServices.Async.Test
File(s): /home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async.Test/TestFifoTaskScheduler.cs
Tag: 76_25271648613
Line coverage
100%
Covered lines: 1
Uncovered lines: 0
Coverable lines: 1
Total lines: 719
Line coverage: 100%
Branch coverage
N/A
Covered branches: 0
Total branches: 0
Branch coverage: N/A
Method coverage

Feature is only available for sponsors

Upgrade to PRO version

Metrics

MethodBranch coverage Crap Score Cyclomatic complexity Line coverage
get_RecentUsage()100%11100%
set_RecentUsage(...)100%11100%

File(s)

/home/runner/work/AmbientServices.Async/AmbientServices.Async/AmbientServices.Async.Test/TestFifoTaskScheduler.cs

#LineLine coverage
 1using AmbientServices;
 2using AmbientServices.Utilities;
 3using System;
 4using System.Collections.Concurrent;
 5using System.Collections.Generic;
 6using System.Diagnostics;
 7using System.Linq;
 8using System.Threading;
 9using System.Threading.Tasks;
 10
 11#nullable enable
 12
 13namespace AmbientServices.Test
 14{
 15    [TestClass]
 16    public class TestFifoTaskScheduler
 17    {
 18        private static readonly AmbientService<IAmbientLogger> LoggerBackend = Ambient.GetService<IAmbientLogger>();
 19        private static readonly AmbientService<IAmbientStatistics> StatisticsBackend = Ambient.GetService<IAmbientStatis
 20        private static readonly AmbientService<IMockCpuUsage> MockCpu = Ambient.GetService<IMockCpuUsage>();
 21        private static int TasksInlined;
 22
 23        private static void FifoTaskScheduler_TaskInlined(object? sender, TaskInlinedEventArgs e)
 24        {
 25            Interlocked.Increment(ref TasksInlined);
 26        }
 27
 28        [TestMethod]
 29        public void UnhandledException()
 30        {
 31            Guid unique = Guid.NewGuid();
 32            UnhandledExceptionTracker tracker = new(unique);
 33            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(UnhandledException), priority: ThreadPrio
 34            try
 35            {
 36                FifoTaskScheduler.UnhandledException += tracker.FifoTaskScheduler_UnhandledException;
 37                scheduler.ExecuteAction(() => throw new ExpectedException(nameof(UnhandledException) + ":" + unique));
 38            }
 39            finally
 40            {
 41                FifoTaskScheduler.UnhandledException -= tracker.FifoTaskScheduler_UnhandledException;
 42            }
 43            Assert.AreEqual(1, tracker.UnhandledExceptions);
 44            Assert.AreEqual(1, tracker.FifoTaskSchedulerCount);
 45        }
 46        [TestMethod]
 47        public void TaskInlinedEvent()
 48        {
 49            try
 50            {
 51                FifoTaskScheduler.TaskInlined += FifoTaskScheduler_TaskInlined;
 52                // force a task to run inline
 53                using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(TaskInlinedEvent), 1, 2, 10);
 54                FifoTaskFactory factory = new(scheduler);
 55                List<Task> tasks = new();
 56                CancellationTokenSource cts = new();
 57                for (int i = 0; i < 2000; ++i)
 58                {
 59                    FakeWork w = new(i, true);
 60                    tasks.Add(factory.StartNew(() =>
 61                    {
 62                        if (TasksInlined > 0) { cts.Cancel(); return Task.CompletedTask; }
 63                        return w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask();
 64                    }, CancellationToken.None, TaskCreationOptions.None, scheduler));
 65                }
 66                Task.WaitAll(tasks.ToArray());
 67                if (TasksInlined > 0) return;   // this works too!
 68            }
 69            catch (OperationCanceledException)
 70            {
 71                // we expect to get canceled for this test!
 72                return;
 73            }
 74            finally
 75            {
 76                FifoTaskScheduler.TaskInlined -= FifoTaskScheduler_TaskInlined;
 77            }
 78            Assert.Fail("Unable to force task to run inline!");
 79        }
 80
 81        [TestMethod]
 82        public async Task RunWithStartNew()
 83        {
 84            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 85            List<Task<Task>> tasks = new();
 86            for (int i = 0; i < 1000; ++i)
 87            {
 88                FakeWork w = new(i, true);
 89                tasks.Add(FifoTaskFactory.Default.StartNew(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 90            }
 91            await Task.WhenAll(tasks.ToArray());
 92            foreach (Task<Task> task in tasks)
 93            {
 94                await task.Result;
 95            }
 96            FifoTaskScheduler.Default.Reset();
 97        }
 98        [TestMethod]
 99        public async Task RunWithFunc()
 100        {
 101            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 102            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunWithFunc), priority: ThreadPriority.Hi
 103            List<Task<Task>> tasks = new();
 104            for (int i = 0; i < 1000; ++i)
 105            {
 106                FakeWork w = new(i, true);
 107                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 108            }
 109            Task.WaitAll(tasks.ToArray());
 110            foreach (Task<Task> task in tasks)
 111            {
 112                await task.Result;
 113            }
 114            scheduler.Reset();
 115        }
 116        [TestMethod]
 117        public async Task RunWithAction()
 118        {
 119            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 120            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunWithAction), priority: ThreadPriority.
 121            ConcurrentBag<Task> tasks = new();
 122            for (int i = 0; i < 1000; ++i)
 123            {
 124                FakeWork w = new(i, true);
 125                tasks.Add(scheduler.Run(() => { tasks.Add(w.DoMixedWorkAsync(CancellationToken.None).AsTask()); }));
 126            }
 127            while (tasks.Count < 1000)
 128            {
 129                await Task.Delay(25);
 130            }
 131            Task.WaitAll(tasks.ToArray());
 132            scheduler.Reset();
 133        }
 134        [TestMethod]
 135        public async Task RunFireAndForget()
 136        {
 137            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 138            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(RunFireAndForget), priority: ThreadPriori
 139            ConcurrentBag<Task> tasks = new();
 140            for (int i = 0; i < 1000; ++i)
 141            {
 142                FakeWork w = new(i, true);
 143                scheduler.FireAndForget(() => tasks.Add(w.DoMixedWorkAsync(CancellationToken.None).AsTask()));
 144            }
 145            while (tasks.Count < 1000)
 146            {
 147                await Task.Delay(25);
 148            }
 149            Task.WaitAll(tasks.ToArray());
 150            scheduler.Reset();
 151        }
 152        [TestMethod]
 153        public async Task NoStatsStartNew()
 154        {
 155            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 156            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsStartNew), priority: ThreadPriorit
 157            FifoTaskFactory testFactory = new(scheduler);
 158            List<Task<Task>> tasks = new();
 159            tasks.Add(scheduler.Run(() => new FakeWork(-1, true).DoMixedWorkAsync(CancellationToken.None).AsTask()));   
 160            for (int i = 0; i < 100; ++i)
 161            {
 162                FakeWork w = new(i, true);
 163                tasks.Add(testFactory.StartNew(() => w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask()));
 164            }
 165            Task.WaitAll(tasks.ToArray());
 166            foreach (Task<Task> task in tasks)
 167            {
 168                await task.Result;
 169            }
 170            await scheduler.Run(() => new ValueTask());
 171            scheduler.Reset();
 172        }
 173        [TestMethod]
 174        public async Task NoStatsRunWithAction()
 175        {
 176            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 177            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsRunWithAction), priority: ThreadPr
 178            FifoTaskFactory testFactory = new(scheduler);
 179            Task? task = null;
 180            await scheduler.Run(() => { task = new FakeWork(1, true).DoDelayOnlyWorkAsync(CancellationToken.None).AsTask
 181            while (task == null)
 182            {
 183                await Task.Delay(25);
 184            }
 185        }
 186        [TestMethod]
 187        public async Task NoStatsFireAndForget()
 188        {
 189            using IDisposable d = StatisticsBackend.ScopedLocalOverride(null);
 190            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(NoStatsFireAndForget), priority: ThreadPr
 191            FifoTaskFactory testFactory = new(scheduler);
 192            Task? task = null;
 193            scheduler.FireAndForget(() => task = new FakeWork(1, true).DoDelayOnlyWorkAsync(CancellationToken.None).AsTa
 194            while (task == null)
 195            {
 196                await Task.Delay(25);
 197            }
 198        }
 199        [TestMethod]
 200        public void ExecuteWithCatchAndLog()
 201        {
 202            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExecuteWithCatchAndLog));
 203            scheduler.ExecuteWithCatchAndLog(() => throw new ExpectedException());
 204            scheduler.ExecuteWithCatchAndLog(() => throw new TaskCanceledException());
 205            //scheduler.ExecuteWithCatchAndLog(() => throw new ThreadAbortException()); // can't construct this, so can'
 206        }
 207        [TestMethod]
 208        public void Constructors()
 209        {
 210            FifoTaskFactory testFactory;
 211            testFactory = new(CancellationToken.None);
 212            testFactory = new(FifoTaskScheduler.Default);
 213            testFactory = new(TaskCreationOptions.None, TaskContinuationOptions.None);
 214        }
 215        class MockCpuUsage : IMockCpuUsage
 216        {
 1217            public float RecentUsage { get; set; }
 218        }
 219        [TestMethod]
 220        public void TooManyWorkers()
 221        {
 222            MockCpuUsage mockCpu = new();
 223            using IDisposable d = MockCpu.ScopedLocalOverride(mockCpu);
 224            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(TooManyWorkers), 1, 2, 10);
 225            Assert.IsGreaterThan(0, scheduler.ReadyWorkers);
 226            Assert.AreEqual(0, scheduler.BusyWorkers);
 227            FifoTaskFactory factory = new(scheduler);
 228            List<Task> tasks = new();
 229            // the scheduler should think the CPU is very high, so it should enter the crazy usage notify
 230            mockCpu.RecentUsage = 1.0f;
 231            for (int i = 0; i < 20; ++i)
 232            {
 233                FakeWork w = new(i, true);
 234                tasks.Add(factory.StartNew(() => w.DoDelayOnlyWorkAsync(CancellationToken.None).AsTask(), CancellationTo
 235            }
 236            Task.WaitAll(tasks.ToArray());
 237            scheduler.Reset();
 238        }
 239        [TestMethod]
 240        public async Task ResetManyWorkers()
 241        {
 242            MockCpuUsage mockCpu = new();
 243            using IDisposable s = StatisticsBackend.ScopedLocalOverride(null);
 244            using IDisposable c = MockCpu.ScopedLocalOverride(mockCpu);
 245            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ResetManyWorkers), 1, 100, 10);
 246            DateTime lastScaleUp = scheduler.LastScaleUp;
 247            Debug.Assert(DateTime.UtcNow > lastScaleUp);
 248            DateTime lastScaleDown = scheduler.LastScaleDown;
 249            Debug.Assert(DateTime.UtcNow > lastScaleDown);
 250            DateTime lastReset = scheduler.LastResetTime;
 251            Debug.Assert(DateTime.UtcNow > lastReset);
 252            ConcurrentBag<Task> tasks = new();
 253            int i;
 254            for (i = 0; i < 250 && scheduler.ReadyWorkers < 3; ++i)
 255            {
 256                FakeWork w = new(i, true);
 257                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));       // note that 
 258            }
 259            // reset the scheduler
 260            scheduler.Reset();
 261
 262            Task.WaitAll(tasks.ToArray());
 263            Assert.IsTrue(scheduler.LastScaleUp > lastScaleUp, $"No scale up, i={i}, threads={scheduler.Workers}");
 264
 265            // wait for a while for the reset to finish
 266            while (scheduler.LastResetTime <= lastReset && DateTime.UtcNow < lastReset.AddSeconds(30))
 267            {
 268                // reset the scheduler again just in case
 269                scheduler.Reset();
 270                await Task.Delay(50);
 271            }
 272            Assert.IsTrue(scheduler.LastResetTime > lastReset, $"No reset, {scheduler.LastResetTime} vs {lastReset} vs {
 273        }
 274        [TestMethod]
 275        public async Task ScaleDown()
 276        {
 277            MockCpuUsage mockCpu = new();
 278            using IDisposable d = MockCpu.ScopedLocalOverride(mockCpu);     // install a mock CPU so that the scheduler 
 279            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ScaleDown), 1, 25, 10);
 280            DateTime lastScaleUp = scheduler.LastScaleUp;
 281            Debug.Assert(DateTime.UtcNow > lastScaleUp);
 282            DateTime lastScaleDown = scheduler.LastScaleDown;
 283            Debug.Assert(DateTime.UtcNow > lastScaleDown);
 284            FifoTaskFactory factory = new(scheduler);
 285            List<Task<Task>> tasks = new();
 286            int i;
 287            for (i = 0; i < 100 && scheduler.Workers < 10; ++i)
 288            {
 289                FakeWork w = new(i, true);
 290                tasks.Add(scheduler.Run(() => w.DoMixedWorkAsync(CancellationToken.None).AsTask()));       // note that 
 291            }
 292            Task.WaitAll(tasks.ToArray());
 293            Assert.IsTrue(scheduler.LastScaleUp > lastScaleUp, $"No scale up, threads={scheduler.Workers}");
 294            // now wait for a while for the master thread to scale things down
 295            while (scheduler.LastScaleDown <= lastScaleDown && DateTime.UtcNow < lastScaleDown.AddSeconds(30))
 296            {
 297                await Task.Delay(50);
 298            }
 299            Assert.IsTrue(scheduler.LastScaleDown > lastScaleDown, $"No scale down, {scheduler.LastScaleDown} vs {lastSc
 300        }
 301        [TestMethod]
 302        public async Task StartNewException()
 303        {
 304            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 305            await Assert.ThrowsExactlyAsync<ExpectedException>(async () => await FifoTaskFactory.Default.StartNew(() => 
 306        }
 307        [TestMethod]
 308        public void DisposedException()
 309        {
 310            FifoTaskFactory test;
 311            using (FifoTaskScheduler testScheduler = FifoTaskScheduler.Start(nameof(DisposedException)))
 312            {
 313                test = new(testScheduler);
 314            }
 315            Assert.Throws<TaskSchedulerException>(() => test.StartNew(() => { }));     // our code throws an ObjectDispo
 316        }
 317
 318        [TestMethod]        // Note that this test will fail when run in isolation, possibly because the garbage collect
 319        public async Task UnobservedTaskException()
 320        {
 321            //using IDisposable d = LoggerBackend.ScopedLocalOverride(new AmbientTraceLogger());
 322            Guid unique = Guid.NewGuid();
 323            UnhandledExceptionTracker tracker = new(unique);
 324            try
 325            {
 326                TaskScheduler.UnobservedTaskException += tracker.TaskScheduler_UnobservedTaskException;
 327                FifoTaskScheduler.UnhandledException += tracker.TaskScheduler_UnobservedTaskException;
 328                StartTaskWithUnobservedException(unique);
 329                for (int loop = 0; loop < 100; ++loop)
 330                {
 331                    if (tracker.UnhandledExceptions > 0) break;
 332                    await Task.Delay(100);
 333                    GC.Collect(2, GCCollectionMode.Forced, true, true);
 334                    GC.WaitForPendingFinalizers();
 335                }
 336                Assert.IsGreaterThanOrEqualTo(1, tracker.UnhandledExceptions);
 337                Assert.IsGreaterThanOrEqualTo(1, tracker.TaskSchedulerCount);
 338            }
 339            finally
 340            {
 341                FifoTaskScheduler.UnhandledException -= tracker.TaskScheduler_UnobservedTaskException;
 342                TaskScheduler.UnobservedTaskException -= tracker.TaskScheduler_UnobservedTaskException;
 343            }
 344        }
 345        private static void StartTaskWithUnobservedException(Guid unique)
 346        {
 347            _ = FifoTaskFactory.Default.StartNew(() => ThrowExpectedUnobservedTaskException(unique));
 348        }
 349
 350        class UnhandledExceptionTracker
 351        {
 352            private Guid _unique;
 353            private int _unhandledExceptions;
 354            private int _taskSchedulerCount;
 355            private int _fifoTaskSchedulerCount;
 356
 357            internal UnhandledExceptionTracker(Guid unique)
 358            {
 359                _unique = unique;
 360            }
 361
 362            internal int UnhandledExceptions => _unhandledExceptions;
 363            internal int TaskSchedulerCount => _taskSchedulerCount;
 364            internal int FifoTaskSchedulerCount => _fifoTaskSchedulerCount;
 365
 366            internal void TaskScheduler_UnobservedTaskException(object? sender, UnobservedTaskExceptionEventArgs e)
 367            {
 368                Assert.AreEqual(typeof(AggregateException), e.Exception?.GetType());
 369                Assert.AreEqual(typeof(ExpectedException), e.Exception?.InnerExceptions[0].GetType());
 370                if ((e.Exception?.InnerExceptions[0] as ExpectedException)?.TestName?.Contains(_unique.ToString()) ?? fa
 371                Interlocked.Increment(ref _taskSchedulerCount);
 372                e.SetObserved();
 373            }
 374            internal void FifoTaskScheduler_UnhandledException(object? sender, UnobservedTaskExceptionEventArgs e)
 375            {
 376                Assert.AreEqual(typeof(AggregateException), e.Exception?.GetType());
 377                Assert.AreEqual(typeof(ExpectedException), e.Exception?.InnerExceptions[0].GetType());
 378                if ((e.Exception?.InnerExceptions[0] as ExpectedException)?.TestName?.Contains(_unique.ToString()) ?? fa
 379                Interlocked.Increment(ref _fifoTaskSchedulerCount);
 380                e.SetObserved();
 381            }
 382        }
 383
 384        private static void ThrowExpectedUnobservedTaskException(Guid unique)
 385        {
 386            throw new ExpectedException(nameof(UnobservedTaskException) + ":" + unique);
 387        }
 388        private static void ThrowExpectedException()
 389        {
 390            throw new ExpectedException();
 391        }
 392        private static async Task RunAndUnwrapAggregateExceptions(Func<Task> f)
 393        {
 394            try
 395            {
 396                await f();
 397            }
 398            catch (AggregateException ex)
 399            {
 400                Async.ConvertAggregateException(ex);
 401                throw;
 402            }
 403        }
 404        [TestMethod]
 405        public void GetScheduledTasks()
 406        {
 407            IEnumerable<Task> tasks = FifoTaskScheduler.Default.GetScheduledTasksDirect();
 408            Assert.AreEqual(0, tasks.Count());
 409        }
 410        [TestMethod]
 411        public async Task InvokeException()
 412        {
 413            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.Run<int>(null!));
 414            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.TransferWork((Func<Va
 415            await Assert.ThrowsExactlyAsync<ArgumentNullException>(() => FifoTaskScheduler.Default.TransferWork((Func<Va
 416            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.Run(null!));
 417            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.FireAndForget(null!));
 418            await Assert.ThrowsExactlyAsync<ExpectedException>(() => FifoTaskScheduler.Default.Run<int>(() => throw new 
 419            await Assert.ThrowsExactlyAsync<ExpectedException>(() => FifoTaskScheduler.Default.Run(() => throw new Expec
 420        }
 421        [TestMethod]
 422        public void QueueTaskExceptions()
 423        {
 424            Assert.ThrowsExactly<ArgumentNullException>(() => FifoTaskScheduler.Default.QueueTaskDirect(null!));
 425        }
 426        [TestMethod]
 427        public void Properties()
 428        {
 429            Assert.IsGreaterThanOrEqualTo(0, FifoTaskScheduler.Default.Workers);
 430            Assert.IsGreaterThanOrEqualTo(0, FifoTaskScheduler.Default.BusyWorkers);
 431            Assert.IsGreaterThanOrEqualTo(Environment.ProcessorCount, FifoTaskScheduler.Default.MaximumConcurrencyLevel)
 432        }
 433        [TestMethod]
 434        public void IntrusiveSinglyLinkedList()
 435        {
 436            InterlockedSinglyLinkedList<NodeTest> list1 = new();
 437            InterlockedSinglyLinkedList<NodeTest> list2 = new();
 438            NodeTest node = new() { Value = 1 };
 439            list1.Push(node);
 440            Assert.ThrowsExactly<ArgumentNullException>(() => list2.Push(null!));
 441            Assert.ThrowsExactly<InvalidOperationException>(() => list2.Push(node));
 442            list1.Validate();
 443            Assert.AreEqual(1, list1.Count);
 444            list1.Clear();
 445            Assert.AreEqual(0, list1.Count);
 446            list2.Push(node);
 447            NodeTest? popped = list2.Pop();
 448            Assert.AreEqual(node, popped);
 449        }
 450        class NodeTest : IntrusiveSinglyLinkedListNode
 451        {
 452            public int Value { get; set; }
 453        }
 454        [TestMethod]
 455        public async Task Worker()
 456        {
 457            FifoTaskScheduler? scheduler = null;
 458            try
 459            {
 460                scheduler = FifoTaskScheduler.Start(nameof(Worker));
 461                FifoWorker worker = FifoWorker.Start(scheduler, "1", ThreadPriority.Normal);  // the worker disposes of 
 462                worker.Invoke(LongWait);
 463                Assert.IsTrue(worker.IsBusy);
 464                Assert.ThrowsExactly<InvalidOperationException>(() => worker.Invoke(LongWait));
 465                Assert.IsFalse(FifoWorker.IsWorkerInternalMethod(null));
 466                Assert.IsFalse(FifoWorker.IsWorkerInternalMethod(typeof(TestFifoTaskScheduler).GetMethod(nameof(Worker))
 467                Assert.IsTrue(FifoWorker.IsWorkerInternalMethod(typeof(FifoWorker).GetMethod("Invoke", System.Reflection
 468                Assert.IsTrue(FifoWorker.IsWorkerInternalMethod(typeof(FifoWorker).GetMethod("WorkerFunc", System.Reflec
 469                worker.Stop();
 470                Assert.ThrowsExactly<InvalidOperationException>(() => worker.Invoke(null!));
 471            }
 472            finally
 473            {
 474                scheduler?.Dispose();
 475            }
 476            Assert.ThrowsExactly<ObjectDisposedException>(() => scheduler.Run(() => { }));
 477            Assert.ThrowsExactly<ObjectDisposedException>(() => scheduler.FireAndForget(() => { }));
 478            await Assert.ThrowsExactlyAsync<ObjectDisposedException>(() => scheduler.Run(() => new ValueTask()));
 479        }
 480        public void LongWait()
 481        {
 482            Thread.Sleep(5000);
 483        }
 484        [TestMethod]
 485        public void WorkerNullWork()
 486        {
 487            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(WorkerNullWork));
 488            FifoWorker worker = scheduler.CreateWorker();    // the worker disposes of itself
 489            worker.Invoke(null!);
 490        }
 491        private AsyncLocal<int> ali = new();
 492        [TestMethod]
 493        public async Task AsyncLocalFlow()
 494        {
 495            int testvalue = 48902343;
 496            ali.Value = testvalue;
 497            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(AsyncLocalFlow));
 498            TaskCompletionSource<bool> completion = new();
 499            await Task.Factory.StartNew(() =>
 500            {
 501                Assert.AreEqual(testvalue, ali.Value);
 502            }, CancellationToken.None, TaskCreationOptions.None, scheduler);
 503            await scheduler.Run(() => Assert.AreEqual(testvalue, ali.Value));
 504        }
 505        [TestMethod]
 506        public async Task ExceptionHandling1()
 507        {
 508            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling1));
 509            scheduler.FireAndForget(() => throw new ExpectedException());
 510            await Task.Delay(1000);
 511        }
 512        [TestMethod]
 513        public async Task ExceptionHandling2()
 514        {
 515            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling2));
 516            Task? t = null;
 517            try
 518            {
 519                t = ThrowExceptionAsyncTask();
 520                await t;
 521            }
 522            catch
 523            {
 524            }
 525            scheduler.QueueTaskDirect(t!);
 526            await Task.Delay(1000);
 527        }
 528        [TestMethod]
 529        public async Task ExceptionHandling4()
 530        {
 531            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling4));
 532            await ExpectExceptionTask(() => scheduler.Run(() => throw new ExpectedException()));
 533            await Task.Delay(1000);
 534        }
 535        [TestMethod]
 536        public async Task ExceptionHandling8()
 537        {
 538            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling8));
 539            await ExpectException(async () => await scheduler.Run(() => throw new ExpectedException()));
 540            await Task.Delay(1000);
 541        }
 542        [TestMethod]
 543        public async Task ExceptionHandling9()
 544        {
 545            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling9));
 546            await ExpectException(async () => await scheduler.TransferWork(ThrowExceptionAsync));
 547            await Task.Delay(1000);
 548        }
 549        [TestMethod]
 550        public async Task ExceptionHandling10()
 551        {
 552            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(ExceptionHandling10));
 553            await ExpectException(async () => await scheduler.TransferWork(ThrowExceptionAsyncType<int>));
 554            await Task.Delay(1000);
 555        }
 556        private async Task ThrowExceptionAsyncTask()
 557        {
 558            await Task.Delay(100);
 559            throw new ExpectedException();
 560        }
 561        private ValueTask ThrowExceptionAsync()
 562        {
 563            throw new ExpectedException();
 564        }
 565        private ValueTask<T> ThrowExceptionAsyncType<T>()
 566        {
 567            throw new ExpectedException();
 568        }
 569        private async ValueTask ExpectExceptionTask(Func<Task> t)
 570        {
 571            try
 572            {
 573                await t();
 574                throw new InvalidOperationException();
 575            }
 576            catch (ExpectedException)
 577            {
 578                // ok!
 579            }
 580        }
 581        private void ExpectException(Action a)
 582        {
 583            try
 584            {
 585                a();
 586                throw new InvalidOperationException();
 587            }
 588            catch (ExpectedException)
 589            {
 590                // ok!
 591            }
 592        }
 593        private async ValueTask ExpectException(Func<ValueTask> a)
 594        {
 595            try
 596            {
 597                await a();
 598                throw new InvalidOperationException();
 599            }
 600            catch (ExpectedException)
 601            {
 602                // ok!
 603            }
 604        }
 605        [TestMethod]
 606        public async Task StartNewAmbientThreadSchedulerSwitching()
 607        {
 608            using FifoTaskScheduler scheduler = FifoTaskScheduler.Start(nameof(StartNewAmbientThreadSchedulerSwitching))
 609            TaskCompletionSource<bool> completion = new();
 610            await Task.Factory.StartNew(() => VerifyTaskSchedulerRemainsCustom(), CancellationToken.None, TaskCreationOp
 611        }
 612        [TestMethod]
 613        public async Task TransferWorkAmbientThreadSchedulerSwitching()
 614        {
 615            await FifoTaskScheduler.Default.TransferWork(async () =>
 616            {
 617                await VerifyTaskSchedulerRemainsCustom();
 618            });
 619        }
 620
 621        private async Task VerifyTaskSchedulerRemainsCustom()
 622        {
 623            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 624            await Task.Yield();
 625            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 626            await Task.Delay(100);
 627            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 628            await Task.Delay(100).ConfigureAwait(true);
 629            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 630
 631            // ... more arbitrary async processing
 632
 633            Assert.IsFalse(ReferenceEquals(TaskScheduler.Current, TaskScheduler.Default));
 634        }
 635    }
 636    public class FakeWork
 637    {
 638        private readonly bool _fast;
 639        private readonly long _id;
 640
 641        public FakeWork(long id, bool fast)
 642        {
 643            _fast = fast;
 644            _id = id;
 645        }
 646
 647        public async ValueTask DoMixedWorkAsync(CancellationToken cancel = default, bool verifyHPThread = true)
 648        {
 649            ulong hash = GetHash(_id);
 650            await Task.Yield();
 651            //string? threadName = Thread.CurrentThread.Name;
 652
 653            if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 654            Stopwatch s = Stopwatch.StartNew();
 655            for (int outer = 0; outer < (int)(hash % 256) && !cancel.IsCancellationRequested; ++outer)
 656            {
 657                Stopwatch cpu = Stopwatch.StartNew();
 658                // use some CPU
 659                for (int spin = 0; spin < (int)((hash >> 6) % (_fast ? 16UL : 256UL)); ++spin)
 660                {
 661                    double d1 = 0.0000000000000001;
 662                    double d2 = 0.0000000000000001;
 663                    for (int inner = 0; inner < (_fast ? 100 : 1000000); ++inner) { d2 = d1 * d2; }
 664                }
 665                cpu.Stop();
 666                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 667                Stopwatch mem = Stopwatch.StartNew();
 668                // use some memory
 669                int bytesPerLoop = (int)((hash >> 12) % (_fast ? 10UL : 1024UL));
 670                int loops = (int)((hash >> 22) % 1024);
 671                for (int memory = 0; memory < loops; ++memory)
 672                {
 673                    byte[] bytes = new byte[bytesPerLoop];
 674                }
 675                mem.Stop();
 676                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 677                Stopwatch io = Stopwatch.StartNew();
 678                // simulate I/O by blocking
 679                await Task.Delay((int)((hash >> 32) % (_fast ? 5UL : 500UL)), cancel);
 680                io.Stop();
 681                if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 682            }
 683            if (verifyHPThread) Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 684            //Debug.WriteLine($"Ran work {_id} on {threadName}!", "Work");
 685        }
 686        public async ValueTask DoDelayOnlyWorkAsync(CancellationToken cancel = default)
 687        {
 688            ulong hash = GetHash(_id);
 689            await Task.Yield();
 690            //string? threadName = Thread.CurrentThread.Name;
 691
 692            Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 693            Stopwatch s = Stopwatch.StartNew();
 694            for (int outer = 0; outer < (int)(hash % 256) && !cancel.IsCancellationRequested; ++outer)
 695            {
 696                Stopwatch io = Stopwatch.StartNew();
 697                // simulate I/O by blocking
 698                await Task.Delay((int)((hash >> 32) % (_fast ? 5UL : 500UL)), cancel);
 699                io.Stop();
 700                Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 701            }
 702            Assert.AreEqual(typeof(FifoTaskScheduler), TaskScheduler.Current.GetType());
 703            //Debug.WriteLine($"Ran work {_id} on {threadName}!", "Work");
 704        }
 705        private static ulong GetHash(long id)
 706        {
 707            unchecked
 708            {
 709                ulong x = (ulong)id * 1_111_111_111_111_111_111UL;        // note that this is a prime number (but not a
 710                x = (((x & 0xaaaaaaaaaaaaaaaa) >> 1) | ((x & 0x5555555555555555) << 1));
 711                x = (((x & 0xcccccccccccccccc) >> 2) | ((x & 0x3333333333333333) << 2));
 712                x = (((x & 0xf0f0f0f0f0f0f0f0) >> 4) | ((x & 0x0f0f0f0f0f0f0f0f) << 4));
 713                x = (((x & 0xff00ff00ff00ff00) >> 8) | ((x & 0x00ff00ff00ff00ff) << 8));
 714                x = (((x & 0xffff0000ffff0000) >> 16) | ((x & 0x0000ffff0000ffff) << 16));
 715                return ((x >> 32) | (x << 32));
 716            }
 717        }
 718    }
 719}