##// END OF EJS Templates
runnable component, work in progress
cin -
r185:822aab37b107 ref20160224
parent child
Show More
@@ -0,0 +1,194
1 using System;
2 using System.Reflection;
3 using System.Threading;
4 using Implab.Parallels;
5 using Implab.Components;
6
7 #if MONO
8
9 using NUnit.Framework;
10 using TestClassAttribute = NUnit.Framework.TestFixtureAttribute;
11 using TestMethodAttribute = NUnit.Framework.TestAttribute;
12
13 #else
14
15 using Microsoft.VisualStudio.TestTools.UnitTesting;
16
17 #endif
18
19 namespace Implab.Test {
20 [TestClass]
21 public class RunnableComponentTests {
22
23 static void ShouldThrow(Action action) {
24 try {
25 action();
26 Assert.Fail();
27 } catch(AssertionException) {
28 throw;
29 } catch {
30 }
31 }
32
33 class Runnable : RunnableComponent {
34 public Runnable(bool initialized) : base(initialized) {
35 }
36
37 public Action MockInit {
38 get;
39 set;
40 }
41
42 public Func<IPromise> MockStart {
43 get;
44 set;
45 }
46
47 public Func<IPromise> MockStop {
48 get;
49 set;
50 }
51
52 protected override IPromise OnStart() {
53 return MockStart != null ? MockStart() : base.OnStart();
54 }
55
56 protected override IPromise OnStop() {
57 return MockStop != null ? MockStop() : base.OnStart();
58 }
59
60 protected override void OnInitialize() {
61 if (MockInit != null)
62 MockInit();
63 }
64 }
65
66 [TestMethod]
67 public void NormalFlowTest() {
68 var comp = new Runnable(false);
69
70 Assert.AreEqual(ExecutionState.Created, comp.State);
71
72 comp.Init();
73
74 Assert.AreEqual(ExecutionState.Ready, comp.State);
75
76 comp.Start().Join(1000);
77
78 Assert.AreEqual(ExecutionState.Running, comp.State);
79
80 comp.Stop().Join(1000);
81
82 Assert.AreEqual(ExecutionState.Disposed, comp.State);
83
84 }
85
86 [TestMethod]
87 public void InitFailTest() {
88 var comp = new Runnable(false) {
89 MockInit = () => {
90 throw new Exception("BAD");
91 }
92 };
93
94 ShouldThrow(() => comp.Start());
95 ShouldThrow(() => comp.Stop());
96 Assert.AreEqual(ExecutionState.Created, comp.State);
97
98 ShouldThrow(comp.Init);
99
100 Assert.AreEqual(ExecutionState.Failed, comp.State);
101
102 ShouldThrow(() => comp.Start());
103 ShouldThrow(() => comp.Stop());
104 Assert.AreEqual(ExecutionState.Failed, comp.State);
105
106 comp.Dispose();
107 Assert.AreEqual(ExecutionState.Disposed, comp.State);
108 }
109
110 [TestMethod]
111 public void DisposedTest() {
112
113 var comp = new Runnable(false);
114 comp.Dispose();
115
116 ShouldThrow(() => comp.Start());
117 ShouldThrow(() => comp.Stop());
118 ShouldThrow(comp.Init);
119
120 Assert.AreEqual(ExecutionState.Disposed, comp.State);
121 }
122
123 [TestMethod]
124 public void StartCancelTest() {
125 var comp = new Runnable(true) {
126 MockStart = () => PromiseHelper.Sleep(100000, 0)
127 };
128
129 var p = comp.Start();
130 Assert.AreEqual(ExecutionState.Starting, comp.State);
131 p.Cancel();
132 ShouldThrow(() => p.Join(1000));
133 Assert.AreEqual(ExecutionState.Failed, comp.State);
134 Assert.IsInstanceOfType(typeof(OperationCanceledException), comp.LastError);
135
136 comp.Dispose();
137 }
138
139 [TestMethod]
140 public void StartStopTest() {
141 var stop = new Signal();
142 var comp = new Runnable(true) {
143 MockStart = () => PromiseHelper.Sleep(100000, 0),
144 MockStop = () => AsyncPool.RunThread(stop.Wait)
145 };
146
147 var p1 = comp.Start();
148 var p2 = comp.Stop();
149 // should enter stopping state
150
151 ShouldThrow(p1.Join);
152 Assert.IsTrue(p1.IsCancelled);
153 Assert.AreEqual(ExecutionState.Stopping, comp.State);
154
155 stop.Set();
156 p2.Join(1000);
157 Assert.AreEqual(ExecutionState.Disposed, comp.State);
158 }
159
160 [TestMethod]
161 public void StartStopFailTest() {
162 var comp = new Runnable(true) {
163 MockStart = () => PromiseHelper.Sleep(100000, 0).Then(null,null,x => { throw new Exception("I'm dead"); })
164 };
165
166 comp.Start();
167 var p = comp.Stop();
168 // if Start fails to cancel, should fail to stop
169 ShouldThrow(() => p.Join(1000));
170 Assert.AreEqual(ExecutionState.Failed, comp.State);
171 Assert.IsNotNull(comp.LastError);
172 Assert.AreEqual("I'm dead", comp.LastError.Message);
173 }
174
175 [TestMethod]
176 public void StopCancelTest() {
177 var comp = new Runnable(true) {
178 MockStop = () => PromiseHelper.Sleep(100000, 0)
179 };
180
181 comp.Start();
182 var p = comp.Stop();
183 Assert.AreEqual(ExecutionState.Stopping, comp.State);
184 p.Cancel();
185 ShouldThrow(() => p.Join(1000));
186 Assert.AreEqual(ExecutionState.Failed, comp.State);
187 Assert.IsInstanceOfType(typeof(OperationCanceledException), comp.LastError);
188
189 comp.Dispose();
190 }
191
192 }
193 }
194
@@ -58,6 +58,7
58 58 <Compile Include="PromiseHelper.cs" />
59 59 <Compile Include="Properties\AssemblyInfo.cs" />
60 60 <Compile Include="CancelationTests.cs" />
61 <Compile Include="RunnableComponentTests.cs" />
61 62 </ItemGroup>
62 63 <ItemGroup>
63 64 <ProjectReference Include="..\Implab\Implab.csproj">
@@ -77,18 +77,19 namespace Implab {
77 77 /// <param name="error">Π˜ΡΠΊΠ»ΡŽΡ‡Π΅Π½ΠΈΠ΅ возникшСС ΠΏΡ€ΠΈ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΈΠΈ ΠΎΠΏΠ΅Ρ€Π°Ρ†ΠΈΠΈ</param>
78 78 /// <exception cref="InvalidOperationException">Π”Π°Π½Π½ΠΎΠ΅ ΠΎΠ±Π΅Ρ‰Π°Π½ΠΈΠ΅ ΡƒΠΆΠ΅ Π²Ρ‹ΠΏΠΎΠ»Π½Π΅Π½ΠΎ</exception>
79 79 protected void SetError(Exception error) {
80 while (error is PromiseTransientException)
81 error = error.InnerException;
82
83 var isCancel = error is OperationCanceledException;
84
80 85 if (BeginTransit()) {
81 if (error is OperationCanceledException) {
82 m_error = error.InnerException;
83 CompleteTransit(CANCELLED_STATE);
84 } else {
85 m_error = error is PromiseTransientException ? error.InnerException : error;
86 CompleteTransit(REJECTED_STATE);
87 }
86 m_error = isCancel ? error.InnerException : error;
87 CompleteTransit(isCancel ? CANCELLED_STATE : REJECTED_STATE);
88
88 89 Signal();
89 90 } else {
90 91 WaitTransition();
91 if (m_state == SUCCEEDED_STATE)
92 if (!isCancel || m_state == SUCCEEDED_STATE)
92 93 throw new InvalidOperationException("The promise is already resolved");
93 94 }
94 95 }
@@ -4,6 +4,15 namespace Implab {
4 4 public class ActionChainTask : ActionChainTaskBase, IDeferred {
5 5 readonly Func<IPromise> m_task;
6 6
7 /// <summary>
8 /// Initializes a new instance of the <see cref="Implab.ActionChainTask"/> class.
9 /// </summary>
10 /// <param name="task">The operation which will be performed when the <see cref="Resolve()"/> is called.</param>
11 /// <param name="error">The error handler which will invoke when the <see cref="Reject(Exception)"/> is called or when the task fails with an error.</param>
12 /// <param name="cancel">The cancellation handler.</param>
13 /// <param name="autoCancellable">If set to <c>true</c> will automatically accept
14 /// all cancel requests before the task is started with <see cref="Resolve()"/>,
15 /// after that all requests are directed to the task.</param>
7 16 public ActionChainTask(Func<IPromise> task, Func<Exception, IPromise> error, Func<Exception, IPromise> cancel, bool autoCancellable) : base(error,cancel, autoCancellable) {
8 17 m_task = task;
9 18 }
@@ -1,8 +1,7
1 1 using System;
2 using Implab.Formats;
3 2
4 3 namespace Implab.Components {
5 public class RunnableComponent : Disposable, IRunnable, IInitializable {
4 public abstract class RunnableComponent : IDisposable, IRunnable, IInitializable {
6 5 enum Commands {
7 6 Ok = 0,
8 7 Fail,
@@ -19,8 +18,11 namespace Implab.Components {
19 18 static StateMachine() {
20 19 _transitions = new ExecutionState[(int)ExecutionState.Last + 1, (int)Commands.Last + 1];
21 20
22 Edge(ExecutionState.Created, ExecutionState.Ready, Commands.Ok);
23 Edge(ExecutionState.Created, ExecutionState.Failed, Commands.Fail);
21 Edge(ExecutionState.Created, ExecutionState.Initializing, Commands.Init);
22 Edge(ExecutionState.Created, ExecutionState.Disposed, Commands.Dispose);
23
24 Edge(ExecutionState.Initializing, ExecutionState.Ready, Commands.Ok);
25 Edge(ExecutionState.Initializing, ExecutionState.Failed, Commands.Fail);
24 26
25 27 Edge(ExecutionState.Ready, ExecutionState.Starting, Commands.Start);
26 28 Edge(ExecutionState.Ready, ExecutionState.Disposed, Commands.Dispose);
@@ -36,7 +38,8 namespace Implab.Components {
36 38
37 39 Edge(ExecutionState.Stopping, ExecutionState.Failed, Commands.Fail);
38 40 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Ok);
39 Edge(ExecutionState.Stopping, ExecutionState.Disposed, Commands.Dispose);
41
42 Edge(ExecutionState.Failed, ExecutionState.Disposed, Commands.Dispose);
40 43 }
41 44
42 45 static void Edge(ExecutionState s1, ExecutionState s2, Commands cmd) {
@@ -70,72 +73,93 namespace Implab.Components {
70 73 m_stateMachine = new StateMachine(initialized ? ExecutionState.Ready : ExecutionState.Created);
71 74 }
72 75
76 protected virtual int DisposeTimeout {
77 get {
78 return 10000;
79 }
80 }
81
73 82 void ThrowInvalidCommand(Commands cmd) {
83 if (m_stateMachine.State == ExecutionState.Disposed)
84 throw new ObjectDisposedException(ToString());
85
74 86 throw new InvalidOperationException(String.Format("Commnd {0} is not allowed in the state {1}", cmd, m_stateMachine.State));
75 87 }
76 88
77 protected void Move(Commands cmd) {
78 lock (m_stateMachine)
89 void Move(Commands cmd) {
79 90 if (!m_stateMachine.Move(cmd))
80 91 ThrowInvalidCommand(cmd);
81 92 }
82 93
83 protected void Fail(Exception err) {
94 void Invoke(Commands cmd, Action action) {
95 lock (m_stateMachine)
96 Move(cmd);
97
98 try {
99 action();
100 lock(m_stateMachine)
101 Move(Commands.Ok);
102
103 } catch (Exception err) {
84 104 lock (m_stateMachine) {
85 if (!m_stateMachine.Move(Commands.Fail))
86 ThrowInvalidCommand(Commands.Fail);
87
105 Move(Commands.Fail);
88 106 m_lastError = err;
89 107 }
90 }
91
92 protected void Success() {
93 Move(Commands.Ok);
94 }
95
96 protected void Invoke(Commands cmd, Action action) {
97 Move(cmd);
98 try {
99 action();
100 Move(Commands.Ok);
101 } catch (Exception err) {
102 Fail(err);
103 108 throw;
104 109 }
105 110 }
106 111
107 protected IPromise InvokeAsync(Commands cmd, Func<IPromise> action) {
108 Move(cmd);
109 var medium = new Promise();
112 IPromise InvokeAsync(Commands cmd, Func<IPromise> action, Action<IPromise, IDeferred> chain) {
113 IPromise promise = null;
114 IPromise prev;
115
116 var task = new ActionChainTask(action, null, null, true);
110 117
111 IPromise promise = null;
118 lock (m_stateMachine) {
119 Move(cmd);
112 120
113 promise = medium.Then(
121 prev = m_pending;
122
123 promise = task.Then(
114 124 () => {
115 125 lock(m_stateMachine) {
116 126 if (m_pending == promise) {
127 Move(Commands.Ok);
117 128 m_pending = null;
118 Move(Commands.Ok);
119 129 }
120 130 }
121 131 }, e => {
132 lock(m_stateMachine) {
122 133 if (m_pending == promise) {
134 Move(Commands.Fail);
123 135 m_pending = null;
124 Fail(
136 m_lastError = e;
137 }
125 138 }
139 throw new PromiseTransientException(e);
140 },
141 r => {
142 lock(m_stateMachine) {
143 if (m_pending == promise) {
144 Move(Commands.Fail);
145 m_pending = null;
146 m_lastError = new OperationCanceledException("The operation has been cancelled", r);
147 }
148
149 }
150 throw new OperationCanceledException("The operation has been cancelled", r);
126 151 }
127 152 );
128 153
129
130
131 return Safe.InvokePromise(action).Then(
132 Success,
133 Fail
134 );
154 m_pending = promise;
135 155 }
136 156
137 void AddPending(IPromise result) {
157 if (prev == null)
158 task.Resolve();
159 else
160 chain(prev, task);
138 161
162 return promise;
139 163 }
140 164
141 165
@@ -153,43 +177,86 namespace Implab.Components {
153 177 #region IRunnable implementation
154 178
155 179 public IPromise Start() {
156 Move(Commands.Start);
157
158 return Safe.InvokePromise(OnStart).Then(
159 () => {
160 Move(Commands.Ok);
161 Run();
162 },
163 () => {
164 Move(Commands.Fail);
165 }
166 );
180 return InvokeAsync(Commands.Start, OnStart, null);
167 181 }
168 182
169 183 protected virtual IPromise OnStart() {
170 184 return Promise.SUCCESS;
171 185 }
172 186
173 protected virtual void Run() {
187 public IPromise Stop() {
188 return InvokeAsync(Commands.Stop, OnStop, StopPending).Then(Dispose);
189 }
190
191 protected virtual IPromise OnStop() {
192 return Promise.SUCCESS;
174 193 }
175 194
176 public IPromise Stop() {
177 throw new NotImplementedException();
195 /// <summary>
196 /// Stops the current operation if one exists.
197 /// </summary>
198 /// <param name="current">Current.</param>
199 /// <param name="stop">Stop.</param>
200 protected virtual void StopPending(IPromise current, IDeferred stop) {
201 if (current == null) {
202 stop.Resolve();
203 } else {
204 current.On(stop.Resolve, stop.Reject, stop.CancelOperation);
205 current.Cancel();
206 }
178 207 }
179 208
180 209 public ExecutionState State {
181 210 get {
182 throw new NotImplementedException();
211 return m_stateMachine.State;
183 212 }
184 213 }
185 214
186 215 public Exception LastError {
187 216 get {
188 throw new NotImplementedException();
217 return m_lastError;
189 218 }
190 219 }
191 220
192 221 #endregion
222
223 #region IDisposable implementation
224
225 public void Dispose() {
226 IPromise pending;
227 lock (m_stateMachine) {
228 if (m_stateMachine.State == ExecutionState.Disposed)
229 return;
230
231 Move(Commands.Dispose);
232
233 GC.SuppressFinalize(this);
234
235 pending = m_pending;
236 m_pending = null;
237 }
238 if (pending != null) {
239 pending.Cancel();
240 pending.Timeout(DisposeTimeout).On(
241 () => Dispose(true, null),
242 err => Dispose(true, err),
243 reason => Dispose(true, new OperationCanceledException("The operation is cancelled", reason))
244 );
245 } else {
246 Dispose(true, m_lastError);
193 247 }
194 248 }
195 249
250 ~RunnableComponent() {
251 Dispose(false, null);
252 }
253
254 #endregion
255
256 protected virtual void Dispose(bool disposing, Exception lastError) {
257
258 }
259
260 }
261 }
262
@@ -3,11 +3,6 using System;
3 3 using Implab.Diagnostics;
4 4 using System.Collections.Generic;
5 5
6
7 #if NET_4_5
8 using System.Threading.Tasks;
9 #endif
10
11 6 namespace Implab {
12 7 public static class PromiseExtensions {
13 8 public static IPromise<T> DispatchToCurrentContext<T>(this IPromise<T> that) {
@@ -17,12 +12,12 namespace Implab {
17 12 return that;
18 13
19 14 var p = new SyncContextPromise<T>(context);
20 p.On(that.Cancel, PromiseEventType.Cancelled);
15 p.CancellationRequested(that.Cancel);
21 16
22 17 that.On(
23 18 p.Resolve,
24 19 p.Reject,
25 p.Cancel
20 p.CancelOperation
26 21 );
27 22 return p;
28 23 }
@@ -32,13 +27,12 namespace Implab {
32 27 Safe.ArgumentNotNull(context, "context");
33 28
34 29 var p = new SyncContextPromise<T>(context);
35 p.On(that.Cancel, PromiseEventType.Cancelled);
36
30 p.CancellationRequested(that.Cancel);
37 31
38 32 that.On(
39 33 p.Resolve,
40 34 p.Reject,
41 p.Cancel
35 p.CancelOperation
42 36 );
43 37 return p;
44 38 }
@@ -77,8 +71,8 namespace Implab {
77 71 };
78 72 }
79 73
80 static void CancelCallback(object cookie) {
81 ((ICancellable)cookie).Cancel();
74 static void CancelByTimeoutCallback(object cookie) {
75 ((ICancellable)cookie).Cancel(new TimeoutException());
82 76 }
83 77
84 78 /// <summary>
@@ -89,7 +83,7 namespace Implab {
89 83 /// <typeparam name="TPromise">The 1st type parameter.</typeparam>
90 84 public static TPromise Timeout<TPromise>(this TPromise that, int milliseconds) where TPromise : IPromise {
91 85 Safe.ArgumentNotNull(that, "that");
92 var timer = new Timer(CancelCallback, that, milliseconds, -1);
86 var timer = new Timer(CancelByTimeoutCallback, that, milliseconds, -1);
93 87 that.On(timer.Dispose, PromiseEventType.All);
94 88 return that;
95 89 }
@@ -180,7 +174,6 namespace Implab {
180 174
181 175 var d = new ActionTask(success, error, cancel, false);
182 176 that.On(d.Resolve, d.Reject, d.CancelOperation);
183 if (success != null)
184 177 d.CancellationRequested(that.Cancel);
185 178 return d;
186 179 }
@@ -198,7 +191,6 namespace Implab {
198 191
199 192 var d = new FuncTask<T>(success, error, cancel, false);
200 193 that.On(d.Resolve, d.Reject, d.CancelOperation);
201 if (success != null)
202 194 d.CancellationRequested(that.Cancel);
203 195 return d;
204 196 }
@@ -215,7 +207,6 namespace Implab {
215 207 Safe.ArgumentNotNull(that, "that");
216 208 var d = new FuncTask<T,T2>(success, error, cancel, false);
217 209 that.On(d.Resolve, d.Reject, d.CancelOperation);
218 if (success != null)
219 210 d.CancellationRequested(that.Cancel);
220 211 return d;
221 212 }
@@ -234,7 +225,6 namespace Implab {
234 225
235 226 var d = new ActionChainTask(success, error, cancel, false);
236 227 that.On(d.Resolve, d.Reject, d.CancelOperation);
237 if (success != null)
238 228 d.CancellationRequested(that.Cancel);
239 229 return d;
240 230 }
General Comments 0
You need to be logged in to leave comments. Login now