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