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 } |
