changeset 203:4d9830a9bbb8 v2

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.
author cin
date Tue, 18 Oct 2016 17:49:54 +0300
parents 2651cb9a4250
children cbb0bd8fc0d1 8200ab154c8a
files Implab.Test/Implab.Test.mono.csproj Implab.Test/Mock/MockPollingComponent.cs Implab.Test/Mock/MockRunnableComponent.cs Implab.Test/PollingComponentTests.cs Implab.Test/PromiseHelper.cs Implab.Test/RunnableComponentTests.cs Implab/Components/IRunnable.cs Implab/Components/PollingComponent.cs Implab/Components/PollingRunnableComponent.cs Implab/Components/RunnableComponent.cs Implab/FailedPromise.cs Implab/FailedPromiseT.cs Implab/Implab.csproj Implab/PromiseT.cs Implab/Safe.cs Implab/SuccessPromise.cs Implab/SuccessPromiseT.cs
diffstat 17 files changed, 767 insertions(+), 235 deletions(-) [+]
line wrap: on
line diff
--- a/Implab.Test/Implab.Test.mono.csproj	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab.Test/Implab.Test.mono.csproj	Tue Oct 18 17:49:54 2016 +0300
@@ -59,6 +59,9 @@
     <Compile Include="Properties\AssemblyInfo.cs" />
     <Compile Include="CancelationTests.cs" />
     <Compile Include="RunnableComponentTests.cs" />
+    <Compile Include="PollingComponentTests.cs" />
+    <Compile Include="Mock\MockRunnableComponent.cs" />
+    <Compile Include="Mock\MockPollingComponent.cs" />
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\Implab\Implab.csproj">
@@ -66,4 +69,7 @@
       <Name>Implab</Name>
     </ProjectReference>
   </ItemGroup>
+  <ItemGroup>
+    <Folder Include="Mock\" />
+  </ItemGroup>
 </Project>
\ No newline at end of file
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/Mock/MockPollingComponent.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -0,0 +1,71 @@
+using System;
+using Implab.Components;
+
+namespace Implab.Test.Mock {
+    class MockPollingComponent : PollingComponent {
+        public MockPollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(interval, dispatcher, initialized)  {
+        }
+
+        public Action MockInit {
+            get;
+            set;
+        }
+
+        public Action<Exception> MockOnError {
+            get;
+            set;
+        }
+
+        public Action<Exception> MockOnCancel {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStart {
+            get;
+            set;
+        }
+
+        public Func<IPromise> MockStop {
+            get;
+            set;
+        }
+
+        public Func<ICancellationToken, IPromise> 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);
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/Mock/MockRunnableComponent.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -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<IPromise> MockStart {
+            get;
+            set;
+        }
+
+        public Func<IPromise> 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();
+        }
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab.Test/PollingComponentTests.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -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);
+        }
+
+    }
+}
+
--- a/Implab.Test/PromiseHelper.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab.Test/PromiseHelper.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -10,5 +10,13 @@
                 return retVal;
             });
         }
+
+        public static IPromise Sleep(int timeout) {
+            return AsyncPool.Invoke((ct) => {
+                ct.CancellationRequested(ct.CancelOperation);
+                Thread.Sleep(timeout);
+                return 0;
+            });
+        }
     }
 }
--- a/Implab.Test/RunnableComponentTests.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab.Test/RunnableComponentTests.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -3,6 +3,7 @@
 using System.Threading;
 using Implab.Parallels;
 using Implab.Components;
+using Implab.Test.Mock;
 
 #if MONO
 
@@ -30,42 +31,11 @@
             }
         }
 
-        class Runnable : RunnableComponent {
-            public Runnable(bool initialized) : base(initialized) {
-            }
-                
-            public Action MockInit {
-                get;
-                set;
-            }
 
-            public Func<IPromise> MockStart {
-                get;
-                set;
-            }
-
-            public Func<IPromise> 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 @@
 
         [TestMethod]
         public void InitFailTest() {
-            var comp = new Runnable(false) {
+            var comp = new MockRunnableComponent(false) {
                 MockInit = () => {
                     throw new Exception("BAD");
                 }
@@ -110,7 +80,7 @@
         [TestMethod]
         public void DisposedTest() {
 
-            var comp = new Runnable(false);
+            var comp = new MockRunnableComponent(false);
             comp.Dispose();
 
             ShouldThrow(() => comp.Start());
@@ -122,7 +92,7 @@
 
         [TestMethod]
         public void StartCancelTest() {
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStart = () => PromiseHelper.Sleep(100000, 0)
             };
 
@@ -140,7 +110,7 @@
         [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 @@
 
         [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 @@
 
         [TestMethod]
         public void StopCancelTest() {
-            var comp = new Runnable(true) {
+            var comp = new MockRunnableComponent(true) {
                 MockStop = () => PromiseHelper.Sleep(100000, 0)
             };
 
--- a/Implab/Components/IRunnable.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/Components/IRunnable.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -2,8 +2,14 @@
 
 namespace Implab.Components {
     public interface IRunnable {
+        /// <summary>
+        /// Starts this instance.
+        /// </summary>
         IPromise Start();
 
+        /// <summary>
+        /// Stops this instance. After the instance is stopped it can't be started again, stopping should be treated as gracefull and async dispose.
+        /// </summary>
         IPromise Stop();
 
         ExecutionState State { get; }
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/Components/PollingComponent.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -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<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher;
+        readonly TimeSpan m_interval;
+
+        readonly object m_lock = new object();
+
+        ActionTask m_pending;
+
+        protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, 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);
+                }
+            }   
+        }
+
+        /// <summary>
+        /// Checks wheather there is no running handler in the component and marks that the handler is starting.
+        /// </summary>
+        /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns>
+        /// <remarks>
+        /// 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.
+        /// </remarks>
+        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;
+            }
+        }
+
+        /// <summary>
+        /// Awaits the tick.
+        /// </summary>
+        /// <param name="tick">Tick.</param>
+        /// <remarks>
+        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
+        /// </remarks>
+        void AwaitTick(IPromise tick) {
+            if (tick == null) {
+                m_pending.Resolve();
+            } else {
+                tick.On(
+                    m_pending.Resolve,
+                    m_pending.Reject,
+                    m_pending.CancelOperation
+                );
+            }
+        }
+
+        /// <summary>
+        /// Handles the tick error.
+        /// </summary>
+        /// <remarks>
+        /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
+        /// </remarks>
+        void HandleTickError(Exception error) {
+            m_pending.Reject(error);
+        }
+
+        protected virtual void OnTickError(Exception error) {
+        }
+
+        protected virtual void OnTickCancel(Exception error) {
+        }
+
+        /// <summary>
+        /// Invoked when the timer ticks, use this method to implement your logic
+        /// </summary>
+        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);
+        }
+    }
+}
+
--- a/Implab/Components/PollingRunnableComponent.cs	Tue Oct 18 01:03:49 2016 +0300
+++ /dev/null	Thu Jan 01 00:00:00 1970 +0000
@@ -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<Func<IPromise>, IPromise> m_dispatcher;
-        readonly TimeSpan m_interval;
-
-        int m_processing;
-        Promise m_pending;
-
-        protected PollingRunnableComponent(TimeSpan interval, Func<Func<IPromise>, 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);
-                }
-            }   
-        }
-
-        /// <summary>
-        /// Starts the tick handler.
-        /// </summary>
-        /// <returns>boolean value, true - the new tick handler may be invoked, false - a tick handler is already running or a component isn't running.</returns>
-        /// <remarks>
-        /// 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.
-        /// </remarks>
-        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);
-        }
-    }
-}
-
--- a/Implab/Components/RunnableComponent.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/Components/RunnableComponent.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -71,12 +71,15 @@
 
         protected RunnableComponent(bool initialized) {
             m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created);
+            DisposeTimeout = 10000;
         }
 
-        protected virtual int DisposeTimeout {
-            get {
-                return 10000;
-            }
+        /// <summary>
+        /// 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.
+        /// </summary>
+        protected int DisposeTimeout {
+            get;
+            set;
         }
 
         void ThrowInvalidCommand(Commands cmd) {
@@ -91,6 +94,23 @@
                 ThrowInvalidCommand(cmd);
         }
 
+        /// <summary>
+        /// Moves the component from running to failed state.
+        /// </summary>
+        /// <param name="error">The exception which is describing the error.</param>
+        /// <returns>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.</returns>
+        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 @@
 
         public ExecutionState State {
             get {
-                lock (m_stateMachine)
-                    return m_stateMachine.State;
+                return m_stateMachine.State;
             }
         }
 
@@ -225,6 +244,18 @@
 
         #region IDisposable implementation
 
+        /// <summary>
+        /// Releases all resource used by the <see cref="Implab.Components.RunnableComponent"/> object.
+        /// </summary>
+        /// <remarks>
+        /// <para>Will not try to stop the component, it will just release all resources.
+        /// To cleanup the component gracefully use <see cref="Stop()"/> method.</para>
+        /// <para>
+        /// In normal cases the <see cref="Dispose()"/> method shouldn't be called, the call to the <see cref="Stop()"/>
+        /// method is sufficient to cleanup the component. Call <see cref="Dispose()"/> only to cleanup after errors,
+        /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may
+        /// lead to the data loss by the component.
+        /// </para></remarks>
         public void Dispose() {
             IPromise pending;
             lock (m_stateMachine) {
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/FailedPromise.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -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<Exception> error, Action<Exception> cancel) {
+            if (error != null) {
+                try {
+                    error(m_error);  
+                // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise On(Action success, Action<Exception> 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<T> Cast<T>() {
+            return (IPromise<T>)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
+    }
+}
+
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/Implab/FailedPromiseT.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -0,0 +1,65 @@
+using System;
+using System.Reflection;
+
+namespace Implab {
+    public class FailedPromise<T> : FailedPromise, IPromise<T> {
+        public FailedPromise(Exception error) : base(error) {
+        }
+
+        public IPromise<T> On(Action<T> success, Action<Exception> error, Action<Exception> cancel) {
+            if (error != null) {
+                try {
+                    error(Error);  
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise<T> On(Action<T> success, Action<Exception> error) {
+            if (error != null) {
+                try {
+                    error(Error);  
+                    // Analysis disable once EmptyGeneralCatchClause
+                } catch {
+                }
+            }
+            return this;
+        }
+
+        public IPromise<T> On(Action<T> success) {
+            return this;
+        }
+
+        T IPromise<T>.Join() {
+            throw new TargetInvocationException(Error);
+        }
+
+        T IPromise<T>.Join(int timeout) {
+            throw new TargetInvocationException(Error);
+        }
+
+
+        IPromise<T> IPromise<T>.On(Action success, Action<Exception> error, Action<Exception> cancel) {
+            On(success, error, cancel);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action success, Action<Exception> error) {
+            On(success, error);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action success) {
+            On(success);
+            return this;
+        }
+
+        IPromise<T> IPromise<T>.On(Action handler, PromiseEventType events) {
+            On(handler, events);
+            return this;
+        }
+    }
+}
+
--- a/Implab/Implab.csproj	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/Implab.csproj	Tue Oct 18 17:49:54 2016 +0300
@@ -194,7 +194,9 @@
     <Compile Include="Components\LazyAndWeak.cs" />
     <Compile Include="AbstractTask.cs" />
     <Compile Include="AbstractTaskT.cs" />
-    <Compile Include="Components\PollingRunnableComponent.cs" />
+    <Compile Include="FailedPromise.cs" />
+    <Compile Include="FailedPromiseT.cs" />
+    <Compile Include="Components\PollingComponent.cs" />
   </ItemGroup>
   <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
   <ItemGroup />
--- a/Implab/PromiseT.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/PromiseT.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -45,9 +45,7 @@
         }
 
         public static IPromise<T> FromException(Exception error) {
-            var p = new Promise<T>();
-            p.Reject(error);
-            return p;
+            return new FailedPromise<T>(error);
         }
 
         public virtual void Resolve(T value) {
--- a/Implab/Safe.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/Safe.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -67,55 +67,41 @@
         }
 
         [DebuggerStepThrough]
-        public static IPromise<T> WrapPromise<T>(Func<T> action) {
-            ArgumentNotNull(action, "action");
-
-            var p = new Promise<T>();
-            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<IPromise> action) {
+        public static IPromise<T> Run<T>(Func<T> 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<T>.FromResult(action());
             } catch (Exception err) {
-                var p = new Promise();
-                p.Reject(err);
-                return p;
+                return Promise<T>.FromException(err);
             }
         }
 
         [DebuggerStepThrough]
-        public static IPromise<T> InvokePromise<T>(Func<IPromise<T>> 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<IPromise> 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<T> Run<T>(Func<IPromise<T>> action) {
             ArgumentNotNull(action, "action");
 
             try {
--- a/Implab/SuccessPromise.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/SuccessPromise.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -8,14 +8,8 @@
             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 @@
             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;
--- a/Implab/SuccessPromiseT.cs	Tue Oct 18 01:03:49 2016 +0300
+++ b/Implab/SuccessPromiseT.cs	Tue Oct 18 17:49:54 2016 +0300
@@ -12,14 +12,8 @@
             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 @@
             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 @@
             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 @@
             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;