##// END OF EJS Templates
working on runnable components
cin -
r257:440801d88019 v3
parent child
Show More
@@ -1,290 +1,331
1 1 using System;
2 2 using System.Diagnostics;
3 3 using System.Threading;
4 4 using System.Threading.Tasks;
5 5
6 6 namespace Implab.Components {
7 7 /// <summary>
8 8 /// Base class for implementing components which support start and stop operations,
9 9 /// such components may represent running services.
10 10 /// </summary>
11 11 /// <remarks>
12 12 /// This class provides a basic lifecycle from the creation to the
13 13 /// termination of the component.
14 14 /// </remarks>
15 public class RunnableComponent : IAsyncComponent, IRunnable, IInitializable, IDisposable {
15 public abstract class RunnableComponent : IAsyncComponent, IRunnable, IInitializable, IDisposable {
16 16
17 17 /// <summary>
18 18 /// This class bounds <see cref="CancellationTokenSource"/> lifetime to the task,
19 19 /// when the task completes the associated token source will be disposed.
20 20 /// </summary>
21 21 class AsyncOperationDescriptor {
22 22
23 23 public static AsyncOperationDescriptor None { get; } = new AsyncOperationDescriptor();
24 24
25 25 readonly CancellationTokenSource m_cts;
26 26
27 27 bool m_done;
28 28
29 29 public CancellationToken Token {
30 30 get { return m_cts == null ? CancellationToken.None : m_cts.Token; }
31 31 }
32 32
33 33 public Task Task { get; private set; }
34 34
35 35 private AsyncOperationDescriptor(Task task, CancellationTokenSource cts) {
36 36 m_cts = cts;
37 37 Task = Chain(task);
38 38 }
39 39
40 40 private AsyncOperationDescriptor() {
41 41 Task = Task.CompletedTask;
42 42 }
43 43
44 44 public void Cancel() {
45 45 if (m_cts != null) {
46 46 lock (m_cts) {
47 47 if (!m_done)
48 48 m_cts.Cancel();
49 49 }
50 50 }
51 51 }
52 52
53 53 void Done() {
54 54 if (m_cts != null) {
55 55 lock (m_cts) {
56 56 m_done = true;
57 57 m_cts.Dispose();
58 58 }
59 59 } else {
60 60 m_done = true;
61 61 }
62 62 }
63 63
64 64 async Task Chain(Task other) {
65 65 try {
66 66 await other;
67 67 } finally {
68 68 Done();
69 69 }
70 70 }
71 71
72 72 public static AsyncOperationDescriptor Create(Func<CancellationToken, Task> factory, CancellationToken ct) {
73 73 var cts = ct.CanBeCanceled ?
74 74 CancellationTokenSource.CreateLinkedTokenSource(ct) :
75 75 new CancellationTokenSource();
76 76
77 77 return new AsyncOperationDescriptor(factory(cts.Token), cts);
78 78 }
79 79
80 80 }
81 81
82 82 // this lock is used to synchronize state flow of the component during
83 83 // processing calls from a client and internal processes.
84 84 readonly object m_lock = new object();
85 85
86 86 // current operation cookie, used to check wheather a call to
87 87 // MoveSuccess/MoveFailed method belongs to the current
88 88 // operation, if cookies didn't match ignore completion result.
89 89 object m_cookie;
90 90
91 91 // AsyncOperationDscriptor aggregates a task and it's cancellation token
92 92 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;
93 93
94 94 ExecutionState m_state;
95 95
96 /// <summary>
97 /// ΠžΠ±ΡŠΠ΅ΠΊΡ‚ синхронизации ΠΈΡΠΏΠΎΠ»ΡŒΠ·ΡƒΠ΅Ρ‚ΡΡ для обСспСчСния совмСстного доступа
98 /// ΠΊΠ»ΠΈΠ΅Π½Ρ‚Π° ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹ ΠΈ процСссов, ΠΏΡ€ΠΎΡ‚Π΅ΠΊΠ°ΡŽΡ‰ΠΈΡ… Π²Π½ΡƒΡ‚Ρ€ΠΈ ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹, ΠΊ ΠΎΠ±Ρ‰Π΅ΠΌΡƒ
99 /// ΡΠΎΡΡ‚ΠΎΡΠ½ΠΈΡŽ, Ρ‚.Π΅.true Ρ‚Π°ΠΊΠΈΠΌ свойствам, ΠΊΠ°ΠΊ <see cref="State"/>,
100 /// <see cref="LastError"/>. ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ события <see cref="StateChanged"/>
101 /// Π²Ρ‹Π·Ρ‹Π²Π°ΡŽΡ‚ΡΡ ΡƒΠΆΠ΅ с установлСнной Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΎΠΉ, поэтому Π΄ΠΎΠΏΠΎΠ»Π½ΠΈΡ‚Π΅Π»ΡŒΠ½Π°Ρ
102 /// синхронизация Π½Π΅ трСбуСтся.
103 /// </summary>
104 public object SynchronizationObject { get { return m_lock; } }
96 105
97 106 protected RunnableComponent(bool initialized) {
98 107 State = initialized ? ExecutionState.Ready : ExecutionState.Created;
99 108 }
100 109
101 110 public Task Completion {
102 111 get { return m_current.Task; }
103 112 }
104 113
105 114 public ExecutionState State {
106 115 get { return m_state; }
107 116 private set {
108 117 if (m_state != value) {
109 118 m_state = value;
110 119 StateChanged.DispatchEvent(this, new StateChangeEventArgs {
111 120 State = value,
112 121 LastError = LastError
113 122 });
114 123 }
115 124 }
116 125 }
117 126
118 127 public Exception LastError { get; private set; }
119 128
129 /// <summary>
130 /// Π‘ΠΎΠ±Ρ‹Ρ‚ΠΈΠ΅ измСнСния состояния ΠΊΠΎΠΌΠΏΠΎΠ½Π΅Π½Ρ‚Ρ‹.see ΠžΠ±Ρ€Π°Π±ΠΎΡ‚Ρ‡ΠΈΠΊΠΈ Π΄Π°Π½Π½ΠΎΠ³ΠΎ события
131 /// Π²Ρ‹Π·Ρ‹Π²Π°ΡŽΡ‚ΡΡ Π²Π½ΡƒΡ‚Ρ€ΠΈ Π±Π»ΠΎΠΊΠΈΡ€ΠΎΠ²ΠΊΠΈ <see cref="SynchronizationObject"/> ΠΈ Π΄ΠΎΠ»ΠΆΠ½Ρ‹
132 /// Π²Ρ‹ΠΏΠΎΠ»Π½ΡΡ‚ΡŒΡΡ максимально быстро.
133 /// </summary>
120 134 public event EventHandler<StateChangeEventArgs> StateChanged;
121 135
122 136 /// <summary>
123 137 /// Releases all resources used by the current component regardless of its
124 138 /// execution state.
125 139 /// </summary>
126 140 /// <remarks>
127 141 /// Calling to this method may result unexpedted results if the component
128 142 /// isn't in the stopped state. Call this method after the component is
129 143 /// stopped if needed or if the component is in the failed state.
130 144 /// </remarks>
131 145 public void Dispose() {
132 146 bool dispose = false;
147 lock (SynchronizationObject) {
148 if (m_state != ExecutionState.Disposed) {
149 dispose = true;
150 m_state = ExecutionState.Disposed;
151 m_cookie = new object();
152 }
153 }
133 154 if (dispose) {
134 155 Dispose(true);
135 156 GC.SuppressFinalize(this);
136 157 }
137 158 }
138 159
139 160 ~RunnableComponent() {
140 161 Dispose(false);
141 162 }
142 163
143 164 /// <summary>
144 165 /// Releases all resources used by the current component regardless of its
145 166 /// execution state.
146 167 /// </summary>
147 168 /// <param name="disposing">Indicates that the component is disposed
148 169 /// during a normal disposing or during GC.</param>
149 170 protected virtual void Dispose(bool disposing) {
150 171 }
151 172
152 173 public void Initialize() {
153 174 var cookie = new object();
154 175 if (MoveInitialize(cookie))
155 ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie);
176 Safe.NoWait(ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie));
156 177 else
157 178 throw new InvalidOperationException();
158 179 }
159 180
160 181 /// <summary>
161 182 /// This method is used for initialization during a component creation.
162 183 /// </summary>
163 184 /// <param name="ct">A cancellation token for this operation</param>
164 185 /// <remarks>
165 186 /// This method should be used for short and mostly syncronous operations,
166 187 /// other operations which require time to run shoud be placed in
167 188 /// <see cref="StartInternalAsync(CancellationToken)"/> method.
168 189 /// </remarks>
169 190 protected virtual Task InitializeInternalAsync(CancellationToken ct) {
170 191 return Task.CompletedTask;
171 192 }
172 193
173 194 public void Start(CancellationToken ct) {
174 195 var cookie = new object();
175 196 if (MoveStart(cookie))
176 ScheduleTask(StartInternalAsync, ct, cookie);
197 Safe.NoWait(ScheduleStartAndRun(ct, cookie));
177 198 else
178 199 throw new InvalidOperationException();
179 200 }
180 201
202 async Task ScheduleStartAndRun(CancellationToken ct, object cookie) {
203 try {
204 await ScheduleTask(StartInternalAsync, ct, cookie);
205 RunInternal();
206 } catch (Exception err) {
207 Fail(err);
208 }
209 }
210
181 211 protected virtual Task StartInternalAsync(CancellationToken ct) {
182 212 return Task.CompletedTask;
183 213 }
184 214
215 /// <summary>
216 /// This method is called after the component is enetered running state,
217 /// use this method to
218 /// </summary>
219 protected virtual void RunInternal() {
220
221 }
222
185 223 public void Stop(CancellationToken ct) {
186 224 var cookie = new object();
187 225 if (MoveStop(cookie))
188 ScheduleTask(StopAsync, ct, cookie);
226 Safe.NoWait(ScheduleTask(StopAsync, ct, cookie));
189 227 else
190 228 throw new InvalidOperationException();
191 229 }
192 230
193 231 async Task StopAsync(CancellationToken ct) {
194 232 m_current.Cancel();
195 233 await Completion;
196 234
197 235 ct.ThrowIfCancellationRequested();
198 236
199 237 await StopInternalAsync(ct);
200 238 }
201 239
202 240 protected virtual Task StopInternalAsync(CancellationToken ct) {
203 241 return Task.CompletedTask;
204 242 }
205 243
206 244 protected void Fail(Exception err) {
207 245 lock(m_lock) {
208 246 if (m_state != ExecutionState.Running)
209 247 return;
210 248 m_cookie = new object();
211 249 LastError = err;
212 250 State = ExecutionState.Failed;
213 251 }
214 252 }
215 253
216 254
217 255 #region state management
218 256
219 257 bool MoveInitialize(object cookie) {
220 258 lock (m_lock) {
221 259 if (State != ExecutionState.Created)
222 260 return false;
223 261 State = ExecutionState.Initializing;
224 262 m_cookie = cookie;
225 263 return true;
226 264 }
227 265 }
228 266
229 267 bool MoveStart(object cookie) {
230 268 lock (m_lock) {
231 269 if (State != ExecutionState.Ready)
232 270 return false;
233 271 State = ExecutionState.Starting;
234 272 m_cookie = cookie;
235 273 return true;
236 274 }
237 275 }
238 276
239 277 bool MoveStop(object cookie) {
240 278 lock (m_lock) {
241 279 if (State != ExecutionState.Starting && State != ExecutionState.Running)
242 280 return false;
243 281 State = ExecutionState.Stopping;
244 282 m_cookie = cookie;
245 283 return true;
246 284 }
247 285 }
248 286
249 287 void MoveSuccess(object cookie) {
250 288 lock (m_lock) {
251 289 if (m_cookie != cookie)
252 290 return;
253 291 switch (State) {
254 292 case ExecutionState.Initializing:
255 293 State = ExecutionState.Ready;
256 294 break;
257 295 case ExecutionState.Starting:
258 296 State = ExecutionState.Running;
259 297 break;
260 298 case ExecutionState.Stopping:
261 299 State = ExecutionState.Stopped;
262 300 break;
263 301 }
264 302 }
265 303 }
266 304
267 305 void MoveFailed(Exception err, object cookie) {
268 306 lock (m_lock) {
269 307 if (m_cookie != cookie)
270 308 return;
271 309 LastError = err;
272 310 State = ExecutionState.Failed;
273 311 }
274 312 }
275 313
276 void ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
314 Task ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
277 315
278 m_current = AsyncOperationDescriptor.Create(async (x) => {
316 var op = AsyncOperationDescriptor.Create(async (x) => {
279 317 try {
280 318 await next(x);
281 319 MoveSuccess(cookie);
282 320 } catch (Exception e) {
283 321 MoveFailed(e, cookie);
284 322 }
285 323 }, ct);
324
325 m_current = op;
326 return op.Task;
286 327 }
287 328
288 329 #endregion
289 330 }
290 331 } No newline at end of file
General Comments 3
Under Review
author

Auto status change to "Under Review"

Approved
author

ok, latest stable version should be in default

You need to be logged in to leave comments. Login now