comparison 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
comparison
equal deleted inserted replaced
202:2651cb9a4250 203:4d9830a9bbb8
1 using System;
2 using System.Threading;
3 using Implab.Diagnostics;
4
5 namespace Implab.Components {
6 public class PollingComponent : RunnableComponent {
7 readonly Timer m_timer;
8 readonly Func<Func<ICancellationToken, IPromise>, IPromise> m_dispatcher;
9 readonly TimeSpan m_interval;
10
11 readonly object m_lock = new object();
12
13 ActionTask m_pending;
14
15 protected PollingComponent(TimeSpan interval, Func<Func<ICancellationToken, IPromise>, IPromise> dispatcher, bool initialized) : base(initialized) {
16 m_timer = new Timer(OnInternalTick);
17
18 m_interval = interval;
19 m_dispatcher = dispatcher;
20 }
21
22 protected override IPromise OnStart() {
23 m_timer.Change(TimeSpan.Zero, m_interval);
24
25 return base.OnStart();
26 }
27
28 void OnInternalTick(object state) {
29 if (StartTick()) {
30 try {
31 if (m_dispatcher != null) {
32 var result = m_dispatcher(OnTick);
33 m_pending.CancellationRequested(result.Cancel);
34 AwaitTick(result);
35 } else {
36 AwaitTick(OnTick(m_pending));
37 }
38 } catch (Exception error) {
39 HandleTickError(error);
40 }
41 }
42 }
43
44 /// <summary>
45 /// Checks wheather there is no running handler in the component and marks that the handler is starting.
46 /// </summary>
47 /// <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>
48 /// <remarks>
49 /// 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.
50 /// </remarks>
51 protected virtual bool StartTick() {
52 lock (m_lock) {
53 if (State != ExecutionState.Running || m_pending != null)
54 return false;
55 // actually the component may be in a not running state here (stopping, disposed or what ever),
56 // but OnStop method also locks on the same object and will handle operation in m_pending
57 m_pending = new ActionTask(
58 () => {
59 // only one operation is running, it's safe to assing m_pending from it
60 m_pending = null;
61 },
62 ex => {
63 try {
64 OnTickError(ex);
65 // Analysis disable once EmptyGeneralCatchClause
66 } catch {
67 } finally {
68 m_pending = null;
69 }
70 // suppress error
71 },
72 ex => {
73 try {
74 OnTickCancel(ex);
75 // Analysis disable once EmptyGeneralCatchClause
76 } catch {
77 } finally {
78 m_pending = null;
79 }
80 // supress cancellation
81 },
82 false
83 );
84 return true;
85 }
86 }
87
88 /// <summary>
89 /// Awaits the tick.
90 /// </summary>
91 /// <param name="tick">Tick.</param>
92 /// <remarks>
93 /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
94 /// </remarks>
95 void AwaitTick(IPromise tick) {
96 if (tick == null) {
97 m_pending.Resolve();
98 } else {
99 tick.On(
100 m_pending.Resolve,
101 m_pending.Reject,
102 m_pending.CancelOperation
103 );
104 }
105 }
106
107 /// <summary>
108 /// Handles the tick error.
109 /// </summary>
110 /// <remarks>
111 /// This method is called only after StartTick method and m_pending will hold the promise which should be fulfilled.
112 /// </remarks>
113 void HandleTickError(Exception error) {
114 m_pending.Reject(error);
115 }
116
117 protected virtual void OnTickError(Exception error) {
118 }
119
120 protected virtual void OnTickCancel(Exception error) {
121 }
122
123 /// <summary>
124 /// Invoked when the timer ticks, use this method to implement your logic
125 /// </summary>
126 protected virtual IPromise OnTick(ICancellationToken cancellationToken) {
127 return Promise.SUCCESS;
128 }
129
130 protected override IPromise OnStop() {
131 m_timer.Change(-1, -1);
132
133 // the component is in the stopping state
134 lock (m_lock) {
135 // after this lock no more pending operations could be created
136 var pending = m_pending;
137 // m_pending could be fulfilled and set to null already
138 if (pending != null) {
139 pending.Cancel();
140 return pending.Then(base.OnStop);
141 }
142 }
143
144 return base.OnStop();
145 }
146
147 protected override void Dispose(bool disposing, Exception lastError) {
148 if (disposing)
149 Safe.Dispose(m_timer);
150
151 base.Dispose(disposing, lastError);
152 }
153 }
154 }
155