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 }