# HG changeset patch # User cin # Date 2016-10-18 14:49:54 # Node ID 4d9830a9bbb837a3301c2b2b4072bfaef1ae62f5 # Parent 2651cb9a4250573a4118d2fb6ec61155a86d164b Added 'Fail' method to RunnableComponent which allows component to move from Running to Failed state. Added PollingComponent a timer based runnable component More tests Added FailPromise a thin class to wrap exceptions Fixed error handling in SuccessPromise classes. diff --git a/Implab.Test/Implab.Test.mono.csproj b/Implab.Test/Implab.Test.mono.csproj --- a/Implab.Test/Implab.Test.mono.csproj +++ b/Implab.Test/Implab.Test.mono.csproj @@ -59,6 +59,9 @@ + + + @@ -66,4 +69,7 @@ Implab + + + \ No newline at end of file diff --git a/Implab.Test/Mock/MockPollingComponent.cs b/Implab.Test/Mock/MockPollingComponent.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/Mock/MockPollingComponent.cs @@ -0,0 +1,71 @@ +using System; +using Implab.Components; + +namespace Implab.Test.Mock { + class MockPollingComponent : PollingComponent { + public MockPollingComponent(TimeSpan interval, Func, IPromise> dispatcher, bool initialized) : base(interval, dispatcher, initialized) { + } + + public Action MockInit { + get; + set; + } + + public Action MockOnError { + get; + set; + } + + public Action MockOnCancel { + get; + set; + } + + public Func MockStart { + get; + set; + } + + public Func MockStop { + get; + set; + } + + public Func MockTick { + get; + set; + } + + protected override IPromise OnStart() { + return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); + } + + protected override IPromise OnStop() { + return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop); + } + + protected override void OnInitialize() { + if (MockInit != null) + MockInit(); + } + + protected override IPromise OnTick(ICancellationToken cancellationToken) { + return MockTick != null ? Safe.Run(() => MockTick(cancellationToken)) : Promise.SUCCESS; + } + + protected override void OnTickCancel(Exception error) { + if (MockOnCancel != null) + MockOnCancel(error); + } + + protected override void OnTickError(Exception error) { + if (MockOnError != null) + MockOnError(error); + } + + public void CallComponentFail(Exception error) { + Fail(error); + } + } +} + diff --git a/Implab.Test/Mock/MockRunnableComponent.cs b/Implab.Test/Mock/MockRunnableComponent.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/Mock/MockRunnableComponent.cs @@ -0,0 +1,38 @@ +using System; +using Implab.Components; + +namespace Implab.Test.Mock { + class MockRunnableComponent : RunnableComponent { + public MockRunnableComponent(bool initialized) : base(initialized) { + } + + public Action MockInit { + get; + set; + } + + public Func MockStart { + get; + set; + } + + public Func MockStop { + get; + set; + } + + protected override IPromise OnStart() { + return MockStart != null ? Safe.Run(MockStart).Chain(base.OnStart) : Safe.Run(base.OnStart); + } + + protected override IPromise OnStop() { + return MockStop != null ? Safe.Run(MockStop).Chain(base.OnStop) : Safe.Run(base.OnStop); + } + + protected override void OnInitialize() { + if (MockInit != null) + MockInit(); + } + } +} + diff --git a/Implab.Test/PollingComponentTests.cs b/Implab.Test/PollingComponentTests.cs new file mode 100644 --- /dev/null +++ b/Implab.Test/PollingComponentTests.cs @@ -0,0 +1,230 @@ +using System; +using System.Reflection; +using System.Threading; +using Implab.Parallels; +using Implab.Components; +using Implab.Test.Mock; + +#if MONO + +using NUnit.Framework; +using TestClassAttribute = NUnit.Framework.TestFixtureAttribute; +using TestMethodAttribute = NUnit.Framework.TestAttribute; +using AssertFailedException = NUnit.Framework.AssertionException; +#else + +using Microsoft.VisualStudio.TestTools.UnitTesting; + +#endif + +namespace Implab.Test { + [TestClass] + public class PollingComponentTests { + static void ShouldThrow(Action action) { + try { + action(); + Assert.Fail(); + } catch (AssertFailedException) { + throw; + } catch { + } + } + + [TestMethod] + public void NormalFlowTest() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, false); + + Assert.AreEqual(ExecutionState.Created, comp.State); + + comp.Init(); + + Assert.AreEqual(ExecutionState.Ready, comp.State); + + comp.Start().Join(1000); + + Assert.AreEqual(ExecutionState.Running, comp.State); + + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + + } + + [TestMethod] + public void ShouldStartTicks() { + var signal = new Signal(); + + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + comp.MockTick = ct => { + signal.Set(); + return Promise.SUCCESS; + }; + + comp.Start().Join(1000); + signal.Wait(1000); + comp.Stop().Join(1000); + } + + [TestMethod] + public void StopShouldWaitForTickToComplete() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var signal = new Signal(); + var promise = new Promise(); + + // timer should tick once + comp.MockTick = ct => { + signal.Set(); + return promise; + }; + + // start timer + comp.Start().Join(1000); + + signal.Wait(); // wait for tick + + // try to stop component + var stopping = comp.Stop(); + + Assert.AreEqual(ExecutionState.Stopping, comp.State); + ShouldThrow(() => stopping.Join(100)); + Assert.AreEqual(ExecutionState.Stopping, comp.State); + + // complete operation + promise.Resolve(); + + // the component should stop normally + stopping.Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void ShouldRecoverAfterTickError() { + var ticks = 0; + + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var signal = new Signal(); // will signal when timer fires 10 times + + comp.MockTick = ct => { + ticks++; + if (ticks == 10) + signal.Set(); + // each time handler dies + throw new Exception("tainted handler"); + }; + + comp.Start(); + + signal.Wait(1000); + + comp.Stop().Join(1000); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void StopCancelHandlerOnStop() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + bool cancelled = false; + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + + while(!ct.IsCancellationRequested) { + Thread.Sleep(1); + } + + cancelled = true; + + throw new OperationCanceledException(); + }; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + // try to stop component + comp.Stop().Join(1000); + + Assert.AreEqual(true, cancelled); + + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void FailTickOnStopShouldBeIgnored() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + var finish = new Signal(); + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + finish.Wait(); + // component is in stopping state here + throw new Exception("Die, die, die!!!"); + }; + + + comp.MockOnError = comp.CallComponentFail; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + // try to stop component + var stopping = comp.Stop(); + + // the component is in stopping state but it is waiting for the tick handler to complete + finish.Set(); // signal the tick handler to finish + + // tick handler should stop rather soon + stopping.Join(1000); + + // validate the component is disposed + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + [TestMethod] + public void FailTickShouldFailComponent() { + var comp = new MockPollingComponent(TimeSpan.FromMilliseconds(1), null, true); + var started = new Signal(); + var finish = new Signal(); + + // timer should tick once + comp.MockTick = ct => { + started.Set(); + throw new Exception("Die, die, die!!!"); + }; + + + comp.MockOnError = err => { + comp.CallComponentFail(err); + finish.Set(); + }; + + // start timer + comp.Start().Join(1000); + + started.Wait(); // wait for tick + + finish.Wait(); + + // try to stop component + ShouldThrow(() => comp.Stop()); + + Assert.AreEqual(ExecutionState.Failed, comp.State); + Assert.IsNotNull(comp.LastError); + Assert.AreEqual("Die, die, die!!!", comp.LastError.Message); + + comp.Dispose(); + Assert.AreEqual(ExecutionState.Disposed, comp.State); + } + + } +} + diff --git a/Implab.Test/PromiseHelper.cs b/Implab.Test/PromiseHelper.cs --- a/Implab.Test/PromiseHelper.cs +++ b/Implab.Test/PromiseHelper.cs @@ -10,5 +10,13 @@ namespace Implab.Test { return retVal; }); } + + public static IPromise Sleep(int timeout) { + return AsyncPool.Invoke((ct) => { + ct.CancellationRequested(ct.CancelOperation); + Thread.Sleep(timeout); + return 0; + }); + } } } diff --git a/Implab.Test/RunnableComponentTests.cs b/Implab.Test/RunnableComponentTests.cs --- a/Implab.Test/RunnableComponentTests.cs +++ b/Implab.Test/RunnableComponentTests.cs @@ -3,6 +3,7 @@ using System.Reflection; using System.Threading; using Implab.Parallels; using Implab.Components; +using Implab.Test.Mock; #if MONO @@ -30,42 +31,11 @@ namespace Implab.Test { } } - class Runnable : RunnableComponent { - public Runnable(bool initialized) : base(initialized) { - } - - public Action MockInit { - get; - set; - } - public Func MockStart { - get; - set; - } - - public Func MockStop { - get; - set; - } - - protected override IPromise OnStart() { - return MockStart != null ? MockStart() : base.OnStart(); - } - - protected override IPromise OnStop() { - return MockStop != null ? MockStop() : base.OnStart(); - } - - protected override void OnInitialize() { - if (MockInit != null) - MockInit(); - } - } [TestMethod] public void NormalFlowTest() { - var comp = new Runnable(false); + var comp = new MockRunnableComponent(false); Assert.AreEqual(ExecutionState.Created, comp.State); @@ -85,7 +55,7 @@ namespace Implab.Test { [TestMethod] public void InitFailTest() { - var comp = new Runnable(false) { + var comp = new MockRunnableComponent(false) { MockInit = () => { throw new Exception("BAD"); } @@ -110,7 +80,7 @@ namespace Implab.Test { [TestMethod] public void DisposedTest() { - var comp = new Runnable(false); + var comp = new MockRunnableComponent(false); comp.Dispose(); ShouldThrow(() => comp.Start()); @@ -122,7 +92,7 @@ namespace Implab.Test { [TestMethod] public void StartCancelTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0) }; @@ -140,7 +110,7 @@ namespace Implab.Test { [TestMethod] public void StartStopTest() { var stop = new Signal(); - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0), MockStop = () => AsyncPool.RunThread(stop.Wait) }; @@ -160,7 +130,7 @@ namespace Implab.Test { [TestMethod] public void StartStopFailTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStart = () => PromiseHelper.Sleep(100000, 0).Then(null,null,x => { throw new Exception("I'm dead"); }) }; @@ -175,7 +145,7 @@ namespace Implab.Test { [TestMethod] public void StopCancelTest() { - var comp = new Runnable(true) { + var comp = new MockRunnableComponent(true) { MockStop = () => PromiseHelper.Sleep(100000, 0) }; diff --git a/Implab/Components/IRunnable.cs b/Implab/Components/IRunnable.cs --- a/Implab/Components/IRunnable.cs +++ b/Implab/Components/IRunnable.cs @@ -2,8 +2,14 @@ namespace Implab.Components { public interface IRunnable { + /// + /// Starts this instance. + /// IPromise Start(); + /// + /// Stops this instance. After the instance is stopped it can't be started again, stopping should be treated as gracefull and async dispose. + /// IPromise Stop(); ExecutionState State { get; } diff --git a/Implab/Components/PollingComponent.cs b/Implab/Components/PollingComponent.cs new file mode 100644 --- /dev/null +++ b/Implab/Components/PollingComponent.cs @@ -0,0 +1,155 @@ +using System; +using System.Threading; +using Implab.Diagnostics; + +namespace Implab.Components { + public class PollingComponent : RunnableComponent { + readonly Timer m_timer; + readonly Func, IPromise> m_dispatcher; + readonly TimeSpan m_interval; + + readonly object m_lock = new object(); + + ActionTask m_pending; + + protected PollingComponent(TimeSpan interval, Func, IPromise> dispatcher, bool initialized) : base(initialized) { + m_timer = new Timer(OnInternalTick); + + m_interval = interval; + m_dispatcher = dispatcher; + } + + protected override IPromise OnStart() { + m_timer.Change(TimeSpan.Zero, m_interval); + + return base.OnStart(); + } + + void OnInternalTick(object state) { + if (StartTick()) { + try { + if (m_dispatcher != null) { + var result = m_dispatcher(OnTick); + m_pending.CancellationRequested(result.Cancel); + AwaitTick(result); + } else { + AwaitTick(OnTick(m_pending)); + } + } catch (Exception error) { + HandleTickError(error); + } + } + } + + /// + /// Checks wheather there is no running handler in the component and marks that the handler is starting. + /// + /// boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running. + /// + /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers. + /// + protected virtual bool StartTick() { + lock (m_lock) { + if (State != ExecutionState.Running || m_pending != null) + return false; + // actually the component may be in a not running state here (stopping, disposed or what ever), + // but OnStop method also locks on the same object and will handle operation in m_pending + m_pending = new ActionTask( + () => { + // only one operation is running, it's safe to assing m_pending from it + m_pending = null; + }, + ex => { + try { + OnTickError(ex); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } finally { + m_pending = null; + } + // suppress error + }, + ex => { + try { + OnTickCancel(ex); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } finally { + m_pending = null; + } + // supress cancellation + }, + false + ); + return true; + } + } + + /// + /// Awaits the tick. + /// + /// Tick. + /// + /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. + /// + void AwaitTick(IPromise tick) { + if (tick == null) { + m_pending.Resolve(); + } else { + tick.On( + m_pending.Resolve, + m_pending.Reject, + m_pending.CancelOperation + ); + } + } + + /// + /// Handles the tick error. + /// + /// + /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled. + /// + void HandleTickError(Exception error) { + m_pending.Reject(error); + } + + protected virtual void OnTickError(Exception error) { + } + + protected virtual void OnTickCancel(Exception error) { + } + + /// + /// Invoked when the timer ticks, use this method to implement your logic + /// + protected virtual IPromise OnTick(ICancellationToken cancellationToken) { + return Promise.SUCCESS; + } + + protected override IPromise OnStop() { + m_timer.Change(-1, -1); + + // the component is in the stopping state + lock (m_lock) { + // after this lock no more pending operations could be created + var pending = m_pending; + // m_pending could be fulfilled and set to null already + if (pending != null) { + pending.Cancel(); + return pending.Then(base.OnStop); + } + } + + return base.OnStop(); + } + + protected override void Dispose(bool disposing, Exception lastError) { + if (disposing) + Safe.Dispose(m_timer); + + base.Dispose(disposing, lastError); + } + } +} + diff --git a/Implab/Components/PollingRunnableComponent.cs b/Implab/Components/PollingRunnableComponent.cs deleted file mode 100644 --- a/Implab/Components/PollingRunnableComponent.cs +++ /dev/null @@ -1,98 +0,0 @@ -using System; -using System.Threading; -using Implab.Diagnostics; - -namespace Implab.Components { - public class PollingRunnableComponent : RunnableComponent { - readonly Timer m_timer; - readonly Func, IPromise> m_dispatcher; - readonly TimeSpan m_interval; - - int m_processing; - Promise m_pending; - - protected PollingRunnableComponent(TimeSpan interval, Func, IPromise> dispatcher, bool initialized) : base(initialized) { - m_timer = new Timer(OnInternalTick); - - m_interval = interval; - m_dispatcher = dispatcher; - } - - protected override IPromise OnStart() { - m_timer.Change(TimeSpan.Zero, m_interval); - - return base.OnStart(); - } - - void OnInternalTick(object state) { - if (StartTick()) { - try { - AwaitTick(m_dispatcher != null ? m_dispatcher(OnTick) : OnTick()); - } catch (Exception error) { - HandleTickError(error); - } - } - } - - /// - /// Starts the tick handler. - /// - /// boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running. - /// - /// If the component is stopping no new handlers can be run. Every successful call to this method must be completed with either AwaitTick or HandleTickError handlers. - /// - protected virtual bool StartTick() { - if (State == ExecutionState.Running && Interlocked.CompareExchange(ref m_processing, 1, 0) == 0) { - m_pending = new Promise(); - m_pending - .On(() => m_processing = 0, PromiseEventType.All) - .On(null, LogTickError); - return true; - } - return false; - } - - protected virtual void AwaitTick(IPromise tick) { - if (tick == null) { - m_pending.Resolve(); - } else { - tick.On( - m_pending.Resolve, - m_pending.Reject, - m_pending.CancelOperation - ); - m_pending.CancellationRequested(tick.Cancel); - } - } - - protected virtual void HandleTickError(Exception error) { - m_pending.Reject(error); - } - - protected virtual void LogTickError(Exception error) { - } - - protected virtual IPromise OnTick() { - return Promise.SUCCESS; - } - - protected override IPromise OnStop() { - m_timer.Change(-1, -1); - - if (m_pending != null) { - m_pending.Cancel(); - return m_pending.Then(base.OnStop); - } - - return base.OnStop(); - } - - protected override void Dispose(bool disposing, Exception lastError) { - if (disposing) - Safe.Dispose(m_timer); - - base.Dispose(disposing, lastError); - } - } -} - diff --git a/Implab/Components/RunnableComponent.cs b/Implab/Components/RunnableComponent.cs --- a/Implab/Components/RunnableComponent.cs +++ b/Implab/Components/RunnableComponent.cs @@ -71,12 +71,15 @@ namespace Implab.Components { protected RunnableComponent(bool initialized) { m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); + DisposeTimeout = 10000; } - protected virtual int DisposeTimeout { - get { - return 10000; - } + /// + /// Gets or sets the timeout to wait for the pending operation to complete. If the pending operation doesn't finish than the component will be disposed anyway. + /// + protected int DisposeTimeout { + get; + set; } void ThrowInvalidCommand(Commands cmd) { @@ -91,6 +94,23 @@ namespace Implab.Components { ThrowInvalidCommand(cmd); } + /// + /// Moves the component from running to failed state. + /// + /// The exception which is describing the error. + /// Returns true if the component is set to the failed state, false - otherwise. + /// This method works only for the running state, in any other state it will return false. + protected bool Fail(Exception error) { + lock (m_stateMachine) { + if(m_stateMachine.State == ExecutionState.Running) { + m_stateMachine.Move(Commands.Fail); + m_lastError = error; + return true; + } + } + return false; + } + void Invoke(Commands cmd, Action action) { lock (m_stateMachine) Move(cmd); @@ -210,8 +230,7 @@ namespace Implab.Components { public ExecutionState State { get { - lock (m_stateMachine) - return m_stateMachine.State; + return m_stateMachine.State; } } @@ -225,6 +244,18 @@ namespace Implab.Components { #region IDisposable implementation + /// + /// Releases all resource used by the object. + /// + /// + /// Will not try to stop the component, it will just release all resources. + /// To cleanup the component gracefully use method. + /// + /// In normal cases the method shouldn't be called, the call to the + /// method is sufficient to cleanup the component. Call only to cleanup after errors, + /// especially if method is failed. Using this method insted of may + /// lead to the data loss by the component. + /// public void Dispose() { IPromise pending; lock (m_stateMachine) { diff --git a/Implab/FailedPromise.cs b/Implab/FailedPromise.cs new file mode 100644 --- /dev/null +++ b/Implab/FailedPromise.cs @@ -0,0 +1,100 @@ +using System; +using System.Reflection; + +namespace Implab { + public class FailedPromise : IPromise { + readonly Exception m_error; + public FailedPromise(Exception error) { + Safe.ArgumentNotNull(error, "error"); + m_error = error; + } + + #region IPromise implementation + + public IPromise On(Action success, Action error, Action cancel) { + if (error != null) { + try { + error(m_error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success, Action error) { + if (error != null) { + try { + error(m_error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success) { + return this; + } + + public IPromise On(Action handler, PromiseEventType events) { + if ((events & PromiseEventType.Error) != 0) { + try { + handler(); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise Cast() { + return (IPromise)this; + } + + public void Join() { + throw new TargetInvocationException(Error); + } + + public void Join(int timeout) { + throw new TargetInvocationException(Error); + } + + public virtual Type PromiseType { + get { + return typeof(void); + } + } + + public bool IsResolved { + get { + return true; + } + } + + public bool IsCancelled { + get { + return false; + } + } + + public Exception Error { + get { + return m_error; + } + } + + #endregion + + #region ICancellable implementation + + public void Cancel() { + } + + public void Cancel(Exception reason) { + } + + #endregion + } +} + diff --git a/Implab/FailedPromiseT.cs b/Implab/FailedPromiseT.cs new file mode 100644 --- /dev/null +++ b/Implab/FailedPromiseT.cs @@ -0,0 +1,65 @@ +using System; +using System.Reflection; + +namespace Implab { + public class FailedPromise : FailedPromise, IPromise { + public FailedPromise(Exception error) : base(error) { + } + + public IPromise On(Action success, Action error, Action cancel) { + if (error != null) { + try { + error(Error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success, Action error) { + if (error != null) { + try { + error(Error); + // Analysis disable once EmptyGeneralCatchClause + } catch { + } + } + return this; + } + + public IPromise On(Action success) { + return this; + } + + T IPromise.Join() { + throw new TargetInvocationException(Error); + } + + T IPromise.Join(int timeout) { + throw new TargetInvocationException(Error); + } + + + IPromise IPromise.On(Action success, Action error, Action cancel) { + On(success, error, cancel); + return this; + } + + IPromise IPromise.On(Action success, Action error) { + On(success, error); + return this; + } + + IPromise IPromise.On(Action success) { + On(success); + return this; + } + + IPromise IPromise.On(Action handler, PromiseEventType events) { + On(handler, events); + return this; + } + } +} + diff --git a/Implab/Implab.csproj b/Implab/Implab.csproj --- a/Implab/Implab.csproj +++ b/Implab/Implab.csproj @@ -194,7 +194,9 @@ - + + + diff --git a/Implab/PromiseT.cs b/Implab/PromiseT.cs --- a/Implab/PromiseT.cs +++ b/Implab/PromiseT.cs @@ -45,9 +45,7 @@ namespace Implab { } public static IPromise FromException(Exception error) { - var p = new Promise(); - p.Reject(error); - return p; + return new FailedPromise(error); } public virtual void Resolve(T value) { diff --git a/Implab/Safe.cs b/Implab/Safe.cs --- a/Implab/Safe.cs +++ b/Implab/Safe.cs @@ -67,55 +67,41 @@ namespace Implab } [DebuggerStepThrough] - public static IPromise WrapPromise(Func action) { - ArgumentNotNull(action, "action"); - - var p = new Promise(); - try { - p.Resolve(action()); - } catch (Exception err) { - p.Reject(err); - } - - return p; - } - - [DebuggerStepThrough] - public static IPromise WrapPromise(Action action) { - ArgumentNotNull(action, "action"); - - var p = new Promise(); - try { - action(); - p.Resolve(); - } catch (Exception err) { - p.Reject(err); - } - - return p; - } - - [DebuggerStepThrough] - public static IPromise InvokePromise(Func action) { + public static IPromise Run(Func action) { ArgumentNotNull(action, "action"); try { - var p = action(); - if (p == null) { - var d = new Promise(); - d.Reject(new Exception("The action returned null")); - p = d; - } - return p; + return Promise.FromResult(action()); } catch (Exception err) { - var p = new Promise(); - p.Reject(err); - return p; + return Promise.FromException(err); } } [DebuggerStepThrough] - public static IPromise InvokePromise(Func> action) { + public static IPromise Run(Action action) { + ArgumentNotNull(action, "action"); + + try { + action(); + return Promise.SUCCESS; + } catch (Exception err) { + return new FailedPromise(err); + } + } + + [DebuggerStepThrough] + public static IPromise Run(Func action) { + ArgumentNotNull(action, "action"); + + try { + return action() ?? new FailedPromise(new Exception("The action returned null")); + } catch (Exception err) { + return new FailedPromise(err); + } + } + + [DebuggerStepThrough] + public static IPromise Run(Func> action) { ArgumentNotNull(action, "action"); try { diff --git a/Implab/SuccessPromise.cs b/Implab/SuccessPromise.cs --- a/Implab/SuccessPromise.cs +++ b/Implab/SuccessPromise.cs @@ -8,14 +8,8 @@ namespace Implab { if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -25,14 +19,8 @@ namespace Implab { if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; diff --git a/Implab/SuccessPromiseT.cs b/Implab/SuccessPromiseT.cs --- a/Implab/SuccessPromiseT.cs +++ b/Implab/SuccessPromiseT.cs @@ -12,14 +12,8 @@ namespace Implab { if (success != null) { try { success(m_value); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -29,14 +23,8 @@ namespace Implab { if (success != null) { try { success(m_value); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -65,14 +53,8 @@ namespace Implab { if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this; @@ -82,14 +64,8 @@ namespace Implab { if (success != null) { try { success(); - } catch(Exception err) { - if (error != null) { - try { - error(err); - // Analysis disable once EmptyGeneralCatchClause - } catch { - } - } + // Analysis disable once EmptyGeneralCatchClause + } catch { } } return this;