1 | | | import 'dart:async'; |
2 | | |
|
3 | | | import 'perf_counter.dart'; |
4 | | | import 'cancellation_token.dart'; |
5 | | | import 'squadron_error.dart'; |
6 | | | import 'squadron_exception.dart'; |
7 | | | import 'worker.dart'; |
8 | | | import 'worker_exception.dart'; |
9 | | | import 'worker_pool.dart'; |
10 | | | import 'worker_service.dart'; |
11 | | |
|
12 | | | /// Base worker task class |
13 | | | abstract class Task<T> { |
14 | | | /// Flag indicating whether the task is actually being executed. |
15 | | | bool get isRunning; |
16 | | |
|
17 | | | /// Flag indicating whether the task has completed. |
18 | | | bool get isFinished; |
19 | | |
|
20 | | | /// Flag indicating whether the task has been cancelled. |
21 | | | bool get isCancelled; |
22 | | |
|
23 | | | /// Duration between the moment the task was posted, and the moment it was assigned to a [Worker]. |
24 | | | Duration get waitTime; |
25 | | |
|
26 | | | /// Duration between the moment the task was assigned to a [Worker], and the moment it finished executing. |
27 | | | Duration get runningTime; |
28 | | |
|
29 | | | /// Cancels the task. If the task is still pending, cancellation is effective immediately with a |
30 | | | /// [CancelledException]. For a running [ValueTask], cancellation is ignored and the task's [ValueTask.value] |
31 | | | /// will eventually complete. For a running [StreamTask], cancellation will be effective after receiving the |
32 | | | /// next value and the task's [StreamTask.stream] will be closed. It should be noted that cancellation of |
33 | | | /// running tasks will not be notified to platform workers. To give running tasks a chance to get notified |
34 | | | /// of cancellation, a [CancellationToken] should be passed to the tasks at the time they are created. |
35 | | | void cancel([String? message]); |
36 | | | } |
37 | | |
|
38 | | | /// Class representing a [Task] returning a single value. |
39 | | | abstract class ValueTask<T> extends Task<T> { |
40 | | | /// The task's value provided as a [Future]. |
41 | | | Future<T> get value; |
42 | | | } |
43 | | |
|
44 | | | /// Class representing a [Task] returning a stream of values. |
45 | | | abstract class StreamTask<T> extends Task<T> { |
46 | | | /// The task's stream. |
47 | | | Stream<T> get stream; |
48 | | | } |
49 | | |
|
50 | | | /// [WorkerTask] registered in the [WorkerPool]. |
51 | | | class WorkerTask<T, W extends Worker> implements ValueTask<T>, StreamTask<T> { |
52 | | | /// Creates a new [ValueTask]. |
53 | | 2 | WorkerTask.value(this._computer, this._counter) |
54 | | 1 | : assert(_computer != null), |
55 | | 1 | _completer = Completer<T>(), |
56 | | | _producer = null, |
57 | | | _streamer = null { |
58 | | 2 | _submitted = _timeStamp(); |
59 | | | } |
60 | | |
|
61 | | | /// Creates a new [StreamTask]. |
62 | | 2 | WorkerTask.stream(this._producer, this._counter) |
63 | | 1 | : assert(_producer != null), |
64 | | 2 | _streamer = StreamController<T>(), |
65 | | | _computer = null, |
66 | | | _completer = null { |
67 | | 2 | _submitted = _timeStamp(); |
68 | | | } |
69 | | |
|
70 | | 3 | static int _timeStamp() => DateTime.now().microsecondsSinceEpoch; |
71 | | |
|
72 | | 1 | late final int _submitted; |
73 | | |
|
74 | | 1 | @override |
75 | | 1 | bool get isRunning => |
76 | | 3 | _executed != null && _finished == null && _cancelled == null; |
77 | | | int? _executed; |
78 | | |
|
79 | | 1 | @override |
80 | | 1 | bool get isFinished => |
81 | | 4 | _executed != null && _finished != null && _cancelled == null; |
82 | | | int? _finished; |
83 | | |
|
84 | | 1 | @override |
85 | | 2 | bool get isCancelled => _cancelled != null; |
86 | | | int? _cancelled; |
87 | | |
|
88 | | 0 | @override |
89 | | 0 | Duration get waitTime => Duration( |
90 | | 0 | microseconds: (_executed ?? _cancelled ?? _timeStamp()) - _submitted); |
91 | | |
|
92 | | 0 | @override |
93 | | 0 | Duration get runningTime => _executed == null |
94 | | | ? Duration.zero |
95 | | 0 | : Duration( |
96 | | | microseconds: |
97 | | 0 | (_cancelled ?? _finished ?? _timeStamp()) - _executed!); |
98 | | |
|
99 | | 2 | void _completeWithError(Exception exception) { |
100 | | 3 | if (!_completer!.isCompleted) { |
101 | | 3 | _completer!.completeError(exception); |
102 | | | } |
103 | | 1 | } |
104 | | |
|
105 | | 1 | void _completeWithResult(dynamic data) { |
106 | | 3 | if (!_completer!.isCompleted) { |
107 | | 3 | _completer!.complete(data); |
108 | | | } |
109 | | | } |
110 | | |
|
111 | | 2 | void _close([Exception? exception]) { |
112 | | 3 | if (!_streamer!.isClosed) { |
113 | | 1 | if (exception != null) { |
114 | | 3 | _streamer!.addError(exception); |
115 | | | } |
116 | | 3 | _streamer!.close(); |
117 | | | } |
118 | | 1 | } |
119 | | |
|
120 | | 1 | @override |
121 | | 1 | void cancel([String? message]) { |
122 | | 2 | if (_cancelled == null) { |
123 | | 3 | _cancelled = _timeStamp(); |
124 | | 3 | if (_completer != null && _executed == null) { |
125 | | 5 | _wrapUp(() => _completeWithError(CancelledException(message: message)), |
126 | | | false); |
127 | | 2 | } else if (_streamer != null) { |
128 | | 5 | _wrapUp(() => _close(CancelledException(message: message)), false); |
129 | | | } |
130 | | | } |
131 | | 1 | } |
132 | | |
|
133 | | 2 | void _wrapUp(SquadronCallback wrapper, bool success) async { |
134 | | 2 | if (_finished == null) { |
135 | | 2 | _finished = _timeStamp(); |
136 | | 5 | _counter?.update(_finished! - _executed!, success); |
137 | | 2 | wrapper(); |
138 | | | } |
139 | | 1 | } |
140 | | |
|
141 | | 2 | Future _runFuture(W worker, Future<T> Function(W worker) computer, |
142 | | | Completer completer) async { |
143 | | 2 | if (completer.isCompleted) return; |
144 | | |
|
145 | | 1 | try { |
146 | | 1 | if (isCancelled) throw CancelledException(); |
147 | | 3 | final value = await computer(worker); |
148 | | 4 | _wrapUp(() => _completeWithResult(value), true); |
149 | | 1 | } catch (ex, st) { |
150 | | 2 | final wex = SquadronException.from(error: ex, stackTrace: st); |
151 | | 4 | _wrapUp(() => _completeWithError(wex), false); |
152 | | | } |
153 | | 1 | } |
154 | | |
|
155 | | 2 | Future _runStream(W worker, Stream<T> Function(W worker) producer, |
156 | | | StreamController streamer) async { |
157 | | 2 | if (streamer.isClosed) return; |
158 | | |
|
159 | | 1 | try { |
160 | | 1 | if (isCancelled) throw CancelledException(); |
161 | | 4 | await for (var value in producer(worker)) { |
162 | | 1 | streamer.add(value); |
163 | | 1 | if (isCancelled) throw CancelledException(); |
164 | | | } |
165 | | 4 | _wrapUp(() => _close(), true); |
166 | | 1 | } catch (ex, st) { |
167 | | 2 | final wex = SquadronException.from(error: ex, stackTrace: st); |
168 | | 4 | _wrapUp(() => _close(wex), false); |
169 | | | } |
170 | | 1 | } |
171 | | |
|
172 | | 2 | Future run(W worker) { |
173 | | 3 | _executed = _timeStamp(); |
174 | | 3 | if (_computer != null && _completer != null) { |
175 | | 4 | return _runFuture(worker, _computer!, _completer!); |
176 | | 3 | } else if (_producer != null && _streamer != null) { |
177 | | 4 | return _runStream(worker, _producer!, _streamer!); |
178 | | | } else { |
179 | | 0 | throw newSquadronError('invalid worker task state'); |
180 | | | } |
181 | | 1 | } |
182 | | |
|
183 | | | final PerfCounter? _counter; |
184 | | |
|
185 | | | final Future<T> Function(W worker)? _computer; |
186 | | | final Completer<T>? _completer; |
187 | | |
|
188 | | 1 | @override |
189 | | 3 | Future<T> get value => _completer!.future; |
190 | | |
|
191 | | | final Stream<T> Function(W worker)? _producer; |
192 | | | final StreamController<T>? _streamer; |
193 | | |
|
194 | | 1 | @override |
195 | | 3 | Stream<T> get stream => _streamer!.stream; |
196 | | | } |