rd-net/Test.Lifetimes/Lifetimes/LifetimeTest.cs (1,154 lines of code) (raw):

using System; using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; using System.Runtime.CompilerServices; using System.Threading; using System.Threading.Tasks; using JetBrains.Core; using JetBrains.Diagnostics; using JetBrains.Diagnostics.Internal; using JetBrains.Lifetimes; using NUnit.Framework; using System.Runtime.InteropServices; using System.Text; using System.Diagnostics; using JetBrains.Threading; using Microsoft.Diagnostics.Runtime; // ReSharper disable MethodSupportsCancellation namespace Test.Lifetimes.Lifetimes { public class LifetimeTest : LifetimesTestBase { // ReSharper disable once InconsistentNaming LifetimeDefinition def; // ReSharper disable once InconsistentNaming private Lifetime lt => def.Lifetime; [SetUp] public void BeforeTest() { def = new LifetimeDefinition(); def.Id = TestContext.CurrentContext.Test.Name; } class FailureException : Exception {} private void Fail() { throw new FailureException(); } private T Fail<T>() { throw new FailureException(); } [Test] public void TestEmptyLifetime() { def.Terminate(); def.Terminate(); } [Test] public void TestActionsSequence() { var log = new List<int>(); def.Lifetime.OnTermination(() => log.Add(1)); def.Lifetime.OnTermination(() => log.Add(2)); def.Lifetime.OnTermination(() => log.Add(3)); def.Terminate(); Assert.AreEqual(new []{3,2,1}, log.ToArray()); } [Test] public void TestNestedLifetime() { var log = new List<int>(); def.Lifetime.OnTermination(() => log.Add(1)); new LifetimeDefinition(def.Lifetime).Lifetime.OnTermination(() => log.Add(2)); def.Lifetime.OnTermination(() => log.Add(3)); def.Terminate(); Assert.AreEqual(new []{3,2,1}, log.ToArray()); } [Test] public void TestUsing() { Lifetime lf; Lifetime.Using(l => { lf = l; Assert.True(lf.IsAlive); Assert.False(lf.IsEternal); }); } [Test] public void TestTerminationWithAsyncTimeout() { var lin = new Linearization(); lin.Enable(); var log = new List<int>(); var task = Task.Run(() => { var first = lt.TryExecute(() => { log.Add(0); Assert.AreEqual(LifetimeStatus.Alive, lt.Status); Assert.True(lt.IsAlive); lin.Point(0); log.Add(1); SpinWaitEx.SpinUntil(() => def.Status == LifetimeStatus.Canceling); Assert.False(lt.IsAlive); }); //shouldn't execute var second = lt.TryExecute(() => { log.Add(2); }); Assert.True(first.Succeed); Assert.False(second.Succeed); }); def.Lifetime.OnTermination(() => {log.Add(-1);}); lin.Point(1); def.Terminate(); lin.Point(2); task.Wait(); Assert.AreEqual(new []{0, 1, -1}, log.ToArray()); } [Test] public void TestEternal() { Assert.True(Lifetime.Eternal.IsEternal); //doesn't fail Lifetime.Eternal.OnTermination(() => { }); } [Test] public void TestEquals() { var oldValue = Lifetime.LogErrorIfLifetimeIsNotInitialized; try { Lifetime.LogErrorIfLifetimeIsNotInitialized = false; DoChecks(false); Lifetime.LogErrorIfLifetimeIsNotInitialized = true; DoChecks(true); } finally { Lifetime.LogErrorIfLifetimeIsNotInitialized = oldValue; } [SuppressMessage("ReSharper", "EqualExpressionComparison")] #pragma warning disable 1718 void DoChecks(bool newDefaultBehaviorFlag) { Assert.AreEqual(Lifetime.LogErrorIfLifetimeIsNotInitialized, newDefaultBehaviorFlag); Lifetime defaultLifetime = default; // Checks that are always correct: Assert.AreEqual(defaultLifetime, defaultLifetime); Assert.AreEqual(Lifetime.Eternal, Lifetime.Eternal); Assert.AreEqual(Lifetime.Terminated, Lifetime.Terminated); Assert.AreNotEqual(Lifetime.Eternal, Lifetime.Terminated); Assert.AreNotEqual(defaultLifetime, Lifetime.Terminated); Assert.True(defaultLifetime == defaultLifetime); Assert.True(Lifetime.Eternal == Lifetime.Eternal); Assert.True(Lifetime.Terminated == Lifetime.Terminated); Assert.False(Lifetime.Eternal == Lifetime.Terminated); Assert.False(defaultLifetime == Lifetime.Terminated); Assert.False(defaultLifetime != defaultLifetime); Assert.False(Lifetime.Eternal != Lifetime.Eternal); Assert.False(Lifetime.Terminated != Lifetime.Terminated); Assert.True(Lifetime.Eternal != Lifetime.Terminated); Assert.True(defaultLifetime != Lifetime.Terminated); // Checks depending on the state of the flag: if (newDefaultBehaviorFlag) { Assert.AreNotEqual(defaultLifetime, Lifetime.Eternal); Assert.False(defaultLifetime == Lifetime.Eternal); Assert.True(defaultLifetime != Lifetime.Eternal); } else { Assert.AreEqual(defaultLifetime, Lifetime.Eternal); Assert.True(defaultLifetime == Lifetime.Eternal); Assert.False(defaultLifetime != Lifetime.Eternal); } } #pragma warning restore 1718 } [Test] public void TestTerminated() { Assert.True(Lifetime.Terminated.Status == LifetimeStatus.Terminated); } [Test] public void TestGetHashCode() { _ = Lifetime.Eternal.GetHashCode(); _ = Lifetime.Terminated.GetHashCode(); _ = ((Lifetime)default).GetHashCode(); } [Test] public void TestRecursiveTermination() { Assert.Throws<InvalidOperationException>(() => lt.TryExecute(() => { def.Terminate(); }).Unwrap()); Assert.AreEqual(LifetimeStatus.Alive, lt.Status); def.AllowTerminationUnderExecution = true; lt.TryExecute(() => { def.Terminate(); }); Assert.AreEqual(LifetimeStatus.Terminated, lt.Status); } [Test] public void TestTryExecuteAction() { Assert.True(lt.TryExecute(() => { }).Succeed); Assert.True(lt.TryExecute(() => { }, true).Succeed); Assert.Throws<FailureException>(() => lt.TryExecute(Fail)); Assert.True(lt.TryExecute(Fail, true).Exception is FailureException); Assert.True(lt.TryExecute(Fail, true).FailedNotCanceled); def.Terminate(); Assert.True(lt.TryExecute(Fail).Canceled); Assert.True(lt.TryExecute(Fail).Exception is LifetimeCanceledException); Assert.True(lt.TryExecute(Fail, true).Canceled); Assert.True(lt.TryExecute(Fail, true).Exception is LifetimeCanceledException); } [Test] public void TestTryExecuteFunc() { Assert.True(lt.TryExecute(() => 1).Succeed); Assert.True(lt.TryExecute(() => 1, true).Succeed); Assert.Throws<FailureException>(() => lt.TryExecute(Fail<int>)); Assert.True(lt.TryExecute(Fail<int>, true).Exception is FailureException); Assert.True(lt.TryExecute(Fail<int>, true).FailedNotCanceled); def.Terminate(); Assert.True(lt.TryExecute(Fail<int>).Canceled); Assert.True(lt.TryExecute(Fail<int>).Exception is LifetimeCanceledException); Assert.True(lt.TryExecute(Fail<int>, true).Canceled); Assert.True(lt.TryExecute(Fail<int>, true).Exception is LifetimeCanceledException); } [Test] public void TestLongTryExecute() { const string expectedWarningText = "can't wait for `ExecuteIfAlive` completed on other thread"; const string expectedExceptionText = "ExecuteIfAlive after termination of"; var warningReceived = false; Exception? receivedException = null; const string stackTraceHeader = "CurrentProcessThreadDumps:"; var executionWasNotCancelledByTimeoutReceived = false; Lifetime.Using(lifetime => { void LoggerHandler(LeveledMessage message) { if (message.Level == LoggingLevel.WARN && message.FormattedMessage.Contains(expectedWarningText)) warningReceived = true; } lifetime.Bracket( () => TestLogger.ExceptionLogger.Handlers += LoggerHandler, () => TestLogger.ExceptionLogger.Handlers -= LoggerHandler ); var lifetimeDefinition = lifetime.CreateNested(); var def2 = lifetime.CreateNested(); LifetimeDefinition.AdditionalDiagnostics = new LifetimeDefinition.AdditionalDiagnosticsInfo(false, async (lf) => { var stacks = GetCurrentProcessThreadDumps(); Assert.AreEqual(lifetimeDefinition.Lifetime, lf); executionWasNotCancelledByTimeoutReceived = true; return $"{stackTraceHeader}\n{stacks}"; }); def2.Terminate(); Assert.IsFalse(executionWasNotCancelledByTimeoutReceived); var lifetimeTerminatedEvent = new ManualResetEvent(false); var backgroundThreadIsInTryExecuteEvent = new ManualResetEvent(false); var thread = new Thread(() => lifetimeDefinition.Lifetime.TryExecute(() => { backgroundThreadIsInTryExecuteEvent.Set(); WaitForLifetimeTerminatedEvent(lifetimeTerminatedEvent); })); thread.Start(); backgroundThreadIsInTryExecuteEvent.WaitOne(); lifetimeDefinition.Terminate(); lifetimeTerminatedEvent.Set(); thread.Join(); try { TestLogger.ExceptionLogger.ThrowLoggedExceptions(); } catch (Exception e) { if (!e.Message.Contains(expectedExceptionText)) throw; receivedException = e; } }); Assert.IsTrue(warningReceived, "Warning `{0}` must have been logged", expectedWarningText); Assert.IsNotNull(receivedException, "Exception `{0}` must have been logged", expectedExceptionText); Assert.IsTrue(executionWasNotCancelledByTimeoutReceived); Assert.IsTrue(receivedException.Message.Contains(stackTraceHeader), $"Exception `{expectedExceptionText}` doesn't contain {stackTraceHeader}"); Assert.IsTrue(receivedException.Message.Contains(nameof(WaitForLifetimeTerminatedEvent)), $"Exception `{expectedExceptionText}` doesn't contain {nameof(WaitForLifetimeTerminatedEvent)} method"); static string GetCurrentProcessThreadDumps() { if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) { // clrmd crashes the process if os is not Windows, so just return the name of the method return nameof(WaitForLifetimeTerminatedEvent); } using var dataTarget = DataTarget.AttachToProcess(Process.GetCurrentProcess().Id, suspend: false); var clrVersion = dataTarget.ClrVersions.SingleOrDefault() ?? throw new Exception("Failed to get single clr from current process"); using var runtime = clrVersion.CreateRuntime(); var output = new StringBuilder(); foreach (var clrThread in runtime.Threads) { if (!clrThread.IsAlive) continue; output.AppendLine($"Thread #{clrThread.ManagedThreadId}:"); foreach (var frame in clrThread.EnumerateStackTrace()) output.AppendLine($"\tat {frame}"); } return output.ToString(); } } [MethodImpl(MethodImplOptions.NoInlining | MethodImplOptions.NoOptimization)] private static void WaitForLifetimeTerminatedEvent(ManualResetEvent lifetimeTerminatedEvent) { lifetimeTerminatedEvent.WaitOne(); } [Test] public void TestBracketGood() { var log = 0; void Inner(Action action) { log = 0; def = new LifetimeDefinition(); action(); Assert.AreEqual(1, log); def.Terminate(); Assert.AreEqual(11, log); } //Action, Action Inner( () => lt.Bracket(() => log += 1, () => log += 10)); //Func<T>, Action Inner(() => Assert.AreEqual(1, lt.Bracket(() => { log += 1; return 1; }, () => { log += 10; } ))); //Func<T>, Action<T> Inner(() => Assert.AreEqual(10, lt.Bracket(() => { log += 1; return 10; }, x => { log += x; } ))); } [Test] public void TestBracketCanceled() { var log = 0; def.Terminate(); void Inner(Action action) { log = 0; Assert.Throws<LifetimeCanceledException>(() => action()); Assert.AreEqual(0, log); def.Terminate(); //once more Assert.AreEqual(0, log); } //Action, Action Inner( () => lt.Bracket(() => log += 1, () => log += 10)); //Func<T>, Action Inner(() => Assert.AreEqual(1, lt.Bracket(() => { log += 1; return 1; }, () => { log += 10; } ))); //Func<T>, Action<T> Inner(() => Assert.AreEqual(10, lt.Bracket(() => { log += 1; return 10; }, x => { log += x; } ))); } [Test] public void TestBracketBadOpening() { var log = 0; void Inner(Action action) { def = new LifetimeDefinition(); log = 0; Assert.Throws<FailureException>(() => action()); Assert.AreEqual(1, log); def.Terminate(); //once more Assert.AreEqual(1, log); } //Action, Action Inner(() => lt.Bracket(() => { log += 1; Fail(); }, () => log += 10)); //Func<T>, Action Inner(() => Assert.AreEqual(1, lt.Bracket(() => { log += 1; return Fail<int>(); }, () => { log += 10; } ))); //Func<T>, Action<T> Inner(() => Assert.AreEqual(10, lt.Bracket(() => { log += 1; return Fail<int>(); }, x => { log += x; } ))); } [Test] public void TestBracketTerminationInOpening() { var log = 0; void Inner(Action action) { def = new LifetimeDefinition(); def.AllowTerminationUnderExecution = true; log = 0; action(); Assert.AreEqual(11, log); def.Terminate(); //once more Assert.AreEqual(11, log); } //Action, Action Inner(() => lt.Bracket(() => { log += 1; def.Terminate(); }, () => log += 10)); //Func<T>, Action Inner(() => Assert.AreEqual(1, lt.Bracket(() => { log += 1; def.Terminate(); return 1; }, () => { log += 10; } ))); //Func<T>, Action<T> Inner(() => Assert.AreEqual(10, lt.Bracket(() => { log += 1; def.Terminate(); return 10; }, x => { log += x; } ))); } [Test] public void TestTryBracketGood() { var log = 0; void Inner(Action action) { log = 0; def = new LifetimeDefinition(); action(); Assert.AreEqual(1, log); def.Terminate(); Assert.AreEqual(11, log); } //Action + Action Inner(() => Assert.True(Result.Unit == lt.TryBracket(() => { log += 1; }, () => { log += 10; } ))); Inner(() => Assert.True(Result.Unit == lt.TryBracket(() => { log += 1; }, () => { log += 10; } , true))); //Func<T> + Action Inner(() => Assert.True(Result.Success(1) == lt.TryBracket(() => { log += 1; return 1; }, () => { log += 10; } ))); Inner(() => Assert.True(Result.Success(1) == lt.TryBracket(() => { log += 1; return 1; }, () => { log += 10; } , true))); //Func<T> + Action<T> Inner(() => Assert.True(Result.Success(10) == lt.TryBracket(() => { log += 1; return 10; }, x => { log += x; } ))); Inner(() => Assert.True(Result.Success(10) == lt.TryBracket(() => { log += 1; return 10; }, x => { log += x; } , true))); } [Test] public void TestTryBracketCanceled() { var log = 0; void Inner(Action action) { log = 0; def.Terminate(); action(); Assert.AreEqual(0, log); def.Terminate(); Assert.AreEqual(0, log); } //Action + Action Inner(() => { var res = lt.TryBracket(() => { log += 1; }, () => { log += 10; } ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; }, () => { log += 10; }, true ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); //Func<T> + Action Inner(() => { var res = lt.TryBracket(() => { log += 1; return 1; }, () => { log += 10; } ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; return 1;}, () => { log += 10; }, true ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); //Func<T> + Action<T> Inner(() => { var res = lt.TryBracket(() => { log += 1; return 1; }, x => { log += x; } ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; return 1;}, x => { log += x; }, true ); Assert.True(res.Canceled); Assert.True(res.Exception is LifetimeCanceledException lce && lce.Lifetime == lt); }); } [Test] public void TestTryBracketBadOpeningWrap() { var log = 0; void Inner(Action action) { log = 0; def = new LifetimeDefinition(); action(); Assert.AreEqual(1, log); def.Terminate(); Assert.AreEqual(1, log); } //Action + Action Inner(() => { Assert.Throws<FailureException>( () => lt.TryBracket(() => { log += 1; Fail(); }, () => { log += 10; } )); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; Fail(); }, () => { log += 10; }, true ); Assert.True(res.FailedNotCanceled); Assert.True(res.Exception is FailureException); }); //Func<T> + Action Inner(() => { Assert.Throws<FailureException>( () => lt.TryBracket(() => { log += 1; return Fail<int>(); }, () => { log += 10; } )); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; return Fail<int>(); }, () => { log += 10; }, true ); Assert.True(res.FailedNotCanceled); Assert.True(res.Exception is FailureException); }); //Func<T> + Action<T> Inner(() => { Assert.Throws<FailureException>(() => lt.TryBracket(() => { log += 1; return Fail<int>(); }, x => { log += x; } )); }); Inner(() => { var res = lt.TryBracket(() => { log += 1; return Fail<int>(); }, x => { log += x; }, true ); Assert.True(res.FailedNotCanceled); Assert.True(res.Exception is FailureException); }); } [Test] public void TestTryBracketTerminationInOpening() { var log = 0; void InnerSuccess<T>(Func<bool, Result<T>> action) { log = 0; def = new LifetimeDefinition {AllowTerminationUnderExecution = true}; Assert.True(action(false).Succeed); Assert.AreEqual(11, log); log = 0; def = new LifetimeDefinition{AllowTerminationUnderExecution = true}; Assert.True(action(true).Succeed); Assert.AreEqual(11, log); Assert.AreEqual(11, log); } void InnerFail<T>(Func<bool, Result<T>> action) { log = 0; def = new LifetimeDefinition{AllowTerminationUnderExecution = true}; Assert.Throws<FailureException>(() => action(false)); Assert.AreEqual(11, log); log = 0; def = new LifetimeDefinition{AllowTerminationUnderExecution = true}; Assert.True(action(true).Exception is FailureException); Assert.AreEqual(11, log); } //Action + Action InnerSuccess(wrap => lt.TryBracket(() => { log += 1; def.Terminate(); }, () => { log += 10; }, wrap )); InnerFail(wrap => lt.TryBracket(() => { log += 1; def.Terminate(); }, () => { log += 10; Fail(); }, wrap )); //Func<T> + Action InnerSuccess(wrap => lt.TryBracket(() => { log += 1; def.Terminate(); return 1; }, () => { log += 10; }, wrap )); // Func<T> + Action<T> InnerFail(wrap => lt.TryBracket(() => { log += 1; def.Terminate(); return 10; }, x => { log += x; Fail();}, wrap )); } [Test] public void TestAddTerminationActionToTerminatedLifetime() { int executed = 0; def.Terminate(); //actions Assert.False(lt.TryOnTermination(() => { executed++; })); Assert.AreEqual(0, executed); //no change Assert.Throws<InvalidOperationException>(() => lt.OnTermination(() => { executed++; })); Assert.AreEqual(1, executed); //dispose Assert.False(lt.TryOnTermination(() => { executed++; })); Assert.AreEqual(1, executed); //no change Assert.Throws<InvalidOperationException>(() => lt.OnTermination(() => { executed++; })); Assert.AreEqual(2, executed); } [Test] public void TestTaskAttachment() { int executed = 0; lt.ExecuteAsync(async () => { await Task.Yield(); executed += 1; }); lt.OnTermination(() => executed *= 2); def.Terminate(); //will wait for task Assert.AreEqual(2, executed); } [Test] public void TestTaskWithTerminatedLifetime() { var task = Lifetime.Terminated.TryExecuteAsync(async () => { await Task.Yield(); return 0; }); Assert.True(task.IsCanceled); } [Test] public void TestFailedTask() { var task = lt.ExecuteAsync(async () => { await Task.Yield(); throw new Exception(); }); Assert.Throws<AggregateException>(task.Wait); } [Test] public void TestAllInnerLifetimesTerminatedExceptLast() { var o = lt.CreateNested(); for (int i = 0; i < 100; i++) { var n = lt.CreateNested(); o.Terminate(); o = n; } var resCount = def.GetDynamicField("myResCount"); Assert.AreEqual(2, resCount); //one is dead var resourcesCapacity = def.GetDynamicField("myResources").GetDynamicProperty("Length"); Assert.AreEqual(2, resourcesCapacity); //one is dead } // [Test] // public void TestScopeLifetime() // { // Lifetime lf; // using (var scoped = new ScopedLifetime()) // { // lf = scoped; // lf.AssertIsAlive(); // } // Assert.False(lf.IsAlive); // } // // [Test] // public void TestScopedLifetimeWithAliveParent() // { // Lifetime lf; // using (var scoped = new ScopedLifetime(lt)) // { // lf = scoped; // lf.AssertIsAlive(); // } // Assert.True(lt.IsAlive); // Assert.False(lf.IsAlive); // } // // [Test] // public void TestScopedLifetimeWithTerminatingParent() // { // Lifetime lf; // using (var scoped = new ScopedLifetime(lt)) // { // lf = scoped; // lf.AssertIsAlive(); // def.Terminate(); // Assert.False(lf.IsAlive); // } // Assert.False(lt.IsAlive); // Assert.False(lf.IsAlive); // } // // [Test] // public void TestScopedLifetimeWithTerminatedParent() // { // Lifetime lf; // using (var scoped = new ScopedLifetime(lt)) // { // lf = scoped.Instance; // lf.AssertIsAlive(); // def.Terminate(); // Assert.False(lf.IsAlive); // } // Assert.False(lt.IsAlive); // Assert.False(lf.IsAlive); // } [Test] public void TestCancellationToken1() { def.Terminate(); var task = Task.Run(() => {}, lt); Log.Root.CatchAndDrop(task.Wait); Assert.AreEqual(TaskStatus.Canceled, task.Status); } [Test] public void TestCancellationToken2() { var evt = new ManualResetEvent(false); var task = Task.Run(() => { evt.Set();}, lt); evt.WaitOne(); def.Terminate(); Log.Root.CatchAndDrop(task.Wait); Assert.AreEqual(TaskStatus.RanToCompletion, task.Status); } [Test] public void TestCancellationToken3() { var task = Task.Run(() => { def.Terminate(); lt.OnTermination(() => { }); }, lt); Log.Root.CatchAndDrop(task.Wait); Assert.AreEqual(TaskStatus.Faulted, task.Status); } [Test] public void TestCancellationToken4() { var task = Task.Run(() => { def.Terminate(); def.ThrowIfNotAlive(); }, lt); Log.Root.CatchAndDrop(task.Wait); Assert.AreEqual(TaskStatus.Faulted, task.Status); } [Test, Ignore("Fails on build server")] public void TestTooLongExecuting() { var oldTimeout = LifetimeDefinition.WaitForExecutingInTerminationTimeoutMs; LifetimeDefinition.WaitForExecutingInTerminationTimeoutMs = 100; try { AutoResetEvent sync = new AutoResetEvent(false); var task = lt.StartAttached(TaskScheduler.Default, () => sync.WaitOne()); def.Terminate(); var ex = Assert.Catch(ThrowLoggedExceptions, $"First exception from {nameof(LifetimeDefinition)}.Terminate"); //first from terminate Assert.True(ex.Message.Contains("ExecuteIfAlive")); sync.Set(); task.Wait(); ex = Assert.Catch(ThrowLoggedExceptions, $"Second exception from {nameof(LifetimeDefinition.ExecuteIfAliveCookie)}.Dispose"); //second from terminate Assert.True(ex.Message.Contains("ExecuteIfAlive")); } finally { LifetimeDefinition.WaitForExecutingInTerminationTimeoutMs = oldTimeout; } } [Test] public void TestTerminationTimeout() { var defA = new LifetimeDefinition(lt) { TerminationTimeoutKind = LifetimeTerminationTimeoutKind.Long }; var defB = new LifetimeDefinition(defA.Lifetime); var defC = new LifetimeDefinition(defA.Lifetime) { TerminationTimeoutKind = LifetimeTerminationTimeoutKind.Short }; Assert.AreEqual(LifetimeTerminationTimeoutKind.Long, defB.TerminationTimeoutKind); Assert.AreEqual(LifetimeTerminationTimeoutKind.Long, defA.TerminationTimeoutKind); Assert.AreEqual(LifetimeTerminationTimeoutKind.Short, defC.TerminationTimeoutKind); } [TestCase(LifetimeTerminationTimeoutKind.Default)] [TestCase(LifetimeTerminationTimeoutKind.Short)] [TestCase(LifetimeTerminationTimeoutKind.Long)] [TestCase(LifetimeTerminationTimeoutKind.ExtraLong)] public void TestSetTestTerminationTimeout(LifetimeTerminationTimeoutKind timeoutKind) { var oldTimeoutMs = LifetimeDefinition.GetTerminationTimeoutMs(timeoutKind); try { LifetimeDefinition.SetTerminationTimeoutMs(timeoutKind, 2000); var subDef = new LifetimeDefinition(lt) { TerminationTimeoutKind = timeoutKind }; var subLt = subDef.Lifetime; subLt.ExecuteAsync(() => Task.Delay(750)); subDef.Terminate(); Assert.DoesNotThrow(ThrowLoggedExceptions); } finally { LifetimeDefinition.SetTerminationTimeoutMs(timeoutKind, oldTimeoutMs); } } [Test] public void T000_Items() { int count = 0; Lifetime.Using(lifetime => { lifetime.OnTermination(() => count++); lifetime.AddDispose(Disposable.CreateAction(() => count++)); lifetime.OnTermination(() => count++); lifetime.OnTermination(Disposable.CreateAction(() => count++)); }); Assert.AreEqual(4, count, "Mismatch."); } [Test] public void T010_SimpleOrder() { var entries = new List<int>(); int x= 0 ; Lifetime.Using(lifetime => { int a = x++; lifetime.OnTermination(() => entries.Add(a)); int b = x++; lifetime.AddDispose(Disposable.CreateAction(() => entries.Add(b))); int c = x++; lifetime.OnTermination(Disposable.CreateAction(() => entries.Add(c))); int d = x++; lifetime.AddDispose(Disposable.CreateAction(() => entries.Add(d))); }); CollectionAssert.AreEqual(Enumerable.Range(0, entries.Count).Reverse().ToArray(), entries, "Order FAIL."); } [Test] public void T020_DefineNestedOrder() { var entries = new List<int>(); int x= 0 ; Func<Action> FMakeAdder = () => { var a = x++; return () => entries.Add(a); }; // Fixes the X value at the moment of FMakeAdder call. bool flag = false; Lifetime.Using(lifetime => { lifetime.OnTermination(FMakeAdder()); lifetime.AddDispose(Disposable.CreateAction(FMakeAdder())); Lifetime.Define(lifetime, atomicAction:(lifeNested) => { lifeNested.OnTermination(FMakeAdder()); lifeNested.OnTermination(FMakeAdder()); lifeNested.OnTermination(FMakeAdder());}); lifetime.AddDispose(Disposable.CreateAction(FMakeAdder())); Lifetime.Define(lifetime, atomicAction:(lifeNested) => { lifeNested.OnTermination(FMakeAdder()); lifeNested.OnTermination(FMakeAdder()); lifeNested.OnTermination(FMakeAdder());}); lifetime.AddDispose(Disposable.CreateAction(FMakeAdder())); Lifetime.Define(lifetime, atomicAction:(lifeNested) => lifeNested.OnTermination(() => flag = true)).Terminate(); Assert.IsTrue(flag, "Nested closing FAIL."); flag = false; lifetime.AddDispose(Disposable.CreateAction(FMakeAdder())); }); Assert.IsFalse(flag, "Nested closed twice."); CollectionAssert.AreEqual(Enumerable.Range(0, entries.Count).Reverse().ToArray(), entries, "Order FAIL."); } [Test] public void CancellationTokenTest() { var def = Lifetime.Define(); var sw = new SpinWait(); var task = Task.Run(() => { while (true) { def.Lifetime.ThrowIfNotAlive(); sw.SpinOnce(); } }, def.Lifetime); Thread.Sleep(100); def.Terminate(); try { task.Wait(); } catch (AggregateException e) { Assert.True(task.IsCanceled); Assert.True(e.IsOperationCanceled()); return; } Assert.Fail("Unreachable"); } [Test] public void CancellationTokenTestAlreadyCancelled() { var def = Lifetime.Define(); def.Terminate(); var task = Task.Run(() => { Assertion.Fail("Unreachable"); }, def.Lifetime); Assert.Throws<AggregateException>(() => task.Wait()); Assert.True(task.IsCanceled); } [Test] public void TestCancellationEternalLifetime() { var lt = Lifetime.Eternal; var task = Task.Run(() => { lt.ThrowIfNotAlive(); Thread.Yield(); }, lt); task.Wait(); Assert.True(task.Status == TaskStatus.RanToCompletion); } [Test] public void TestCreateTaskCompletionSource() { Assert.True(Lifetime.Terminated.CreateTaskCompletionSource<Unit>().Task.IsCanceled); var t = lt.CreateTaskCompletionSource<Unit>().Task; Assert.False(t.IsCompleted); def.Terminate(); Assert.True(t.IsCanceled); } [Test] public void TestSynchronizeTaskCompletionSource() { //lifetime terminated var tcs = new TaskCompletionSource<Unit>(); Lifetime.Terminated.CreateNested().SynchronizeWith(tcs); Assert.True(tcs.Task.IsCanceled); //tcs completed tcs = new TaskCompletionSource<Unit>(); tcs.SetResult(Unit.Instance); Lifetime.Terminated.CreateNested().SynchronizeWith(tcs); //nothing Lifetime.Eternal.CreateNested().SynchronizeWith(tcs); //nothing def.SynchronizeWith(tcs); Assert.True(lt.Status == LifetimeStatus.Terminated); //lifetime terminates first tcs = new TaskCompletionSource<Unit>(); var d = new LifetimeDefinition(); d.SynchronizeWith(tcs); Assert.True(d.Lifetime.IsAlive); Assert.False(tcs.Task.IsCompleted); d.Terminate(); Assert.True(tcs.Task.IsCanceled); //tcs terminates first tcs = new TaskCompletionSource<Unit>(); d = new LifetimeDefinition(); d.SynchronizeWith(tcs); tcs.SetCanceled(); Assert.True(d.Lifetime.Status == LifetimeStatus.Terminated); } [Test] public void TestTerminatesAfter() { var lf = TestLifetime.CreateTerminatedAfter(TimeSpan.FromMilliseconds(100)); Assert.True(lf.IsAlive); Thread.Sleep(200); Assert.True(lf.IsNotAlive); lf = TestLifetime.CreateTerminatedAfter(TimeSpan.FromMilliseconds(100)); Assert.True(lf.IsAlive); LifetimeDefinition.Terminate(); Assert.True(lf.IsNotAlive); Thread.Sleep(200); Assert.True(lf.IsNotAlive); } [Test] public void CancellationTokenStressTest() { var cancel = new CancellationTokenSource(1000).Token; var def = new LifetimeDefinition(); Task.WaitAll( Task.Run(() => { while (!cancel.IsCancellationRequested) { def.Terminate(); Thread.Yield(); def = new LifetimeDefinition(); } }), Task.Run(() => { while (!cancel.IsCancellationRequested) def.ToCancellationToken(); })); } [Test] public void CancellationTokenActualCancellationStressTest() { var cancel = new CancellationTokenSource(1000).Token; var sum = 0; var def = Lifetime.Define(); Task.WaitAll( Task.Run(() => { while (!cancel.IsCancellationRequested) { def.Terminate(); Thread.Yield(); def = new LifetimeDefinition(); } def.Terminate(); }), Task.Run(CheckerProc), Task.Run(CheckerProc), Task.Run(CheckerProc), Task.Run(CheckerProc), Task.Run(CheckerProc), Task.Run(CheckerProc) ); void CheckerProc() { var cache = def; var localSum = 0; while (!cancel.IsCancellationRequested) { if (def != cache) { cache = def; Interlocked.Increment(ref localSum); cache.ToCancellationToken().Register(() => Interlocked.Decrement(ref localSum)); } } SpinWait.SpinUntil(() => def.Lifetime.Status == LifetimeStatus.Terminated); Interlocked.Add(ref sum, localSum); } Assert.AreEqual(0, sum); } [Test] public void CancellationTokenMultiThreadTerminationTest() { const int n = 6; const int magicNumber = (n + 1) * 1000; for (int i = 0; i < 1000; i++) { var count = 0; var sum = 0; var def = TestLifetime.CreateNested(); var creatorTask = Task.Run(Creator); Task.WaitAll(Enumerable.Range(0, n).Select(_ => Task.Run(Terminator)).Concat(new []{creatorTask}).ToArray()); def.Terminate(); Assert.AreEqual(0, sum); void Creator() { while (Volatile.Read(ref count) <= magicNumber) { var newDef = new LifetimeDefinition(); Interlocked.Increment(ref sum); newDef.ToCancellationToken().Register(() => Interlocked.Decrement(ref sum)); Interlocked.Exchange(ref def, newDef).Terminate(); Interlocked.Increment(ref count); } } void Terminator() { while (Volatile.Read(ref count) <= magicNumber) { Volatile.Read(ref def).Terminate(); Interlocked.Increment(ref count); } } } } [Test] public void SimpleOnTerminationStressTest() { for (int i = 0; i < 100; i++) { using var lifetimeDefinition = new LifetimeDefinition(); var lifetime = lifetimeDefinition.Lifetime; int count = 0; const int threadsCount = 10; const int iterations = 1000; Task.Factory.StartNew(() => { for (int j = 0; j < threadsCount; j++) { Task.Factory.StartNew(() => { for (int k = 0; k < iterations; k++) lifetime.OnTermination(() => count++); }, TaskCreationOptions.AttachedToParent | TaskCreationOptions.LongRunning); } }).Wait(); lifetimeDefinition.Terminate(); Assert.AreEqual(threadsCount * iterations, count); } } [Test] public void IntersectionsAndInheritTimeoutKindTest() { var lf1 = new LifetimeDefinition(); var lf2 = new LifetimeDefinition(); var lf3 = new LifetimeDefinition(); DoTest1(LifetimeTerminationTimeoutKind.Default); DoTest2(LifetimeTerminationTimeoutKind.Default); lf1.TerminationTimeoutKind = LifetimeTerminationTimeoutKind.ExtraLong; DoTest1(LifetimeTerminationTimeoutKind.Default); DoTest2(LifetimeTerminationTimeoutKind.Default); lf2.TerminationTimeoutKind = LifetimeTerminationTimeoutKind.Long; DoTest1(LifetimeTerminationTimeoutKind.Long); DoTest2(LifetimeTerminationTimeoutKind.Default); lf3.TerminationTimeoutKind = LifetimeTerminationTimeoutKind.Short; DoTest1(LifetimeTerminationTimeoutKind.Long); DoTest2(LifetimeTerminationTimeoutKind.Short); void DoTest1(LifetimeTerminationTimeoutKind expected) { var definedLifetime = Lifetime.DefineIntersection(lf1.Lifetime, lf2.Lifetime); var outerDefinedLifetime = OuterLifetime.DefineIntersection(lf1.Lifetime, lf2.Lifetime); Assert.AreEqual(expected, definedLifetime.TerminationTimeoutKind); Assert.AreEqual(expected, outerDefinedLifetime.TerminationTimeoutKind); } void DoTest2(LifetimeTerminationTimeoutKind expected) { var definedLifetime = Lifetime.DefineIntersection(lf1.Lifetime, lf2.Lifetime, lf3.Lifetime); var outerDefinedLifetime = OuterLifetime.DefineIntersection(lf1.Lifetime, lf2.Lifetime, lf3.Lifetime); Assert.AreEqual(expected, definedLifetime.TerminationTimeoutKind); Assert.AreEqual(expected, outerDefinedLifetime.TerminationTimeoutKind); } } [Test] public void DefineLifetimeInheritTimeoutKindTest() { var definition = new LifetimeDefinition { TerminationTimeoutKind = LifetimeTerminationTimeoutKind.ExtraLong }; Assert.AreEqual(LifetimeTerminationTimeoutKind.ExtraLong, Lifetime.Define(definition.Lifetime, "id", (LifetimeDefinition ld) => {}).TerminationTimeoutKind); Assert.AreEqual(LifetimeTerminationTimeoutKind.ExtraLong, Lifetime.Define(definition.Lifetime, "id", (Lifetime ld) => {}).TerminationTimeoutKind); Assert.AreEqual(LifetimeTerminationTimeoutKind.ExtraLong, OuterLifetime.Define(definition.Lifetime, "id", (ld, lf) => {}).TerminationTimeoutKind); } [Test] public void EternalLifetimeKeepalive() { Assert.DoesNotThrow(() => { var o = new object(); Lifetime.Eternal.KeepAlive(o); }); var ex = Assert.Throws<Exception>(() => { TestLogger.ExceptionLogger.ThrowLoggedExceptions(); }); Assert.True(ex.Message.Contains("!IsEternal")); } } }