diff Implab/Components/PollingComponent.cs @ 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
children 8200ab154c8a
line wrap: on
line diff
--- /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);
+        }
+    }
+}
+