Mercurial > pub > ImplabNet
comparison Implab/Components/RunnableComponent.cs @ 205:8200ab154c8a v2
Added ResetState to RunnableComponent to reset in case of failure
Added StateChanged event to IRunnable
Renamed Promise.SUCCESS -> Promise.Success
Added Promise.FromException
Renamed Bundle -> PromiseAll in PromiseExtensions
author | cin |
---|---|
date | Tue, 25 Oct 2016 17:40:33 +0300 |
parents | 4d9830a9bbb8 |
children | 7d07503621fe |
comparison
equal
deleted
inserted
replaced
203:4d9830a9bbb8 | 205:8200ab154c8a |
---|---|
7 Fail, | 7 Fail, |
8 Init, | 8 Init, |
9 Start, | 9 Start, |
10 Stop, | 10 Stop, |
11 Dispose, | 11 Dispose, |
12 Last = Dispose | 12 Reset, |
13 Last = Reset | |
13 } | 14 } |
14 | 15 |
15 class StateMachine { | 16 class StateMachine { |
16 static readonly ExecutionState[,] _transitions; | 17 static readonly ExecutionState[,] _transitions; |
17 | 18 |
35 Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail); | 36 Edge(ExecutionState.Running, ExecutionState.Failed, Commands.Fail); |
36 Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop); | 37 Edge(ExecutionState.Running, ExecutionState.Stopping, Commands.Stop); |
37 Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); | 38 Edge(ExecutionState.Running, ExecutionState.Disposed, Commands.Dispose); |
38 | 39 |
39 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); | 40 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail); |
40 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok); | 41 Edge(ExecutionState.Stopping, ExecutionState.Ready, Commands.Ok); |
42 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose); | |
41 | 43 |
42 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); | 44 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose); |
45 Edge(ExecutionState.Failed, ExecutionState.Initializing, Commands.Reset); | |
43 } | 46 } |
44 | 47 |
45 static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { | 48 static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) { |
46 _transitions[(int)s1, (int)cmd] = s2; | 49 _transitions[(int)s1, (int)cmd] = s2; |
47 } | 50 } |
66 | 69 |
67 IPromise m_pending; | 70 IPromise m_pending; |
68 Exception m_lastError; | 71 Exception m_lastError; |
69 | 72 |
70 readonly StateMachine m_stateMachine; | 73 readonly StateMachine m_stateMachine; |
71 | 74 readonly bool m_reusable; |
72 protected RunnableComponent(bool initialized) { | 75 public event EventHandler<StateChangeEventArgs> StateChanged; |
76 | |
77 /// <summary> | |
78 /// Initializes component state. | |
79 /// </summary> | |
80 /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> | |
81 /// <param name="reusable">If set, the component may start after it has been stopped, otherwise the component is disposed after being stopped.</param> | |
82 protected RunnableComponent(bool initialized, bool reusable) { | |
73 m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); | 83 m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created); |
84 m_reusable = reusable; | |
74 DisposeTimeout = 10000; | 85 DisposeTimeout = 10000; |
86 } | |
87 | |
88 /// <summary> | |
89 /// Initializes component state. The component created with this constructor is not reusable, i.e. it will be disposed after stop. | |
90 /// </summary> | |
91 /// <param name="initialized">If set, the component initial state is <see cref="ExecutionState.Ready"/> and the component is ready to start, otherwise initialization is required.</param> | |
92 protected RunnableComponent(bool initialized) : this(initialized, false) { | |
75 } | 93 } |
76 | 94 |
77 /// <summary> | 95 /// <summary> |
78 /// 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. | 96 /// 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. |
79 /// </summary> | 97 /// </summary> |
83 } | 101 } |
84 | 102 |
85 void ThrowInvalidCommand(Commands cmd) { | 103 void ThrowInvalidCommand(Commands cmd) { |
86 if (m_stateMachine.State == ExecutionState.Disposed) | 104 if (m_stateMachine.State == ExecutionState.Disposed) |
87 throw new ObjectDisposedException(ToString()); | 105 throw new ObjectDisposedException(ToString()); |
88 | 106 |
89 throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); | 107 throw new InvalidOperationException(String.Format("Command {0} is not allowed in the state {1}", cmd, m_stateMachine.State)); |
90 } | 108 } |
91 | 109 |
92 void Move(Commands cmd) { | 110 bool MoveIfInState(Commands cmd, IPromise pending, Exception error, ExecutionState state) { |
93 if (!m_stateMachine.Move(cmd)) | 111 ExecutionState prev, current; |
94 ThrowInvalidCommand(cmd); | 112 lock (m_stateMachine) { |
113 if (m_stateMachine.State != state) | |
114 return false; | |
115 | |
116 prev = m_stateMachine.State; | |
117 if (!m_stateMachine.Move(cmd)) | |
118 ThrowInvalidCommand(cmd); | |
119 current = m_stateMachine.State; | |
120 | |
121 m_pending = pending; | |
122 m_lastError = error; | |
123 } | |
124 if (prev != current) | |
125 OnStateChanged(prev, current, error); | |
126 return true; | |
127 } | |
128 | |
129 bool MoveIfPending(Commands cmd, IPromise pending, Exception error, IPromise expected) { | |
130 ExecutionState prev, current; | |
131 lock (m_stateMachine) { | |
132 if (m_pending != expected) | |
133 return false; | |
134 prev = m_stateMachine.State; | |
135 if (!m_stateMachine.Move(cmd)) | |
136 ThrowInvalidCommand(cmd); | |
137 current = m_stateMachine.State; | |
138 m_pending = pending; | |
139 m_lastError = error; | |
140 } | |
141 if (prev != current) | |
142 OnStateChanged(prev, current, error); | |
143 return true; | |
144 } | |
145 | |
146 IPromise Move(Commands cmd, IPromise pending, Exception error) { | |
147 ExecutionState prev, current; | |
148 IPromise ret; | |
149 lock (m_stateMachine) { | |
150 prev = m_stateMachine.State; | |
151 if (!m_stateMachine.Move(cmd)) | |
152 ThrowInvalidCommand(cmd); | |
153 current = m_stateMachine.State; | |
154 | |
155 ret = m_pending; | |
156 m_pending = pending; | |
157 m_lastError = error; | |
158 | |
159 } | |
160 if(prev != current) | |
161 OnStateChanged(prev, current, error); | |
162 return ret; | |
163 } | |
164 | |
165 protected virtual void OnStateChanged(ExecutionState previous, ExecutionState current, Exception error) { | |
166 var h = StateChanged; | |
167 if (h != null) | |
168 h(this, new StateChangeEventArgs { | |
169 State = current, | |
170 LastError = error | |
171 }); | |
95 } | 172 } |
96 | 173 |
97 /// <summary> | 174 /// <summary> |
98 /// Moves the component from running to failed state. | 175 /// Moves the component from running to failed state. |
99 /// </summary> | 176 /// </summary> |
100 /// <param name="error">The exception which is describing the error.</param> | 177 /// <param name="error">The exception which is describing the error.</param> |
101 /// <returns>Returns true if the component is set to the failed state, false - otherwise. | |
102 /// This method works only for the running state, in any other state it will return false.</returns> | |
103 protected bool Fail(Exception error) { | 178 protected bool Fail(Exception error) { |
104 lock (m_stateMachine) { | 179 return MoveIfInState(Commands.Fail, null, error, ExecutionState.Running); |
105 if(m_stateMachine.State == ExecutionState.Running) { | 180 } |
106 m_stateMachine.Move(Commands.Fail); | 181 |
107 m_lastError = error; | 182 /// <summary> |
108 return true; | 183 /// Tries to reset <see cref="ExecutionState.Failed"/> state to <see cref="ExecutionState.Ready"/>. |
109 } | 184 /// </summary> |
110 } | 185 /// <returns>True if component is reset to <see cref="ExecutionState.Ready"/>, false if the componet wasn't |
111 return false; | 186 /// in <see cref="ExecutionState.Failed"/> state.</returns> |
112 } | 187 /// <remarks> |
113 | 188 /// This method checks the current state of the component and if it's in <see cref="ExecutionState.Failed"/> |
114 void Invoke(Commands cmd, Action action) { | 189 /// moves component to <see cref="ExecutionState.Initializing"/>. |
115 lock (m_stateMachine) | 190 /// The <see cref="OnResetState()"/> is called and if this method completes succesfully the component moved |
116 Move(cmd); | 191 /// to <see cref="ExecutionState.Ready"/> state, otherwise the component is moved to <see cref="ExecutionState.Failed"/> |
117 | 192 /// state. If <see cref="OnResetState()"/> throws an exception it will be propagated by this method to the caller. |
193 /// </remarks> | |
194 protected bool ResetState() { | |
195 if (!MoveIfInState(Commands.Reset, null, null, ExecutionState.Failed)) | |
196 return false; | |
197 | |
118 try { | 198 try { |
119 action(); | 199 OnResetState(); |
120 lock(m_stateMachine) | 200 Move(Commands.Ok, null, null); |
121 Move(Commands.Ok); | 201 return true; |
122 | |
123 } catch (Exception err) { | 202 } catch (Exception err) { |
124 lock (m_stateMachine) { | 203 Move(Commands.Fail, null, err); |
125 Move(Commands.Fail); | |
126 m_lastError = err; | |
127 } | |
128 throw; | 204 throw; |
129 } | 205 } |
206 } | |
207 | |
208 /// <summary> | |
209 /// This method is called by <see cref="ResetState"/> to reinitialize component in the failed state. | |
210 /// </summary> | |
211 /// <remarks> | |
212 /// Default implementation throws <see cref="NotImplementedException"/> which will cause the component | |
213 /// fail to reset it's state and it left in <see cref="ExecutionState.Failed"/> state. | |
214 /// If this method doesn't throw exceptions the component is moved to <see cref="ExecutionState.Ready"/> state. | |
215 /// </remarks> | |
216 protected virtual void OnResetState() { | |
217 throw new NotImplementedException(); | |
130 } | 218 } |
131 | 219 |
132 IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { | 220 IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) { |
133 IPromise promise = null; | 221 IPromise promise = null; |
134 IPromise prev; | 222 IPromise prev; |
135 | 223 |
136 var task = new ActionChainTask(action, null, null, true); | 224 var task = new ActionChainTask(action, null, null, true); |
137 | 225 |
138 lock (m_stateMachine) { | 226 Action<Exception> errorOrCancel = e => { |
139 Move(cmd); | 227 if (e == null) |
140 | 228 e = new OperationCanceledException(); |
141 prev = m_pending; | 229 MoveIfPending(Commands.Fail, null, e, promise); |
142 | 230 throw new PromiseTransientException(e); |
143 Action<Exception> errorOrCancel = e => { | 231 }; |
144 if (e == null) | 232 |
145 e = new OperationCanceledException(); | 233 promise = task.Then( |
146 | 234 () => MoveIfPending(Commands.Ok, null, null, promise), |
147 lock (m_stateMachine) { | 235 errorOrCancel, |
148 if (m_pending == promise) { | 236 errorOrCancel |
149 Move(Commands.Fail); | 237 ); |
150 m_pending = null; | 238 |
151 m_lastError = e; | 239 prev = Move(cmd, promise, null); |
152 } | |
153 } | |
154 throw new PromiseTransientException(e); | |
155 }; | |
156 | |
157 promise = task.Then( | |
158 () => { | |
159 lock(m_stateMachine) { | |
160 if (m_pending == promise) { | |
161 Move(Commands.Ok); | |
162 m_pending = null; | |
163 } | |
164 } | |
165 }, | |
166 errorOrCancel, | |
167 errorOrCancel | |
168 ); | |
169 | |
170 m_pending = promise; | |
171 } | |
172 | 240 |
173 if (prev == null) | 241 if (prev == null) |
174 task.Resolve(); | 242 task.Resolve(); |
175 else | 243 else |
176 chain(prev, task); | 244 chain(prev, task); |
179 } | 247 } |
180 | 248 |
181 | 249 |
182 #region IInitializable implementation | 250 #region IInitializable implementation |
183 | 251 |
184 public void Init() { | 252 public void Initialize() { |
185 Invoke(Commands.Init, OnInitialize); | 253 Move(Commands.Init, null, null); |
254 | |
255 try { | |
256 OnInitialize(); | |
257 Move(Commands.Ok, null, null); | |
258 } catch (Exception err) { | |
259 Move(Commands.Fail, null, err); | |
260 throw; | |
261 } | |
186 } | 262 } |
187 | 263 |
188 protected virtual void OnInitialize() { | 264 protected virtual void OnInitialize() { |
189 } | 265 } |
190 | 266 |
195 public IPromise Start() { | 271 public IPromise Start() { |
196 return InvokeAsync(Commands.Start, OnStart, null); | 272 return InvokeAsync(Commands.Start, OnStart, null); |
197 } | 273 } |
198 | 274 |
199 protected virtual IPromise OnStart() { | 275 protected virtual IPromise OnStart() { |
200 return Promise.SUCCESS; | 276 return Promise.Success; |
201 } | 277 } |
202 | 278 |
203 public IPromise Stop() { | 279 public IPromise Stop() { |
204 return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose); | 280 var pending = InvokeAsync(Commands.Stop, OnStop, StopPending); |
281 return m_reusable ? pending : pending.Then(Dispose); | |
205 } | 282 } |
206 | 283 |
207 protected virtual IPromise OnStop() { | 284 protected virtual IPromise OnStop() { |
208 return Promise.SUCCESS; | 285 return Promise.Success; |
209 } | 286 } |
210 | 287 |
211 /// <summary> | 288 /// <summary> |
212 /// Stops the current operation if one exists. | 289 /// Stops the current operation if one exists. |
213 /// </summary> | 290 /// </summary> |
256 /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may | 333 /// especially if <see cref="Stop"/> method is failed. Using this method insted of <see cref="Stop()"/> may |
257 /// lead to the data loss by the component. | 334 /// lead to the data loss by the component. |
258 /// </para></remarks> | 335 /// </para></remarks> |
259 public void Dispose() { | 336 public void Dispose() { |
260 IPromise pending; | 337 IPromise pending; |
338 | |
261 lock (m_stateMachine) { | 339 lock (m_stateMachine) { |
262 if (m_stateMachine.State == ExecutionState.Disposed) | 340 if (m_stateMachine.State == ExecutionState.Disposed) |
263 return; | 341 return; |
264 | 342 pending = Move(Commands.Dispose, null, null); |
265 Move(Commands.Dispose); | 343 } |
266 | 344 |
267 GC.SuppressFinalize(this); | 345 GC.SuppressFinalize(this); |
268 | |
269 pending = m_pending; | |
270 m_pending = null; | |
271 } | |
272 if (pending != null) { | 346 if (pending != null) { |
273 pending.Cancel(); | 347 pending.Cancel(); |
274 pending.Timeout(DisposeTimeout).On( | 348 pending.Timeout(DisposeTimeout).On( |
275 () => Dispose(true, null), | 349 () => Dispose(true, null), |
276 err => Dispose(true, err), | 350 err => Dispose(true, err), |
277 reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) | 351 reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason)) |
278 ); | 352 ); |
279 } else { | 353 } else { |
280 Dispose(true, m_lastError); | 354 Dispose(true, null); |
281 } | 355 } |
282 } | 356 } |
283 | 357 |
284 ~RunnableComponent() { | 358 ~RunnableComponent() { |
285 Dispose(false, null); | 359 Dispose(false, null); |
286 } | 360 } |
287 | 361 |
288 #endregion | 362 #endregion |
289 | 363 |
364 /// <summary> | |
365 /// Releases all resources used by the component, called automatically, override this method to implement your cleanup. | |
366 /// </summary> | |
367 /// <param name="disposing">true if this method is called during normal dispose process.</param> | |
368 /// <param name="lastError">The last error which occured during the component stop.</param> | |
290 protected virtual void Dispose(bool disposing, Exception lastError) { | 369 protected virtual void Dispose(bool disposing, Exception lastError) { |
291 | 370 |
292 } | 371 } |
293 | 372 |
294 } | 373 } |