# 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;