##// END OF EJS Templates
Added IInitializable.Initialize() overload...
cin -
r262:f1696cdc3d7a v3.0.8 v3
parent child
Show More
@@ -1,173 +1,152
1 using NUnit.Framework;
1 using Xunit;
2 using System;
2 using System;
3 using Implab.Automaton;
3 using Implab.Automaton;
4 using Implab.Xml;
4 using Implab.Xml;
5 using System.Xml;
5 using System.Xml;
6 using Implab.Formats;
6 using Implab.Formats;
7 using Implab.Formats.Json;
7 using Implab.Formats.Json;
8 using System.IO;
8 using System.IO;
9
9
10 namespace Implab.Format.Test {
10 namespace Implab.Test {
11 [TestFixture]
11 public class JsonTests {
12 public class JsonTests {
12
13
13 [Fact]
14 [Test]
15 public void TestScannerValidTokens() {
14 public void TestScannerValidTokens() {
16 using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) {
15 using (var scanner = JsonStringScanner.Create(@"9123, -123, 0, 0.1, -0.2, -0.1e3, 1.3E-3, ""some \t\n\u0020 text"", literal []{}:")) {
17
16
18 Tuple<JsonTokenType, object>[] expexted = {
17 Tuple<JsonTokenType, object>[] expexted = {
19 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "9123"),
18 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "9123"),
20 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
19 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
21 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-123"),
20 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-123"),
22 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
21 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
23 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0"),
22 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0"),
24 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
23 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
25 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0.1"),
24 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "0.1"),
26 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
25 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
27 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.2"),
26 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.2"),
28 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
27 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
29 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.1e3"),
28 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "-0.1e3"),
30 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
29 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
31 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "1.3E-3"),
30 new Tuple<JsonTokenType,object>(JsonTokenType.Number, "1.3E-3"),
32 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
31 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
33 new Tuple<JsonTokenType,object>(JsonTokenType.String, "some \t\n text"),
32 new Tuple<JsonTokenType,object>(JsonTokenType.String, "some \t\n text"),
34 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
33 new Tuple<JsonTokenType,object>(JsonTokenType.ValueSeparator, null),
35 new Tuple<JsonTokenType,object>(JsonTokenType.Literal, "literal"),
34 new Tuple<JsonTokenType,object>(JsonTokenType.Literal, "literal"),
36 new Tuple<JsonTokenType,object>(JsonTokenType.BeginArray, null),
35 new Tuple<JsonTokenType,object>(JsonTokenType.BeginArray, null),
37 new Tuple<JsonTokenType,object>(JsonTokenType.EndArray, null),
36 new Tuple<JsonTokenType,object>(JsonTokenType.EndArray, null),
38 new Tuple<JsonTokenType,object>(JsonTokenType.BeginObject, null),
37 new Tuple<JsonTokenType,object>(JsonTokenType.BeginObject, null),
39 new Tuple<JsonTokenType,object>(JsonTokenType.EndObject, null),
38 new Tuple<JsonTokenType,object>(JsonTokenType.EndObject, null),
40 new Tuple<JsonTokenType,object>(JsonTokenType.NameSeparator, null)
39 new Tuple<JsonTokenType,object>(JsonTokenType.NameSeparator, null)
41 };
40 };
42
41
43 string value;
42 string value;
44 JsonTokenType tokenType;
43 JsonTokenType tokenType;
45 for (var i = 0; i < expexted.Length; i++) {
44 for (var i = 0; i < expexted.Length; i++) {
46
45
47 Assert.IsTrue(scanner.ReadToken(out value, out tokenType));
46 Assert.True(scanner.ReadToken(out value, out tokenType));
48 Assert.AreEqual(expexted[i].Item1, tokenType);
47 Assert.Equal(expexted[i].Item1, tokenType);
49 Assert.AreEqual(expexted[i].Item2, value);
48 Assert.Equal(expexted[i].Item2, value);
50 }
49 }
51
50
52 Assert.IsFalse(scanner.ReadToken(out value, out tokenType));
51 Assert.False(scanner.ReadToken(out value, out tokenType));
53 }
52 }
54 }
53 }
55
54
56 [Test]
55 [Fact]
57 public void TestScannerBadTokens() {
56 public void TestScannerBadTokens() {
58 var bad = new[] {
57 var bad = new[] {
59 " 1",
58 " 1",
60 " literal",
59 " literal",
61 " \"",
60 " \"",
62 "\"unclosed string",
61 "\"unclosed string",
63 "1.bad",
62 "1.bad",
64 "001", // should be read as three numbers
63 "001", // should be read as three numbers
65 "--10",
64 "--10",
66 "+10",
65 "+10",
67 "1.0.0",
66 "1.0.0",
68 "1e1.0",
67 "1e1.0",
69 "l1teral0",
68 "l1teral0",
70 ".123",
69 ".123",
71 "-.123"
70 "-.123"
72 };
71 };
73
72
74 foreach (var json in bad) {
73 foreach (var json in bad) {
75 using (var scanner = JsonStringScanner.Create(json)) {
74 using (var scanner = JsonStringScanner.Create(json)) {
76 try {
75 try {
77 string value;
76 string value;
78 JsonTokenType token;
77 JsonTokenType token;
79 scanner.ReadToken(out value, out token);
78 scanner.ReadToken(out value, out token);
80 if (!Object.Equals(value, json)) {
79 if (!Object.Equals(value, json)) {
81 Console.WriteLine("'{0}' is read as {1}", json, value is String ? String.Format("'{0}'", value) : value);
80 Console.WriteLine("'{0}' is read as {1}", json, value is String ? String.Format("'{0}'", value) : value);
82 continue;
81 continue;
83 }
82 }
84 Assert.Fail("Token '{0}' shouldn't pass", json);
83 Assert.True(false, $"Token '{json}' shouldn't pass");
85 } catch (ParserException e) {
84 } catch (ParserException e) {
86 Console.WriteLine(e.Message);
85 Console.WriteLine(e.Message);
87 }
86 }
88 }
87 }
89 }
88 }
90 }
89 }
91
90
92 [Test]
91 [Fact]
93 public void JsonXmlReaderSimpleTest() {
92 public void JsonXmlReaderSimpleTest() {
94 var json = "\"some text\"";
93 var json = "\"some text\"";
95 //Console.WriteLine($"JSON: {json}");
94 //Console.WriteLine($"JSON: {json}");
96 //Console.WriteLine("XML");
95 //Console.WriteLine("XML");
97 /*using (var xmlReader = new JsonXmlReader(new JSONParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", RootName = "string", NodesPrefix = "json" })) {
96 /*using (var xmlReader = new JsonXmlReader(new JSONParser(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", RootName = "string", NodesPrefix = "json" })) {
98 Assert.AreEqual(xmlReader.ReadState, System.Xml.ReadState.Initial);
97 Assert.AreEqual(xmlReader.ReadState, System.Xml.ReadState.Initial);
99
98
100 AssertRead(xmlReader, XmlNodeType.XmlDeclaration);
99 AssertRead(xmlReader, XmlNodeType.XmlDeclaration);
101 AssertRead(xmlReader, XmlNodeType.Element);
100 AssertRead(xmlReader, XmlNodeType.Element);
102 AssertRead(xmlReader, XmlNodeType.Text);
101 AssertRead(xmlReader, XmlNodeType.Text);
103 AssertRead(xmlReader, XmlNodeType.EndElement);
102 AssertRead(xmlReader, XmlNodeType.EndElement);
104 Assert.IsFalse(xmlReader.Read());
103 Assert.IsFalse(xmlReader.Read());
105 }*/
104 }*/
106
105
107 //DumpJsonParse("\"text value\"");
106 DumpJsonParse("\"text value\"");
108 //DumpJsonParse("null");
107 DumpJsonParse("null");
109 //DumpJsonParse("true");
108 DumpJsonParse("true");
110 //DumpJsonParse("{}");
109 DumpJsonParse("{}");
111 //DumpJsonParse("[]");
110 DumpJsonParse("[]");
112 DumpJsonParse("{\"one\":1, \"two\":2}");
111 DumpJsonParse("{\"one\":1, \"two\":2}");
113 DumpJsonParse("[1,\"\",2,3]");
112 DumpJsonParse("[1,\"\",2,3]");
114 DumpJsonParse("[{\"info\": [7,8,9]}]");
113 DumpJsonParse("[{\"info\": [7,8,9]}]");
115 DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]");
114 DumpJsonFlatParse("[1,2,\"\",[3,4],{\"info\": [5,6]},{\"num\": [7,8,null]}, null,[null]]");
116 }
115 }
117
116
118 [Test]
117 void AssertRead(XmlReader reader, XmlNodeType expected) {
119 public void JsonBenchmark() {
118 Assert.True(reader.Read());
120 var t = Environment.TickCount;
119 Console.WriteLine($"{new string(' ', reader.Depth * 2)}{reader}");
121 using (var reader = new JsonXmlReader(JsonReader.Create("e:\\citylots.json"), new JsonXmlReaderOptions { NamespaceUri = "XmlReaderSimpleTest", RootName = "data" })) {
120 Assert.Equal(expected, reader.NodeType);
122 while (reader.Read()) {
123 }
124 }
125 Console.WriteLine($"JsonXmlReader: {Environment.TickCount - t} ms");
126
127 t = Environment.TickCount;
128 using(var reader = JsonReader.Create("e:\\citylots.json")) {
129 while(reader.Read()) {
130 }
131 }
132
133 Console.WriteLine($"JsonReader: {Environment.TickCount - t} ms");
134
135 t = Environment.TickCount;
136 using (var reader = XmlReader.Create("file:///e:\\citylots.xml")) {
137 while (reader.Read()) {
138 }
139 }
140
141 Console.WriteLine($"XmlReader: {Environment.TickCount - t} ms");
142 }
143
144 void AssertRead(XmlReader reader, XmlNodeType expected) {
145 Assert.IsTrue(reader.Read());
146 Console.WriteLine($"{new string(' ', reader.Depth * 2)}{reader}");
147 Assert.AreEqual(expected, reader.NodeType);
148 }
121 }
149
122
150 void DumpJsonParse(string json) {
123 void DumpJsonParse(string json) {
151 Console.WriteLine($"JSON: {json}");
124 Console.WriteLine($"JSON: {json}");
152 Console.WriteLine("XML");
125 Console.WriteLine("XML");
153 using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) {
126 using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings {
154 while (xmlReader.Read())
127 Indent = true,
155 Console.WriteLine($"{new string(' ', xmlReader.Depth * 2)}{xmlReader}");
128 CloseOutput = false,
156 }
129 ConformanceLevel = ConformanceLevel.Document
130 }))
131 using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "json" })) {
132 xmlWriter.WriteNode(xmlReader, false);
133 }
134 Console.WriteLine();
157 }
135 }
158
136
159 void DumpJsonFlatParse(string json) {
137 void DumpJsonFlatParse(string json) {
160 Console.WriteLine($"JSON: {json}");
138 Console.WriteLine($"JSON: {json}");
161 Console.WriteLine("XML");
139 Console.WriteLine("XML");
162 using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings {
140 using (var xmlWriter = XmlWriter.Create(Console.Out, new XmlWriterSettings {
163 Indent = true,
141 Indent = true,
164 CloseOutput = false,
142 CloseOutput = false,
165 ConformanceLevel = ConformanceLevel.Document
143 ConformanceLevel = ConformanceLevel.Document
166 }))
144 }))
167 using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) {
145 using (var xmlReader = new JsonXmlReader(JsonReader.ParseString(json), new JsonXmlReaderOptions { NamespaceUri = "JsonXmlReaderSimpleTest", NodesPrefix = "", FlattenArrays = true })) {
168 xmlWriter.WriteNode(xmlReader, false);
146 xmlWriter.WriteNode(xmlReader, false);
169 }
147 }
148 Console.WriteLine();
170 }
149 }
171 }
150 }
172 }
151 }
173
152
@@ -1,35 +1,37
1 using System;
1 using System;
2 using System.Threading;
2 using System.Threading;
3 using System.Threading.Tasks;
3 using System.Threading.Tasks;
4 using Implab.Components;
4 using Implab.Components;
5
5
6 namespace Implab.Test {
6 namespace Implab.Test {
7 public class MockPollComponent : PollingComponent {
7 public class MockPollComponent : PollingComponent {
8
8
9 public Func<CancellationToken,Task> PollWorker { get; set;}
9 public Func<CancellationToken,Task> PollWorker { get; set;}
10
10
11 public Func<CancellationToken, Task> StartWorker { get; set; }
11 public Func<CancellationToken, Task> StartWorker { get; set; }
12
12
13 public Func<CancellationToken, Task> StopWorker { get; set; }
13 public Func<CancellationToken, Task> StopWorker { get; set; }
14
14
15 public MockPollComponent(bool initialized) : base(initialized) {
15 public MockPollComponent(bool initialized) : base(initialized) {
16 }
16 }
17
17
18 protected async override Task Poll(CancellationToken ct) {
18 protected async override Task Poll(CancellationToken ct) {
19 if(PollWorker!= null)
19 if(PollWorker!= null)
20 await PollWorker.Invoke(ct);
20 await PollWorker.Invoke(ct);
21 }
21 }
22
22
23 protected async override Task StopInternalAsync(CancellationToken ct) {
23 protected async override Task StopInternalAsync(CancellationToken ct) {
24 await base.StopInternalAsync(ct);
24 if (StopWorker != null)
25 if (StopWorker != null)
25 await StopWorker.Invoke(ct);
26 await StopWorker.Invoke(ct);
26 }
27 }
27
28
28 protected async override Task StartInternalAsync(CancellationToken ct) {
29 protected async override Task StartInternalAsync(CancellationToken ct) {
30 await base.StartInternalAsync(ct);
29 if (StartWorker != null)
31 if (StartWorker != null)
30 await StartWorker.Invoke(ct);
32 await StartWorker.Invoke(ct);
31 }
33 }
32
34
33
35
34 }
36 }
35 } No newline at end of file
37 }
@@ -1,30 +1,264
1 using System;
1 using System;
2 using System.Threading;
2 using System.Threading;
3 using System.Threading.Tasks;
3 using System.Threading.Tasks;
4 using Implab.Components;
4 using Implab.Components;
5 using Xunit;
5 using Xunit;
6
6
7 namespace Implab.Test {
7 namespace Implab.Test {
8
8
9 public class RunnableComponentTests {
9 public class RunnableComponentTests {
10 [Fact]
10 [Fact]
11 public async Task Test1() {
11 public async Task SimpleStartStop() {
12
12
13 using (var m = new MockPollComponent(true)) {
13 using (var m = new MockPollComponent(true)) {
14 m.StartWorker = async (ct) => await Task.Yield();
14 m.StartWorker = async (ct) => await Task.Yield();
15 m.StopWorker = async (ct) => await Task.Yield();
15 m.StopWorker = async (ct) => await Task.Yield();
16
16
17 Assert.Equal(ExecutionState.Ready, m.State);
17 Assert.Equal(ExecutionState.Ready, m.State);
18 Assert.NotNull(m.Completion);
18 Assert.NotNull(m.Completion);
19
19
20 m.Start(CancellationToken.None);
20 m.Start();
21 await m.Completion;
21 await m.Completion;
22 Assert.Equal(ExecutionState.Running, m.State);
22 Assert.Equal(ExecutionState.Running, m.State);
23
23
24 m.Stop();
25 await m.Completion;
26 Assert.Equal(ExecutionState.Stopped, m.State);
27 }
28 }
29
30 [Fact]
31 public async Task SyncStart() {
32 using (var m = new MockPollComponent(true)) {
33 m.Start();
34 Assert.Equal(ExecutionState.Running, m.State);
35 await m.Completion;
36 }
37 }
38
39 [Fact]
40 public async Task AsyncStarting() {
41 using (var m = new MockPollComponent(true)) {
42 var signal = Safe.CreateTask();
43
44 m.StartWorker = async (ct) => await signal;
45 m.Start();
46
47 Assert.Equal(ExecutionState.Starting, m.State);
48 Assert.False(m.Completion.IsCompleted);
49
50 signal.Start();
51
52 await m.Completion;
53
54 Assert.Equal(ExecutionState.Running, m.State);
55 }
56 }
57
58 [Fact]
59 public async Task FailWhileStarting() {
60 using (var m = new MockPollComponent(true)) {
61 const string failMessage = "Fail me";
62 var signal = new Task(() => {
63 throw new Exception(failMessage);
64 });
65
66 m.StartWorker = async (ct) => await signal;
67 m.Start();
68
69 Assert.Equal(ExecutionState.Starting, m.State);
70 Assert.False(m.Completion.IsCompleted);
71
72 signal.Start();
73 try {
74 await m.Completion;
75 Assert.True(false);
76 } catch (Exception e) {
77 Assert.Equal(failMessage, e.Message);
78 }
79
80 Assert.Equal(ExecutionState.Failed, m.State);
81 }
82 }
83
84 [Fact]
85 public async Task SyncStop() {
86 using (var m = new MockPollComponent(true)) {
87 m.Start();
88 Assert.Equal(ExecutionState.Running, m.State);
89 m.Stop();
90 Assert.Equal(ExecutionState.Stopped, m.State);
91 await m.Completion;
92 }
93 }
94
95 [Fact]
96 public async Task AsyncStopping() {
97 using (var m = new MockPollComponent(true)) {
98 var signal = Safe.CreateTask();
99
100 m.StopWorker = async (ct) => await signal;
101
102 // Start
103 m.Start();
104 Assert.Equal(ExecutionState.Running, m.State);
105
106 // Stop
107 m.Stop();
108 Assert.Equal(ExecutionState.Stopping, m.State);
109 Assert.False(m.Completion.IsCompleted);
110 signal.Start();
111
112 await m.Completion;
113
114 Assert.Equal(ExecutionState.Stopped, m.State);
115 }
116 }
117
118 [Fact]
119 public async Task FailWhileStopping() {
120 using (var m = new MockPollComponent(true)) {
121 const string failMessage = "Fail me";
122 var signal = new Task(() => {
123 throw new Exception(failMessage);
124 });
125
126 m.StopWorker = async (ct) => await signal;
127
128 // Start
129 m.Start();
130 Assert.Equal(ExecutionState.Running, m.State);
131
132 // Stop
133 m.Stop();
134 Assert.Equal(ExecutionState.Stopping, m.State);
135 Assert.False(m.Completion.IsCompleted);
136
137 signal.Start();
138 try {
139 await m.Completion;
140 Assert.True(false);
141 } catch (Exception e) {
142 Assert.Equal(failMessage, e.Message);
143 }
144
145 Assert.Equal(ExecutionState.Failed, m.State);
146 }
147 }
148
149 [Fact]
150 public async Task ThrowOnInvalidTrasition() {
151 using (var m = new MockPollComponent(false)) {
152 var started = Safe.CreateTask();
153 var stopped = Safe.CreateTask();
154
155 m.StartWorker = async (ct) => await started;
156 m.StopWorker = async (ct) => await stopped;
157
158 Assert.Throws<InvalidOperationException>(() => m.Start());
159
160 // Initialize
161 m.Initialize();
162 await m.Completion;
163
164 // Start
165 m.Start();
166 Assert.Equal(ExecutionState.Starting, m.State);
167
168 // Check invalid transitions
169 Assert.Throws<InvalidOperationException>(() => m.Start());
170
171 // Component can be stopped before started
172 // m.Stop(CancellationToken.None);
173
174 // Running
175 started.Start();
176 await m.Completion;
177 Assert.Equal(ExecutionState.Running, m.State);
178
179
180 Assert.Throws<InvalidOperationException>(() => m.Start());
181
182 // Stop
183 m.Stop();
184
185 // Check invalid transitions
186 Assert.Throws<InvalidOperationException>(() => m.Start());
187 Assert.Throws<InvalidOperationException>(() => m.Stop());
188
189 // Stopped
190 stopped.Start();
191 await m.Completion;
192 Assert.Equal(ExecutionState.Stopped, m.State);
193
194 // Check invalid transitions
195 Assert.Throws<InvalidOperationException>(() => m.Start());
196 Assert.Throws<InvalidOperationException>(() => m.Stop());
197 }
198 }
199
200 [Fact]
201 public async Task CancelStart() {
202 using (var m = new MockPollComponent(true)) {
203 m.StartWorker = (ct) => Safe.CreateTask(ct);
204
205 m.Start();
206 var start = m.Completion;
207
208 Assert.Equal(ExecutionState.Starting, m.State);
209 m.Stop();
210 await m.Completion;
211 Assert.Equal(ExecutionState.Stopped, m.State);
212 Assert.True(start.IsCompleted);
213 Assert.True(start.IsCanceled);
214 }
215 }
216
217 [Fact]
218 public async Task AwaitWorker() {
219 using (var m = new MockPollComponent(true)) {
220 var worker = Safe.CreateTask();
221
222 m.PollWorker = (ct) => worker;
223
224 m.Start(CancellationToken.None);
225 await m.Completion;
226
227 Assert.Equal(ExecutionState.Running, m.State);
228
24 m.Stop(CancellationToken.None);
229 m.Stop(CancellationToken.None);
230 Assert.Equal(ExecutionState.Stopping, m.State);
231 worker.Start();
232 await m.Completion;
233 Assert.Equal(ExecutionState.Stopped, m.State);
234 }
235 }
236
237 [Fact]
238 public async Task CancelWorker() {
239 using (var m = new MockPollComponent(true)) {
240 var worker = Safe.CreateTask();
241
242 var started = Safe.CreateTask();
243
244 m.PollWorker = async (ct) => {
245 started.Start();
246 await worker;
247 ct.ThrowIfCancellationRequested();
248 };
249
250 m.Start(CancellationToken.None);
251 await m.Completion;
252 await started; // await for the poll worker to start
253
254 Assert.Equal(ExecutionState.Running, m.State);
255
256 m.Stop(CancellationToken.None);
257 Assert.Equal(ExecutionState.Stopping, m.State);
258 worker.Start();
25 await m.Completion;
259 await m.Completion;
26 Assert.Equal(ExecutionState.Stopped, m.State);
260 Assert.Equal(ExecutionState.Stopped, m.State);
27 }
261 }
28 }
262 }
29 }
263 }
30 }
264 }
@@ -1,28 +1,30
1 using System;
1 using System;
2 using System.Threading;
2
3
3 namespace Implab.Components {
4 namespace Implab.Components {
4 /// <summary>
5 /// <summary>
5 /// Initializable components are created and initialized in two steps, first we have create the component,
6 /// Initializable components are created and initialized in two steps, first we have create the component,
6 /// then we have to complete it's creation by calling an <see cref="Initialize()"/> method. All parameters needed
7 /// then we have to complete it's creation by calling an <see cref="Initialize()"/> method. All parameters needed
7 /// to complete the initialization must be passed before the calling <see cref="Initialize()"/>
8 /// to complete the initialization must be passed before the calling <see cref="Initialize()"/>
8 /// </summary>
9 /// </summary>
9 public interface IInitializable {
10 public interface IInitializable {
10 /// <summary>
11 /// <summary>
11 /// Completes initialization.
12 /// Completes initialization.
12 /// </summary>
13 /// </summary>
13 /// <remarks>
14 /// <remarks>
14 /// <para>
15 /// <para>
15 /// Normally virtual methods shouldn't be called from the constructor, due to the incomplete object state, but
16 /// Normally virtual methods shouldn't be called from the constructor, due to the incomplete object state, but
16 /// they can be called from this method. This method is also usefull when we constructing a complex grpah
17 /// they can be called from this method. This method is also usefull when we constructing a complex grpah
17 /// of components where cyclic references may take place.
18 /// of components where cyclic references may take place.
18 /// </para>
19 /// </para>
19 /// <para>
20 /// <para>
20 /// In asyncronous patterns <see cref="Initialize()"/> can be called
21 /// In asyncronous patterns <see cref="Initialize()"/> can be called
21 /// to start initialization and the <see cref="IRunnable.Completion"/>
22 /// to start initialization and the <see cref="IRunnable.Completion"/>
22 /// property can be used to track operation completion.
23 /// property can be used to track operation completion.
23 /// </para>
24 /// </para>
24 /// </remarks>
25 /// </remarks>
25 void Initialize();
26 void Initialize();
27 void Initialize(CancellationToken ct);
26 }
28 }
27 }
29 }
28
30
@@ -1,52 +1,54
1 using System;
1 using System;
2 using System.Threading;
2 using System.Threading;
3 using System.Threading.Tasks;
3 using System.Threading.Tasks;
4
4
5 namespace Implab.Components {
5 namespace Implab.Components {
6 /// <summary>
6 /// <summary>
7 /// Interface for the component which performs a long running task.
7 /// Interface for the component which performs a long running task.
8 /// </summary>
8 /// </summary>
9 /// <remarks>
9 /// <remarks>
10 /// The access to the runnable component should be sequential, the
10 /// The access to the runnable component should be sequential, the
11 /// componet should support asynchronous completion of the initiated
11 /// componet should support asynchronous completion of the initiated
12 /// operation but operations itself must be initiated sequentially.
12 /// operation but operations itself must be initiated sequentially.
13 /// </remarks>
13 /// </remarks>
14 public interface IRunnable {
14 public interface IRunnable {
15 /// <summary>
15 /// <summary>
16 /// Starts this instance
16 /// Starts this instance
17 /// </summary>
17 /// </summary>
18 /// <remarks>
18 /// <remarks>
19 /// This operation is cancellable and it's expected to move to
19 /// This operation is cancellable and it's expected to move to
20 /// the failed state or just ignore the cancellation request,
20 /// the failed state or just ignore the cancellation request,
21 /// </remarks>
21 /// </remarks>
22 void Start();
22 void Start(CancellationToken ct);
23 void Start(CancellationToken ct);
23
24
24 /// <summary>
25 /// <summary>
25 /// Stops this instance and releases all resources, after the
26 /// Stops this instance and releases all resources, after the
26 /// instance is stopped it is moved to Disposed state and
27 /// instance is stopped it is moved to Disposed state and
27 /// can't be reused.
28 /// can't be reused.
28 /// </summary>
29 /// </summary>
29 /// <remarks>
30 /// <remarks>
30 /// If the componet was in the starting state the pending operation
31 /// If the componet was in the starting state the pending operation
31 /// will be requested to cancel. The stop operatin will be
32 /// will be requested to cancel. The stop operatin will be
32 /// performed only if the component in the running state.
33 /// performed only if the component in the running state.
33 /// </remarks>
34 /// </remarks>
35 void Stop();
34 void Stop(CancellationToken ct);
36 void Stop(CancellationToken ct);
35
37
36 /// <summary>
38 /// <summary>
37 /// Current state of the componenet, dynamically reflects the current state.
39 /// Current state of the componenet, dynamically reflects the current state.
38 /// </summary>
40 /// </summary>
39 ExecutionState State { get; }
41 ExecutionState State { get; }
40
42
41 /// <summary>
43 /// <summary>
42 /// Event to monitor the state of the component.
44 /// Event to monitor the state of the component.
43 /// </summary>
45 /// </summary>
44 event EventHandler<StateChangeEventArgs> StateChanged;
46 event EventHandler<StateChangeEventArgs> StateChanged;
45
47
46 /// <summary>
48 /// <summary>
47 /// The last error
49 /// The last error
48 /// </summary>
50 /// </summary>
49 Exception LastError { get; }
51 Exception LastError { get; }
50 }
52 }
51 }
53 }
52
54
@@ -1,93 +1,94
1 using System;
1 using System;
2 using System.Threading;
2 using System.Threading;
3 using System.Threading.Tasks;
3 using System.Threading.Tasks;
4
4
5 namespace Implab.Components {
5 namespace Implab.Components {
6 public abstract class PollingComponent : RunnableComponent {
6 public abstract class PollingComponent : RunnableComponent {
7
7
8 readonly Timer m_timer;
8 readonly Timer m_timer;
9
9
10 readonly CancellationTokenSource m_cancellation = new CancellationTokenSource();
10 readonly CancellationTokenSource m_cancellation = new CancellationTokenSource();
11
11
12 Task m_pending;
12 Task m_pending;
13 Task m_poll;
13 Task m_poll;
14
14
15 /// <summary>
15 /// <summary>
16 /// Poll interval in milliseconds.
16 /// Poll interval in milliseconds.
17 /// </summary>
17 /// </summary>
18 /// <returns></returns>
18 /// <returns></returns>
19 public int Interval { get; set; }
19 public int Interval { get; set; }
20
20
21 /// <summary>
21 /// <summary>
22 /// Delay to the first poll after start in milliseconds
22 /// Delay to the first poll after start in milliseconds
23 /// </summary>
23 /// </summary>
24 /// <returns></returns>
24 /// <returns></returns>
25 public int Delay { get; set; }
25 public int Delay { get; set; }
26
26
27 /// <summary>
27 /// <summary>
28 /// Indicates how to handle unhandled exceptions in <see cref="Poll()"/> method.
28 /// Indicates how to handle unhandled exceptions in <see cref="Poll()"/> method.
29 /// </summary>
29 /// </summary>
30 /// <returns></returns>
30 /// <returns></returns>
31 public bool FailOnError { get; set; }
31 public bool FailOnError { get; set; }
32
32
33 /// <summary>
33 /// <summary>
34 /// Event for the unhandled exceptions in <see cref="Poll()"/> method.
34 /// Event for the unhandled exceptions in <see cref="Poll()"/> method.
35 /// </summary>
35 /// </summary>
36 public event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
36 public event EventHandler<UnhandledExceptionEventArgs> UnhandledException;
37
37
38 protected PollingComponent(bool initialized) : base(initialized) {
38 protected PollingComponent(bool initialized) : base(initialized) {
39 m_timer = new Timer(OnTimer);
39 m_timer = new Timer(OnTimer);
40 }
40 }
41
41
42 protected override void RunInternal() {
42 protected override void RunInternal() {
43 ScheduleNextPoll(Delay);
43 ScheduleNextPoll(Delay);
44 }
44 }
45
45
46
46
47 protected override async Task StopInternalAsync(CancellationToken ct) {
47 protected override async Task StopInternalAsync(CancellationToken ct) {
48 // component in Stopping state, no new polls will be scheduled
48 // component in Stopping state, no new polls will be scheduled
49 m_cancellation.Cancel();
49 m_cancellation.Cancel();
50 try {
50 try {
51 // await for pending poll
51 // await for pending poll
52 await m_poll;
52 if (m_poll != null)
53 await m_poll;
53 } catch (OperationCanceledException) {
54 } catch (OperationCanceledException) {
54 // OK
55 // OK
55 }
56 }
56 }
57 }
57
58
58 protected abstract Task Poll(CancellationToken ct);
59 protected abstract Task Poll(CancellationToken ct);
59
60
60 void ScheduleNextPoll(int timeout) {
61 void ScheduleNextPoll(int timeout) {
61 lock (SynchronizationObject) {
62 lock (SynchronizationObject) {
62 if (State == ExecutionState.Running) {
63 if (State == ExecutionState.Running) {
63 m_pending = Safe.CreateTask(m_cancellation.Token);
64 m_pending = Safe.CreateTask(m_cancellation.Token);
64 m_poll = m_pending.Then(() => Poll(m_cancellation.Token));
65 m_poll = m_pending.Then(() => Poll(m_cancellation.Token));
65 m_timer.Change(timeout, Timeout.Infinite);
66 m_timer.Change(timeout, Timeout.Infinite);
66 }
67 }
67 }
68 }
68 }
69 }
69
70
70 async void OnTimer(object state) {
71 async void OnTimer(object state) {
71 try {
72 try {
72 m_pending.Start();
73 m_pending.Start();
73 await m_poll;
74 await m_poll;
74 ScheduleNextPoll(Interval);
75 ScheduleNextPoll(Interval);
75 } catch (Exception e) {
76 } catch (Exception e) {
76 UnhandledException.DispatchEvent(this, new UnhandledExceptionEventArgs(e, false));
77 UnhandledException.DispatchEvent(this, new UnhandledExceptionEventArgs(e, false));
77
78
78 if (FailOnError)
79 if (FailOnError)
79 Fail(e);
80 Fail(e);
80 else
81 else
81 ScheduleNextPoll(Interval);
82 ScheduleNextPoll(Interval);
82 }
83 }
83
84
84 }
85 }
85
86
86 protected override void Dispose(bool disposing) {
87 protected override void Dispose(bool disposing) {
87 if (disposing)
88 if (disposing)
88 Safe.Dispose(m_timer, m_cancellation);
89 Safe.Dispose(m_timer, m_cancellation);
89 base.Dispose(disposing);
90 base.Dispose(disposing);
90 }
91 }
91
92
92 }
93 }
93 } No newline at end of file
94 }
@@ -1,332 +1,350
1 using System;
1 using System;
2 using System.Diagnostics;
2 using System.Diagnostics;
3 using System.Threading;
3 using System.Threading;
4 using System.Threading.Tasks;
4 using System.Threading.Tasks;
5
5
6 namespace Implab.Components {
6 namespace Implab.Components {
7 /// <summary>
7 /// <summary>
8 /// Base class for implementing components which support start and stop operations,
8 /// Base class for implementing components which support start and stop operations,
9 /// such components may represent running services.
9 /// such components may represent running services.
10 /// </summary>
10 /// </summary>
11 /// <remarks>
11 /// <remarks>
12 /// This class provides a basic lifecycle from the creation to the
12 /// This class provides a basic lifecycle from the creation to the
13 /// termination of the component.
13 /// termination of the component.
14 /// </remarks>
14 /// </remarks>
15 public abstract class RunnableComponent : IAsyncComponent, IRunnable, IInitializable, IDisposable {
15 public abstract class RunnableComponent : IAsyncComponent, IRunnable, IInitializable, IDisposable {
16
16
17 /// <summary>
17 /// <summary>
18 /// This class bounds <see cref="CancellationTokenSource"/> lifetime to the task,
18 /// This class bounds <see cref="CancellationTokenSource"/> lifetime to the task,
19 /// when the task completes the associated token source will be disposed.
19 /// when the task completes the associated token source will be disposed.
20 /// </summary>
20 /// </summary>
21 class AsyncOperationDescriptor {
21 class AsyncOperationDescriptor {
22
22
23 public static AsyncOperationDescriptor None { get; } = new AsyncOperationDescriptor();
23 public static AsyncOperationDescriptor None { get; } = new AsyncOperationDescriptor();
24
24
25 readonly CancellationTokenSource m_cts;
25 readonly CancellationTokenSource m_cts;
26
26
27 bool m_done;
27 bool m_done;
28
28
29 public CancellationToken Token {
29 public CancellationToken Token {
30 get { return m_cts == null ? CancellationToken.None : m_cts.Token; }
30 get { return m_cts == null ? CancellationToken.None : m_cts.Token; }
31 }
31 }
32
32
33 public Task Task { get; private set; }
33 public Task Task { get; private set; }
34
34
35 private AsyncOperationDescriptor(Task task, CancellationTokenSource cts) {
35 private AsyncOperationDescriptor(Task task, CancellationTokenSource cts) {
36 m_cts = cts;
36 m_cts = cts;
37 Task = Chain(task);
37 Task = Chain(task);
38 }
38 }
39
39
40 private AsyncOperationDescriptor() {
40 private AsyncOperationDescriptor() {
41 Task = Task.CompletedTask;
41 Task = Task.CompletedTask;
42 }
42 }
43
43
44 public void Cancel() {
44 public void Cancel() {
45 if (m_cts != null) {
45 if (m_cts != null) {
46 lock (m_cts) {
46 lock (m_cts) {
47 if (!m_done)
47 if (!m_done)
48 m_cts.Cancel();
48 m_cts.Cancel();
49 }
49 }
50 }
50 }
51 }
51 }
52
52
53 void Done() {
53 void Done() {
54 if (m_cts != null) {
54 if (m_cts != null) {
55 lock (m_cts) {
55 lock (m_cts) {
56 m_done = true;
56 m_done = true;
57 m_cts.Dispose();
57 m_cts.Dispose();
58 }
58 }
59 } else {
59 } else {
60 m_done = true;
60 m_done = true;
61 }
61 }
62 }
62 }
63
63
64 async Task Chain(Task other) {
64 async Task Chain(Task other) {
65 try {
65 try {
66 await other;
66 await other;
67 } finally {
67 } finally {
68 Done();
68 Done();
69 }
69 }
70 }
70 }
71
71
72 public static AsyncOperationDescriptor Create(Func<CancellationToken, Task> factory, CancellationToken ct) {
72 public static AsyncOperationDescriptor Create(Func<CancellationToken, Task> factory, CancellationToken ct) {
73 var cts = ct.CanBeCanceled ?
73 var cts = ct.CanBeCanceled ?
74 CancellationTokenSource.CreateLinkedTokenSource(ct) :
74 CancellationTokenSource.CreateLinkedTokenSource(ct) :
75 new CancellationTokenSource();
75 new CancellationTokenSource();
76
76
77 return new AsyncOperationDescriptor(factory(cts.Token), cts);
77 return new AsyncOperationDescriptor(factory(cts.Token), cts);
78 }
78 }
79
79
80 }
80 }
81
81
82 // this lock is used to synchronize state flow of the component during
82 // this lock is used to synchronize state flow of the component during
83 // processing calls from a client and internal processes.
83 // processing calls from a client and internal processes.
84 readonly object m_lock = new object();
84 readonly object m_lock = new object();
85
85
86 // current operation cookie, used to check wheather a call to
86 // current operation cookie, used to check wheather a call to
87 // MoveSuccess/MoveFailed method belongs to the current
87 // MoveSuccess/MoveFailed method belongs to the current
88 // operation, if cookies didn't match ignore completion result.
88 // operation, if cookies didn't match ignore completion result.
89 object m_cookie;
89 object m_cookie;
90
90
91 // AsyncOperationDscriptor aggregates a task and it's cancellation token
91 // AsyncOperationDscriptor aggregates a task and it's cancellation token
92 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;
92 AsyncOperationDescriptor m_current = AsyncOperationDescriptor.None;
93
93
94 ExecutionState m_state;
94 ExecutionState m_state;
95
95
96 /// <summary>
96 /// <summary>
97 /// Объект синхронизации используется для обеспечения совместного доступа
97 /// Объект синхронизации используется для обеспечения совместного доступа
98 /// клиента компоненты и процессов, протекающих внутри компоненты, к общему
98 /// клиента компоненты и процессов, протекающих внутри компоненты, к общему
99 /// состоянию, т.е.true таким свойствам, как <see cref="State"/>,
99 /// состоянию, т.е.true таким свойствам, как <see cref="State"/>,
100 /// <see cref="LastError"/>. Обработчики события <see cref="StateChanged"/>
100 /// <see cref="LastError"/>. Обработчики события <see cref="StateChanged"/>
101 /// вызываются уже с установленной блокировкой, поэтому дополнительная
101 /// вызываются уже с установленной блокировкой, поэтому дополнительная
102 /// синхронизация не требуется.
102 /// синхронизация не требуется.
103 /// </summary>
103 /// </summary>
104 public object SynchronizationObject { get { return m_lock; } }
104 public object SynchronizationObject { get { return m_lock; } }
105
105
106 protected RunnableComponent(bool initialized) {
106 protected RunnableComponent(bool initialized) {
107 State = initialized ? ExecutionState.Ready : ExecutionState.Created;
107 State = initialized ? ExecutionState.Ready : ExecutionState.Created;
108 }
108 }
109
109
110 public Task Completion {
110 public Task Completion {
111 get { return m_current.Task; }
111 get { return m_current.Task; }
112 }
112 }
113
113
114 public ExecutionState State {
114 public ExecutionState State {
115 get { return m_state; }
115 get { return m_state; }
116 private set {
116 private set {
117 if (m_state != value) {
117 if (m_state != value) {
118 m_state = value;
118 m_state = value;
119 StateChanged.DispatchEvent(this, new StateChangeEventArgs {
119 StateChanged.DispatchEvent(this, new StateChangeEventArgs {
120 State = value,
120 State = value,
121 LastError = LastError
121 LastError = LastError
122 });
122 });
123 }
123 }
124 }
124 }
125 }
125 }
126
126
127 public Exception LastError { get; private set; }
127 public Exception LastError { get; private set; }
128
128
129 /// <summary>
129 /// <summary>
130 /// Событие изменения состояния компоненты.see Обработчики данного события
130 /// Событие изменения состояния компоненты.see Обработчики данного события
131 /// вызываются внутри блокировки <see cref="SynchronizationObject"/> и должны
131 /// вызываются внутри блокировки <see cref="SynchronizationObject"/> и должны
132 /// выполняться максимально быстро.
132 /// выполняться максимально быстро.
133 /// </summary>
133 /// </summary>
134 public event EventHandler<StateChangeEventArgs> StateChanged;
134 public event EventHandler<StateChangeEventArgs> StateChanged;
135
135
136 /// <summary>
136 /// <summary>
137 /// Releases all resources used by the current component regardless of its
137 /// Releases all resources used by the current component regardless of its
138 /// execution state.
138 /// execution state.
139 /// </summary>
139 /// </summary>
140 /// <remarks>
140 /// <remarks>
141 /// Calling to this method may result unexpedted results if the component
141 /// Calling to this method may result unexpedted results if the component
142 /// isn't in the stopped state. Call this method after the component is
142 /// isn't in the stopped state. Call this method after the component is
143 /// stopped if needed or if the component is in the failed state.
143 /// stopped if needed or if the component is in the failed state.
144 /// </remarks>
144 /// </remarks>
145 public void Dispose() {
145 public void Dispose() {
146 bool dispose = false;
146 bool dispose = false;
147 lock (SynchronizationObject) {
147 lock (SynchronizationObject) {
148 if (m_state != ExecutionState.Disposed) {
148 if (m_state != ExecutionState.Disposed) {
149 dispose = true;
149 dispose = true;
150 m_state = ExecutionState.Disposed;
150 m_state = ExecutionState.Disposed;
151 m_cookie = new object();
151 m_cookie = new object();
152 }
152 }
153 }
153 }
154 if (dispose) {
154 if (dispose) {
155 Dispose(true);
155 Dispose(true);
156 GC.SuppressFinalize(this);
156 GC.SuppressFinalize(this);
157 }
157 }
158 }
158 }
159
159
160 ~RunnableComponent() {
160 ~RunnableComponent() {
161 Dispose(false);
161 Dispose(false);
162 }
162 }
163
163
164 /// <summary>
164 /// <summary>
165 /// Releases all resources used by the current component regardless of its
165 /// Releases all resources used by the current component regardless of its
166 /// execution state.
166 /// execution state.
167 /// </summary>
167 /// </summary>
168 /// <param name="disposing">Indicates that the component is disposed
168 /// <param name="disposing">Indicates that the component is disposed
169 /// during a normal disposing or during GC.</param>
169 /// during a normal disposing or during GC.</param>
170 protected virtual void Dispose(bool disposing) {
170 protected virtual void Dispose(bool disposing) {
171 }
171 }
172
172
173 public void Initialize() {
173 public void Initialize() {
174 Initialize(CancellationToken.None);
175 }
176
177 public void Initialize(CancellationToken ct) {
174 var cookie = new object();
178 var cookie = new object();
175 if (MoveInitialize(cookie))
179 if (MoveInitialize(cookie))
176 Safe.NoWait(ScheduleTask(InitializeInternalAsync, CancellationToken.None, cookie));
180 Safe.NoWait(ScheduleTask(InitializeInternalAsync, ct, cookie));
177 else
181 else
178 throw new InvalidOperationException();
182 throw new InvalidOperationException();
179 }
183 }
180
184
181 /// <summary>
185 /// <summary>
182 /// This method is used for initialization during a component creation.
186 /// This method is used for initialization during a component creation.
183 /// </summary>
187 /// </summary>
184 /// <param name="ct">A cancellation token for this operation</param>
188 /// <param name="ct">A cancellation token for this operation</param>
185 /// <remarks>
189 /// <remarks>
186 /// This method should be used for short and mostly syncronous operations,
190 /// This method should be used for short and mostly syncronous operations,
187 /// other operations which require time to run shoud be placed in
191 /// other operations which require time to run shoud be placed in
188 /// <see cref="StartInternalAsync(CancellationToken)"/> method.
192 /// <see cref="StartInternalAsync(CancellationToken)"/> method.
189 /// </remarks>
193 /// </remarks>
190 protected virtual Task InitializeInternalAsync(CancellationToken ct) {
194 protected virtual Task InitializeInternalAsync(CancellationToken ct) {
191 return Task.CompletedTask;
195 return Task.CompletedTask;
192 }
196 }
193
197
198 public void Start() {
199 Start(CancellationToken.None);
200 }
201
194 public void Start(CancellationToken ct) {
202 public void Start(CancellationToken ct) {
195 var cookie = new object();
203 var cookie = new object();
196 if (MoveStart(cookie))
204 if (MoveStart(cookie))
197 Safe.NoWait(ScheduleStartAndRun(ct, cookie));
205 Safe.NoWait(ScheduleStartAndRun(ct, cookie));
198 else
206 else
199 throw new InvalidOperationException();
207 throw new InvalidOperationException();
200 }
208 }
201
209
202 async Task ScheduleStartAndRun(CancellationToken ct, object cookie) {
210 async Task ScheduleStartAndRun(CancellationToken ct, object cookie) {
203 try {
211 try {
204 await ScheduleTask(StartInternalAsync, ct, cookie);
212 await ScheduleTask(StartInternalAsync, ct, cookie);
205 RunInternal();
213 RunInternal();
206 } catch (Exception err) {
214 } catch (Exception err) {
207 Fail(err);
215 Fail(err);
208 }
216 }
209 }
217 }
210
218
211 protected virtual Task StartInternalAsync(CancellationToken ct) {
219 protected virtual Task StartInternalAsync(CancellationToken ct) {
212 return Task.CompletedTask;
220 return Task.CompletedTask;
213 }
221 }
214
222
215 /// <summary>
223 /// <summary>
216 /// This method is called after the component is enetered running state,
224 /// This method is called after the component is enetered running state,
217 /// use this method to
225 /// use this method to
218 /// </summary>
226 /// </summary>
219 protected virtual void RunInternal() {
227 protected virtual void RunInternal() {
220
228
221 }
229 }
222
230
231 public void Stop() {
232 Stop(CancellationToken.None);
233 }
234
223 public void Stop(CancellationToken ct) {
235 public void Stop(CancellationToken ct) {
224 var cookie = new object();
236 var cookie = new object();
225 if (MoveStop(cookie))
237 if (MoveStop(cookie))
226 Safe.NoWait(ScheduleTask(StopAsync, ct, cookie));
238 Safe.NoWait(ScheduleTask(StopAsync, ct, cookie));
227 else
239 else
228 throw new InvalidOperationException();
240 throw new InvalidOperationException();
229 }
241 }
230
242
231 async Task StopAsync(CancellationToken ct) {
243 async Task StopAsync(CancellationToken ct) {
232 m_current.Cancel();
244 m_current.Cancel();
233 await Completion;
245
246 try {
247 await Completion;
248 } catch(OperationCanceledException) {
249 // OK
250 }
234
251
235 ct.ThrowIfCancellationRequested();
252 ct.ThrowIfCancellationRequested();
236
253
237 await StopInternalAsync(ct);
254 await StopInternalAsync(ct);
238 }
255 }
239
256
240 protected virtual Task StopInternalAsync(CancellationToken ct) {
257 protected virtual Task StopInternalAsync(CancellationToken ct) {
241 return Task.CompletedTask;
258 return Task.CompletedTask;
242 }
259 }
243
260
244 protected void Fail(Exception err) {
261 protected void Fail(Exception err) {
245 lock(m_lock) {
262 lock(m_lock) {
246 if (m_state != ExecutionState.Running)
263 if (m_state != ExecutionState.Running)
247 return;
264 return;
248 m_cookie = new object();
265 m_cookie = new object();
249 LastError = err;
266 LastError = err;
250 State = ExecutionState.Failed;
267 State = ExecutionState.Failed;
251 }
268 }
252 }
269 }
253
270
254
271
255 #region state management
272 #region state management
256
273
257 bool MoveInitialize(object cookie) {
274 bool MoveInitialize(object cookie) {
258 lock (m_lock) {
275 lock (m_lock) {
259 if (State != ExecutionState.Created)
276 if (State != ExecutionState.Created)
260 return false;
277 return false;
261 State = ExecutionState.Initializing;
278 State = ExecutionState.Initializing;
262 m_cookie = cookie;
279 m_cookie = cookie;
263 return true;
280 return true;
264 }
281 }
265 }
282 }
266
283
267 bool MoveStart(object cookie) {
284 bool MoveStart(object cookie) {
268 lock (m_lock) {
285 lock (m_lock) {
269 if (State != ExecutionState.Ready)
286 if (State != ExecutionState.Ready)
270 return false;
287 return false;
271 State = ExecutionState.Starting;
288 State = ExecutionState.Starting;
272 m_cookie = cookie;
289 m_cookie = cookie;
273 return true;
290 return true;
274 }
291 }
275 }
292 }
276
293
277 bool MoveStop(object cookie) {
294 bool MoveStop(object cookie) {
278 lock (m_lock) {
295 lock (m_lock) {
279 if (State != ExecutionState.Starting && State != ExecutionState.Running)
296 if (State != ExecutionState.Starting && State != ExecutionState.Running)
280 return false;
297 return false;
281 State = ExecutionState.Stopping;
298 State = ExecutionState.Stopping;
282 m_cookie = cookie;
299 m_cookie = cookie;
283 return true;
300 return true;
284 }
301 }
285 }
302 }
286
303
287 void MoveSuccess(object cookie) {
304 void MoveSuccess(object cookie) {
288 lock (m_lock) {
305 lock (m_lock) {
289 if (m_cookie != cookie)
306 if (m_cookie != cookie)
290 return;
307 return;
291 switch (State) {
308 switch (State) {
292 case ExecutionState.Initializing:
309 case ExecutionState.Initializing:
293 State = ExecutionState.Ready;
310 State = ExecutionState.Ready;
294 break;
311 break;
295 case ExecutionState.Starting:
312 case ExecutionState.Starting:
296 State = ExecutionState.Running;
313 State = ExecutionState.Running;
297 break;
314 break;
298 case ExecutionState.Stopping:
315 case ExecutionState.Stopping:
299 State = ExecutionState.Stopped;
316 State = ExecutionState.Stopped;
300 break;
317 break;
301 }
318 }
302 }
319 }
303 }
320 }
304
321
305 void MoveFailed(Exception err, object cookie) {
322 bool MoveFailed(Exception err, object cookie) {
306 lock (m_lock) {
323 lock (m_lock) {
307 if (m_cookie != cookie)
324 if (m_cookie != cookie)
308 return;
325 return false;
309 LastError = err;
326 LastError = err;
310 State = ExecutionState.Failed;
327 State = ExecutionState.Failed;
328 return true;
311 }
329 }
312 }
330 }
313
331
314 Task ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
332 Task ScheduleTask(Func<CancellationToken, Task> next, CancellationToken ct, object cookie) {
315
333
316 var op = AsyncOperationDescriptor.Create(async (x) => {
334 var op = AsyncOperationDescriptor.Create(async (x) => {
317 try {
335 try {
318 await next(x);
336 await next(x);
319 MoveSuccess(cookie);
337 MoveSuccess(cookie);
320 } catch (Exception e) {
338 } catch (Exception e) {
321 MoveFailed(e, cookie);
339 MoveFailed(e, cookie);
322 throw;
340 throw;
323 }
341 }
324 }, ct);
342 }, ct);
325
343
326 m_current = op;
344 m_current = op;
327 return op.Task;
345 return op.Task;
328 }
346 }
329
347
330 #endregion
348 #endregion
331 }
349 }
332 } No newline at end of file
350 }
@@ -1,22 +1,22
1 <Project Sdk="Microsoft.NET.Sdk">
1 <Project Sdk="Microsoft.NET.Sdk">
2
2
3 <PropertyGroup>
3 <PropertyGroup>
4 <Authors>Sergey Smirnov</Authors>
4 <Authors>Sergey Smirnov</Authors>
5 <Title>Implab library</Title>
5 <Title>Implab library</Title>
6 <Description>Provides some helper clesses like XML serialization helpers, JSON XML reader,
6 <Description>Provides some helper clesses like XML serialization helpers, JSON XML reader,
7 JSON pull-parser, ECMA-style promises, lightweight synchonization routines Signal
7 JSON pull-parser, ECMA-style promises, lightweight synchonization routines Signal
8 and SharedLock, Trace helpers on top of System.Diagnostics, ObjectPool etc.
8 and SharedLock, Trace helpers on top of System.Diagnostics, ObjectPool etc.
9 </Description>
9 </Description>
10 <Copyright>2012-2018 Sergey Smirnov</Copyright>
10 <Copyright>2012-2018 Sergey Smirnov</Copyright>
11 <Version>3.0.6</Version>
11 <Version>3.0.8</Version>
12 <PackageLicenseUrl>https://opensource.org/licenses/BSD-2-Clause</PackageLicenseUrl>
12 <PackageLicenseUrl>https://hg.implab.org/pub/ImplabNet/file/tip/Implab/license.txt</PackageLicenseUrl>
13 <PackageProjectUrl>https://implab.org</PackageProjectUrl>
13 <PackageProjectUrl>https://implab.org</PackageProjectUrl>
14 <RepositoryUrl>https://hg.implab.org/pub/ImplabNet/</RepositoryUrl>
14 <RepositoryUrl>https://hg.implab.org/pub/ImplabNet/</RepositoryUrl>
15 <RepositoryType>mercurial</RepositoryType>
15 <RepositoryType>mercurial</RepositoryType>
16 <PackageTags>IMPLAB;Json pull-parser;Json Xml;async;diagnostics;serialization;</PackageTags>
16 <PackageTags>IMPLAB;Json pull-parser;Json Xml;async;diagnostics;serialization;</PackageTags>
17 <TargetFrameworks>netstandard2.0;net46</TargetFrameworks>
17 <TargetFrameworks>netstandard2.0;net46</TargetFrameworks>
18 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net46' and '$(OSTYPE)'=='linux'">/usr/lib/mono/4.5/</FrameworkPathOverride>
18 <FrameworkPathOverride Condition="'$(TargetFramework)'=='net46' and '$(OSTYPE)'=='linux'">/usr/lib/mono/4.5/</FrameworkPathOverride>
19 <DefineConstants Condition="'$(TargetFramework)'=='net46'">NETFX_TRACE_BUG;$(DefineConstants)</DefineConstants>
19 <DefineConstants Condition="'$(TargetFramework)'=='net46'">NETFX_TRACE_BUG;$(DefineConstants)</DefineConstants>
20 </PropertyGroup>
20 </PropertyGroup>
21
21
22 </Project>
22 </Project>
1 NO CONTENT: file was removed
NO CONTENT: file was removed
1 NO CONTENT: file was removed
NO CONTENT: file was removed
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