LCOV - code coverage report

Current view
top level - src/worker - worker.dart
Test
lcov.info
Date
2026-11-07
Legend
Lines
hit
not hit
Branches
taken
not taken
# not executed
HitTotalCoverage
Lines759380.6%
Functions00-
Branches00-
Each row represents a line of source code
LineBranchHitsSource code
1import 'dart:async';
2
3import 'package:cancelation_token/cancelation_token.dart';
4import 'package:logger/web.dart';
5import 'package:using/using.dart';
6
7import '../_impl/xplat/_forward_completer.dart';
8import '../_impl/xplat/_forward_stream_controller.dart';
9import '../channel.dart';
10import '../exceptions/exception_manager.dart';
11import '../exceptions/squadron_exception.dart';
12import '../exceptions/task_terminated_exception.dart';
13import '../exceptions/worker_exception.dart';
14import '../invoker.dart';
15import '../iworker.dart';
16import '../stats/worker_stat.dart';
17import '../tokens/_squadron_cancelation_token.dart';
18import '../typedefs.dart';
19import '../worker/worker_request.dart';
20import '../worker_service.dart';
21
22part 'worker.stats.dart';
23
24/// Base worker class.
25///
26/// This base class takes care of creating the [Channel] and firing up the
27/// worker. Typically, derived classes should add proxy methods sending
28/// [WorkerRequest]s to the worker.
29abstract class Worker
30 with Releasable
31 implements WorkerService, IWorker, Invoker {
32 /// Creates a [Worker] with the specified entrypoint.
331 Worker(this._entryPoint,
34 {PlatformThreadHook? threadHook, ExceptionManager? exceptionManager})
35 : _threadHook = threadHook,
361 exceptionManager = exceptionManager ?? ExceptionManager() {
372 _stats = _Stats(this);
38 }
39
401 @override
41 void release() {
421 stop();
431 super.release();
44 }
45
46 /// The [Worker]'s entry point; typically, a top-level function in native
47 /// world or the Uri to a JavaScript file in browser world.
48 final EntryPoint _entryPoint;
49
50 @override
51 Logger? channelLogger;
52
53 @override
54 final ExceptionManager exceptionManager;
55
56 final PlatformThreadHook? _threadHook;
57
58 /// The [Worker]'s start arguments.
59 List? getStartArgs();
60
61 late final _Stats _stats;
62
633 bool get isStopped => _stats.isStopped;
64
65 /// [Worker] statistics.
663 WorkerStat getStats() => _stats.snapshot;
67
68 /// Returns true if the [Worker] is connected i.e., it has a valid [Channel].
69 /// Returns false otherwise.
702 bool get isConnected => _channel != null;
71
72 /// Shared [Channel] that can be used to communicate with the worker.
733 Channel? getSharedChannel() => _channel?.share();
74
75 Channel? _channel;
76 Future<Channel>? _openChannel;
77
78 /// Sends a workload to the worker.
791 @override
80 Future<dynamic> send(
81 int command, {
82 List args = const [],
83 CancelationToken? token,
84 bool inspectRequest = false,
85 bool inspectResponse = false,
86 }) {
870 token?.throwIfCanceled();
881 final channel = _channel;
89 return (channel != null && token == null)
901 ? _sendUncancelable(channel, command,
91 args: args,
92 inspectRequest: inspectRequest,
93 inspectResponse: inspectResponse)
941 : _sendAsync(command,
95 args: args,
96 token: token,
97 inspectRequest: inspectRequest,
98 inspectResponse: inspectResponse);
99 }
100
1011 Future<dynamic> _sendAsync(
102 int command, {
103 List args = const [],
104 CancelationToken? token,
105 bool inspectRequest = false,
106 bool inspectResponse = false,
107 }) async {
108 // start the worker and get the channel
1091 final channel = await start();
110
111 if (token == null) {
1121 return _sendUncancelable(channel, command,
113 args: args,
114 inspectRequest: inspectRequest,
115 inspectResponse: inspectResponse);
116 }
117
1180 final completer = ForwardCompleter();
119
1200 final squadronToken = token.wrap();
1210 squadronToken.onCanceled.then((ex) {
1220 channel.cancelToken(squadronToken);
1230 completer.failure(SquadronException.from(ex, null, command));
124 });
125
1260 _stats.beginWork();
1270 completer.future.whenComplete(_stats.endWork).ignore();
128
129 try {
1300 final res = await channel.sendRequest(
131 command,
132 args,
133 token: squadronToken,
134 inspectRequest: inspectRequest,
135 inspectResponse: inspectResponse,
136 );
1370 completer.success(res);
138 } catch (ex, st) {
1390 _stats.failed();
1400 completer.failure(SquadronException.from(ex, st, command));
141 }
142
1430 return completer.future;
144 }
145
1461 Future<dynamic> _sendUncancelable(
147 Channel channel,
148 int command, {
149 List args = const [],
150 bool inspectRequest = false,
151 bool inspectResponse = false,
152 }) {
1532 _stats.beginWork();
154 try {
155 return channel
1561 .sendRequest(
157 command,
158 args,
159 inspectRequest: inspectRequest,
160 inspectResponse: inspectResponse,
161 )
1622 .catchError((ex, st) {
1632 _stats.failed();
1641 throw SquadronException.from(ex, st, command);
1653 }).whenComplete(_stats.endWork);
166 } catch (ex, st) {
1670 _stats.failed();
1680 _stats.endWork();
1690 throw SquadronException.from(ex, st, command);
170 }
171 }
172
173 /// Sends a streaming workload to the worker.
1741 @override
175 Stream<dynamic> stream(
176 int command, {
177 List args = const [],
178 CancelationToken? token,
179 bool inspectRequest = false,
180 bool inspectResponse = false,
181 }) {
1821 final squadronToken = token?.wrap();
183
184 late final ForwardStreamController controller;
185
1863 squadronToken?.onCanceled.then((ex) {
1871 if (!controller.isClosed) {
1882 controller.subscription?.cancel();
1892 controller.safeAddError(SquadronException.from(ex, null, command));
1901 controller.close();
191 }
1922 _channel?.cancelToken(squadronToken);
193 });
194
1952 controller = ForwardStreamController(onListen: () async {
196 try {
1971 if (controller.isClosed) return;
1981 squadronToken?.throwIfCanceled();
1992 final channel = _channel ?? await start();
2001 if (controller.isClosed) return;
201
2022 _stats.beginWork();
2035 controller.done.whenComplete(_stats.endWork).ignore();
204
2051 controller.attachSubscription(channel
2061 .sendStreamingRequest(
207 command,
208 args,
209 token: squadronToken,
210 inspectRequest: inspectRequest,
211 inspectResponse: inspectResponse,
212 )
2131 .listen(
2141 controller.safeAdd,
2151 onError: (ex, st) => controller
2162 .safeAddError(SquadronException.from(ex, st, command)),
2171 onDone: controller.close,
218 cancelOnError: false,
219 ));
220 } catch (ex, st) {
2212 _stats.failed();
2221 controller.subscription?.cancel();
2232 controller.safeAddError(SquadronException.from(ex, st, command));
2241 controller.close();
225 }
226 });
227
2281 return controller.stream;
229 }
230
231 /// Creates a [Channel] and starts the worker using the [_entryPoint].
2321 @override
233 Future<Channel> start() {
2342 if (_stats.isStopped) {
2351 throw WorkerException('Invalid state: worker is stopped');
236 }
237
2381 if (_channel != null) {
2390 return Future.value(_channel!);
240 }
241
2422 return _openChannel ??= Channel.open(
2431 exceptionManager,
2441 channelLogger,
2451 _entryPoint,
2461 getStartArgs() ?? const [],
2471 _threadHook,
2482 ).then((channel) {
2491 _channel = channel;
2502 _stats.start();
251 return channel;
252 });
253 }
254
255 /// Stops this worker.
2561 @override
257 void stop() {
2582 if (!_stats.isStopped) {
2592 channelLogger?.d('Stop worker');
2602 _stats.stop();
2611 _openChannel = null;
2622 _channel?.close();
2631 _channel = null;
264 }
265 }
266
267 /// Terminates this worker.
2681 @override
269 void terminate([TaskTerminatedException? ex]) {
270 // terminate channel and stop worker
2711 ex ??= TaskTerminatedException('Worker has been terminated');
2722 _channel?.terminate(ex);
2731 stop();
274 }
275
276 /// Workers do not need an [operations] map.
2770 @override
278 OperationsMap get operations => WorkerService.noOperations;
279}
Choose Features