@agoric/swingset-vat 0.33.0-u18a.0 → 0.33.0-u19.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,120 +1,119 @@
1
1
  import { q } from '@endo/errors';
2
+ import { objectMap } from '@agoric/internal';
3
+ import { makeLimitedConsole } from '@agoric/internal/src/ses-utils.js';
4
+
5
+ /** @import {Callable} from '@agoric/internal'; */
6
+ /** @import {LimitedConsole} from '@agoric/internal/src/js-utils.js'; */
2
7
 
3
8
  const IDLE = 'idle';
4
9
  const STARTUP = 'startup';
5
10
  const DELIVERY = 'delivery';
6
11
 
7
- function makeCallbackRegistry(callbacks) {
8
- const todo = new Set(Object.keys(callbacks));
9
- return harden({
10
- /**
11
- * Robustly wrap a method with a callbacks[method] function, if defined. We
12
- * incur no runtime overhead if the given callback method isn't defined.
13
- *
14
- * @param {string} method wrap with callbacks[method]
15
- * @param {(...args: Array<unknown>) => unknown} impl the original
16
- * implementation of the method
17
- * @returns {(...args: Array<unknown>) => unknown} the wrapped method if the
18
- * callback is defined, or original method if not
19
- */
20
- registerCallback(method, impl) {
21
- todo.delete(method);
22
- const cb = callbacks[method];
23
- if (!cb) {
24
- // No registered callback, just use the implementation directly.
25
- // console.error('no registered callback for', method);
26
- return impl;
27
- }
12
+ const noopFinisher = harden(() => {});
13
+
14
+ /** @typedef {(...finishArgs: unknown[]) => unknown} AnyFinisher */
15
+ /** @typedef {Partial<Record<Exclude<keyof KernelSlog, 'write'>, (methodName: string, args: unknown[], finisher: AnyFinisher) => unknown>>} SlogWrappers */
16
+
17
+ /**
18
+ * Support asynchronous slog callbacks that are invoked at the start
19
+ * of an operation and return either a non-function result or a "finisher"
20
+ * function to be invoked upon operation completion.
21
+ * This maker accepts a collection of wrapper functions that receive the same
22
+ * arguments as the method they wrap, along with the result of that method
23
+ * (e.g., its finisher), and are expected to return a finisher of their own that
24
+ * will invoke that wrapped finisher.
25
+ *
26
+ * @template {Record<string, Callable>} Methods
27
+ * @param {SlogWrappers} slogCallbacks
28
+ * @param {string} unusedMsgPrefix prefix for warn-level logging about unused callbacks
29
+ * @param {Methods} methods to wrap
30
+ * @returns {Methods}
31
+ */
32
+ function addSlogCallbacks(slogCallbacks, unusedMsgPrefix, methods) {
33
+ const unused = new Set(Object.keys(slogCallbacks));
34
+ const wrappedMethods = /** @type {Methods} */ (
35
+ objectMap(methods, (impl, methodKey) => {
36
+ const methodName = /** @type {keyof typeof slogCallbacks} */ (methodKey);
37
+ unused.delete(methodName);
38
+ const wrapper = slogCallbacks[methodName];
28
39
 
29
- return (...args) => {
30
- // Invoke the implementation first.
31
- const ret = impl(...args);
40
+ // If there is no registered wrapper, return the implementation directly.
41
+ if (!wrapper) return impl;
42
+
43
+ const wrapped = (...args) => {
44
+ const maybeFinisher = /** @type {AnyFinisher} */ (impl(...args));
32
45
  try {
33
- // Allow the callback to observe the call synchronously, and affect
34
- // the finisher function, but not to throw an exception.
35
- const cbRet = cb(method, args, ret);
36
- if (typeof ret === 'function') {
37
- // We wrap the finisher in the callback's return value.
38
- return (...finishArgs) => {
39
- try {
40
- return cbRet(...finishArgs);
41
- } catch (e) {
42
- console.error(
43
- `failed to call registered ${method}.finish function:`,
44
- e,
45
- );
46
- }
47
- return ret(...args);
48
- };
49
- }
50
- // We just return the callback's return value.
51
- return cbRet;
46
+ // Allow the callback to observe the call synchronously, and replace
47
+ // the implementation's finisher function, but not to throw an exception.
48
+ const wrapperFinisher = wrapper(methodName, args, maybeFinisher);
49
+ if (typeof maybeFinisher !== 'function') return wrapperFinisher;
50
+
51
+ // We wrap the finisher in the callback's return value.
52
+ return (...finishArgs) => {
53
+ try {
54
+ return /** @type {AnyFinisher} */ (wrapperFinisher)(
55
+ ...finishArgs,
56
+ );
57
+ } catch (e) {
58
+ console.error(`${methodName} wrapper finisher failed:`, e);
59
+ return maybeFinisher(...finishArgs);
60
+ }
61
+ };
52
62
  } catch (e) {
53
- console.error('failed to call registered', method, 'callback:', e);
63
+ console.error(`${methodName} wrapper failed:`, e);
64
+ return maybeFinisher;
54
65
  }
55
- return ret;
56
66
  };
57
- },
58
- /**
59
- * Declare that all the methods have been registered.
60
- *
61
- * @param {string} errorUnusedMsg message to display if there are callback
62
- * names that don't correspond to a registration
63
- */
64
- doneRegistering(errorUnusedMsg = `Unrecognized callback names:`) {
65
- const cbNames = [...todo.keys()];
66
- if (!cbNames.length) {
67
- return;
68
- }
69
- console.warn(errorUnusedMsg, cbNames.map(q).sort().join(', '));
70
- },
71
- });
67
+ return /** @type {typeof impl} */ (/** @type {unknown} */ (wrapped));
68
+ })
69
+ );
70
+ if (unused.size) {
71
+ console.warn(unusedMsgPrefix, ...[...unused.keys()].sort().map(q));
72
+ }
73
+ return wrappedMethods;
72
74
  }
73
75
 
76
+ export const badConsole = makeLimitedConsole(level => () => {
77
+ throw Error(`unexpected use of badConsole.${level}`);
78
+ });
79
+ export const noopConsole = makeLimitedConsole(_level => () => {});
80
+
74
81
  /**
75
- * @param {*} slogCallbacks
76
- * @param {Pick<Console, 'debug'|'log'|'info'|'warn'|'error'>} dummyConsole
82
+ * @param {SlogWrappers} slogCallbacks
83
+ * @param {LimitedConsole} [dummyConsole]
77
84
  * @returns {KernelSlog}
78
85
  */
79
- export function makeDummySlogger(slogCallbacks, dummyConsole) {
80
- const { registerCallback: reg, doneRegistering } =
81
- makeCallbackRegistry(slogCallbacks);
82
- const dummySlogger = harden({
83
- provideVatSlogger: reg('provideVatSlogger', () =>
84
- harden({
85
- vatSlog: {
86
- delivery: () => () => 0,
87
- },
88
- }),
89
- ),
90
- vatConsole: reg('vatConsole', () => dummyConsole),
91
- startup: reg('startup', () => () => 0), // returns nop finish() function
92
- replayVatTranscript: reg('replayVatTranscript', () => () => 0),
93
- delivery: reg('delivery', () => () => 0),
94
- syscall: reg('syscall', () => () => 0),
95
- changeCList: reg('changeCList', () => () => 0),
96
- terminateVat: reg('terminateVat', () => () => 0),
97
- write: () => 0,
86
+ export function makeDummySlogger(slogCallbacks, dummyConsole = badConsole) {
87
+ const unusedWrapperPrefix =
88
+ 'Unused methods in makeDummySlogger slogCallbacks';
89
+ const wrappedMethods = addSlogCallbacks(slogCallbacks, unusedWrapperPrefix, {
90
+ provideVatSlogger: () =>
91
+ harden({ vatSlog: { delivery: () => noopFinisher } }),
92
+ vatConsole: () => dummyConsole,
93
+ startup: () => noopFinisher,
94
+ delivery: () => noopFinisher,
95
+ syscall: () => noopFinisher,
96
+ changeCList: () => noopFinisher,
97
+ terminateVat: () => noopFinisher,
98
98
  });
99
- doneRegistering(`Unrecognized makeDummySlogger slogCallbacks names:`);
100
- // @ts-expect-error xxx
101
- return dummySlogger;
99
+ return harden({ ...wrappedMethods, write: noopFinisher });
102
100
  }
103
101
 
104
102
  /**
105
- * @param {*} slogCallbacks
106
- * @param {*} writeObj
103
+ * @param {SlogWrappers} slogCallbacks
104
+ * @param {(obj: object) => void} [writeObj]
107
105
  * @returns {KernelSlog}
108
106
  */
109
107
  export function makeSlogger(slogCallbacks, writeObj) {
110
- const safeWrite = e => {
111
- try {
112
- writeObj(e);
113
- } catch (err) {
114
- console.error('WARNING: slogger write error', err);
115
- }
116
- };
117
- const write = writeObj ? safeWrite : () => 0;
108
+ const safeWrite = writeObj
109
+ ? obj => {
110
+ try {
111
+ writeObj(obj);
112
+ } catch (err) {
113
+ console.error('WARNING: slogger write error', err);
114
+ }
115
+ }
116
+ : () => {};
118
117
 
119
118
  const vatSlogs = new Map(); // vatID -> vatSlog
120
119
 
@@ -130,36 +129,33 @@ export function makeSlogger(slogCallbacks, writeObj) {
130
129
  console.error(
131
130
  `WARNING: slogger state confused: vat ${vatID} in ${state}, not ${exp}: ${msg}`,
132
131
  );
133
- write({ type: 'slogger-confused', vatID, state, exp, msg });
132
+ safeWrite({ type: 'slogger-confused', vatID, state, exp, msg });
134
133
  }
135
134
  }
136
135
 
137
136
  function vatConsole(sourcedConsole) {
138
- const vc = {};
139
- for (const level of ['debug', 'log', 'info', 'warn', 'error']) {
140
- vc[level] = (sourceTag, ...args) => {
141
- if (replay) {
142
- // Don't duplicate stale console output.
143
- return;
144
- }
145
- sourcedConsole[level](sourceTag, ...args);
146
- const when = { state, crankNum, vatID, deliveryNum };
147
- const source = sourceTag === 'ls' ? 'liveslots' : sourceTag;
148
- write({ type: 'console', source, ...when, level, args });
149
- };
150
- }
151
- return harden(vc);
137
+ return makeLimitedConsole(level => (source, ...args) => {
138
+ // Don't duplicate stale output.
139
+ if (replay) return;
140
+
141
+ // Write to the console, then to the slog.
142
+ sourcedConsole[level](source, ...args);
143
+ // TODO: Just use "liveslots" rather than "ls"?
144
+ if (source === 'ls') source = 'liveslots';
145
+ const when = { state, crankNum, vatID, deliveryNum };
146
+ safeWrite({ type: 'console', source, ...when, level, args });
147
+ });
152
148
  }
153
149
 
154
150
  function startup() {
155
151
  // provide a context for console calls during startup
156
152
  checkOldState(IDLE, 'did startup get called twice?');
157
153
  state = STARTUP;
158
- write({ type: 'vat-startup-start', vatID });
154
+ safeWrite({ type: 'vat-startup-start', vatID });
159
155
  function finish() {
160
156
  checkOldState(STARTUP, 'startup-finish called twice?');
161
157
  state = IDLE;
162
- write({ type: 'vat-startup-finish', vatID });
158
+ safeWrite({ type: 'vat-startup-finish', vatID });
163
159
  }
164
160
  return harden(finish);
165
161
  }
@@ -172,13 +168,13 @@ export function makeSlogger(slogCallbacks, writeObj) {
172
168
  deliveryNum = newDeliveryNum;
173
169
  replay = inReplay;
174
170
  const when = { crankNum, vatID, deliveryNum, replay };
175
- write({ type: 'deliver', ...when, kd, vd });
171
+ safeWrite({ type: 'deliver', ...when, kd, vd });
176
172
  syscallNum = 0;
177
173
 
178
174
  // dr: deliveryResult
179
175
  function finish(dr) {
180
176
  checkOldState(DELIVERY, 'delivery-finish called twice?');
181
- write({ type: 'deliver-result', ...when, dr });
177
+ safeWrite({ type: 'deliver-result', ...when, dr });
182
178
  state = IDLE;
183
179
  }
184
180
  return harden(finish);
@@ -188,24 +184,24 @@ export function makeSlogger(slogCallbacks, writeObj) {
188
184
  function syscall(ksc, vsc) {
189
185
  checkOldState(DELIVERY, 'syscall invoked outside of delivery');
190
186
  const when = { crankNum, vatID, deliveryNum, syscallNum, replay };
191
- write({ type: 'syscall', ...when, ksc, vsc });
187
+ safeWrite({ type: 'syscall', ...when, ksc, vsc });
192
188
  syscallNum += 1;
193
189
 
194
190
  // ksr: kernelSyscallResult, vsr: vatSyscallResult
195
191
  function finish(ksr, vsr) {
196
192
  checkOldState(DELIVERY, 'syscall finished after delivery?');
197
- write({ type: 'syscall-result', ...when, ksr, vsr });
193
+ safeWrite({ type: 'syscall-result', ...when, ksr, vsr });
198
194
  }
199
195
  return harden(finish);
200
196
  }
201
197
 
202
198
  // mode: 'import' | 'export' | 'drop'
203
199
  function changeCList(crank, mode, kobj, vobj) {
204
- write({ type: 'clist', crankNum: crank, mode, vatID, kobj, vobj });
200
+ safeWrite({ type: 'clist', crankNum: crank, mode, vatID, kobj, vobj });
205
201
  }
206
202
 
207
203
  function terminateVat(shouldReject, info) {
208
- write({ type: 'terminate', vatID, shouldReject, info });
204
+ safeWrite({ type: 'terminate', vatID, shouldReject, info });
209
205
  }
210
206
 
211
207
  return harden({
@@ -233,7 +229,7 @@ export function makeSlogger(slogCallbacks, writeObj) {
233
229
  }
234
230
  const vatSlog = makeVatSlog(vatID);
235
231
  vatSlogs.set(vatID, vatSlog);
236
- write({
232
+ safeWrite({
237
233
  type: 'create-vat',
238
234
  vatID,
239
235
  dynamic,
@@ -246,44 +242,21 @@ export function makeSlogger(slogCallbacks, writeObj) {
246
242
  return { vatSlog, starting: true };
247
243
  }
248
244
 
249
- function replayVatTranscript(vatID) {
250
- write({ type: 'replay-transcript-start', vatID });
251
- function finish() {
252
- write({ type: 'replay-transcript-finish', vatID });
253
- }
254
- return harden(finish);
255
- }
256
-
257
- // function annotateVat(vatID, data) {
258
- // write({ type: 'annotate-vat', vatID, data });
259
- // }
260
-
261
- const { registerCallback: reg, doneRegistering } =
262
- makeCallbackRegistry(slogCallbacks);
263
- const slogger = harden({
264
- provideVatSlogger: reg('provideVatSlogger', provideVatSlogger),
265
- vatConsole: reg('vatConsole', (vatID, ...args) =>
245
+ const unusedWrapperPrefix = 'Unused methods in makeSlogger slogCallbacks';
246
+ const wrappedMethods = addSlogCallbacks(slogCallbacks, unusedWrapperPrefix, {
247
+ provideVatSlogger,
248
+ vatConsole: (vatID, ...args) =>
266
249
  provideVatSlogger(vatID).vatSlog.vatConsole(...args),
267
- ),
268
- startup: reg('startup', (vatID, ...args) =>
250
+ startup: (vatID, ...args) =>
269
251
  provideVatSlogger(vatID).vatSlog.startup(...args),
270
- ),
271
- replayVatTranscript,
272
- delivery: reg('delivery', (vatID, ...args) =>
252
+ delivery: (vatID, ...args) =>
273
253
  provideVatSlogger(vatID).vatSlog.delivery(...args),
274
- ),
275
- syscall: reg('syscall', (vatID, ...args) =>
254
+ syscall: (vatID, ...args) =>
276
255
  provideVatSlogger(vatID).vatSlog.syscall(...args),
277
- ),
278
- changeCList: reg('changeCList', (vatID, ...args) =>
256
+ changeCList: (vatID, ...args) =>
279
257
  provideVatSlogger(vatID).vatSlog.changeCList(...args),
280
- ),
281
- terminateVat: reg('terminateVat', (vatID, ...args) =>
258
+ terminateVat: (vatID, ...args) =>
282
259
  provideVatSlogger(vatID).vatSlog.terminateVat(...args),
283
- ),
284
- write,
285
260
  });
286
- doneRegistering(`Unrecognized makeSlogger slogCallbacks names:`);
287
- // @ts-expect-error xxx
288
- return slogger;
261
+ return harden({ ...wrappedMethods, write: safeWrite });
289
262
  }
@@ -1,5 +1,7 @@
1
1
  import { Nat, isNat } from '@endo/nat';
2
2
  import { assert, Fail } from '@endo/errors';
3
+ import { naturalCompare } from '@agoric/internal/src/natural-sort.js';
4
+ import { makeDummySlogger, noopConsole } from '../slogger.js';
3
5
  import {
4
6
  initializeVatState,
5
7
  makeVatKeeper,
@@ -329,7 +331,7 @@ export const DEFAULT_GC_KREFS_PER_BOYD = 20;
329
331
  /**
330
332
  * @param {SwingStoreKernelStorage} kernelStorage
331
333
  * @param {number | 'uninitialized'} expectedVersion
332
- * @param {KernelSlog} [kernelSlog]
334
+ * @param {KernelSlog} [kernelSlog] optional only for expectedVersion 'uninitialized'
333
335
  */
334
336
  export default function makeKernelKeeper(
335
337
  kernelStorage,
@@ -355,10 +357,13 @@ export default function makeKernelKeeper(
355
357
  if (versionString) {
356
358
  throw Error(`kernel DB already initialized (v${versionString})`);
357
359
  }
360
+ kernelSlog ||= makeDummySlogger({}, noopConsole);
358
361
  } else if (expectedVersion !== version) {
359
362
  throw Error(
360
363
  `kernel DB is too old: has version v${version}, but expected v${expectedVersion}`,
361
364
  );
365
+ } else if (!kernelSlog) {
366
+ throw Error('kernelSlog is required for an already-initialized kernel DB');
362
367
  } else {
363
368
  // DB is up-to-date, so populate any caches we use
364
369
  terminatedVats = JSON.parse(getRequired('vats.terminated'));
@@ -1886,39 +1891,12 @@ export default function makeKernelKeeper(
1886
1891
  }
1887
1892
  }
1888
1893
 
1889
- function compareNumbers(a, b) {
1890
- return Number(a - b);
1891
- }
1892
-
1893
- function compareStrings(a, b) {
1894
- // natural-sort strings having a shared prefix followed by digits
1895
- // (e.g., 'ko42' and 'ko100')
1896
- const [_a, aPrefix, aDigits] = /^(\D+)(\d+)$/.exec(a) || [];
1897
- if (aPrefix) {
1898
- const [_b, bPrefix, bDigits] = /^(\D+)(\d+)$/.exec(b) || [];
1899
- if (bPrefix === aPrefix) {
1900
- return compareNumbers(aDigits, bDigits);
1901
- }
1902
- }
1903
-
1904
- // otherwise use the default string ordering
1905
- if (a > b) {
1906
- return 1;
1907
- }
1908
- if (a < b) {
1909
- return -1;
1910
- }
1911
- return 0;
1912
- }
1913
-
1894
+ // Perform an element-by-element natural sort.
1914
1895
  kernelTable.sort(
1915
1896
  (a, b) =>
1916
- compareStrings(a[0], b[0]) ||
1917
- compareStrings(a[1], b[1]) ||
1918
- compareNumbers(a[2], b[2]) ||
1919
- compareStrings(a[3], b[3]) ||
1920
- compareNumbers(a[4], b[4]) ||
1921
- compareNumbers(a[5], b[5]) ||
1897
+ naturalCompare(a[0], b[0]) ||
1898
+ naturalCompare(a[1], b[1]) ||
1899
+ naturalCompare(a[2], b[2]) ||
1922
1900
  0,
1923
1901
  );
1924
1902
 
@@ -1931,7 +1909,7 @@ export default function makeKernelKeeper(
1931
1909
  promises.push({ id: kpid, ...getKernelPromise(kpid) });
1932
1910
  }
1933
1911
  }
1934
- promises.sort((a, b) => compareStrings(a.id, b.id));
1912
+ promises.sort((a, b) => naturalCompare(a.id, b.id));
1935
1913
 
1936
1914
  const objects = [];
1937
1915
  const nextObjectID = Nat(BigInt(getRequired('ko.nextID')));
@@ -86,7 +86,7 @@ export function initializeVatState(
86
86
  /**
87
87
  * @typedef {object} VatKeeperPowers
88
88
  * @property {TranscriptStore} transcriptStore Accompanying transcript store, for the transcripts
89
- * @property {*} kernelSlog
89
+ * @property {KernelSlog} kernelSlog
90
90
  * @property {*} addKernelObject Kernel function to add a new object to the kernel's mapping tables.
91
91
  * @property {*} addKernelPromiseForVat Kernel function to add a new promise to the kernel's mapping tables.
92
92
  * @property {(kernelSlot: string) => boolean} kernelObjectExists
@@ -411,15 +411,13 @@ export function makeVatKeeper(
411
411
  // update any necessary refcounts consistently
412
412
  kvStore.set(kernelKey, buildReachableAndVatSlot(false, vatSlot));
413
413
  kvStore.set(vatKey, kernelSlot);
414
- if (kernelSlog) {
415
- kernelSlog.changeCList(
416
- vatID,
417
- getCrankNumber(),
418
- 'export',
419
- kernelSlot,
420
- vatSlot,
421
- );
422
- }
414
+ kernelSlog.changeCList(
415
+ vatID,
416
+ getCrankNumber(),
417
+ 'export',
418
+ kernelSlot,
419
+ vatSlot,
420
+ );
423
421
  kdebug(`Add mapping v->k ${kernelKey}<=>${vatKey}`);
424
422
  } else {
425
423
  // the vat didn't allocate it, and the kernel didn't allocate it
@@ -486,15 +484,13 @@ export function makeVatKeeper(
486
484
  incStat('clistEntries');
487
485
  kvStore.set(vatKey, kernelSlot);
488
486
  kvStore.set(kernelKey, buildReachableAndVatSlot(false, vatSlot));
489
- if (kernelSlog) {
490
- kernelSlog.changeCList(
491
- vatID,
492
- getCrankNumber(),
493
- 'import',
494
- kernelSlot,
495
- vatSlot,
496
- );
497
- }
487
+ kernelSlog.changeCList(
488
+ vatID,
489
+ getCrankNumber(),
490
+ 'import',
491
+ kernelSlot,
492
+ vatSlot,
493
+ );
498
494
  kdebug(`Add mapping k->v ${kernelKey}<=>${vatKey}`);
499
495
  }
500
496
 
@@ -537,15 +533,13 @@ export function makeVatKeeper(
537
533
  const vatKey = `${vatID}.c.${vatSlot}`;
538
534
  assert(kvStore.has(kernelKey));
539
535
  kdebug(`Delete mapping ${kernelKey}<=>${vatKey}`);
540
- if (kernelSlog) {
541
- kernelSlog.changeCList(
542
- vatID,
543
- getCrankNumber(),
544
- 'drop',
545
- kernelSlot,
546
- vatSlot,
547
- );
548
- }
536
+ kernelSlog.changeCList(
537
+ vatID,
538
+ getCrankNumber(),
539
+ 'drop',
540
+ kernelSlot,
541
+ vatSlot,
542
+ );
549
543
  const isExport = allocatedByVat;
550
544
  // We tolerate the object kref not being present in the kernel object
551
545
  // table, either because we're being called during the translation of
@@ -677,13 +671,7 @@ export function makeVatKeeper(
677
671
  restartWorker,
678
672
  );
679
673
 
680
- const {
681
- hash: snapshotID,
682
- uncompressedSize,
683
- dbSaveSeconds,
684
- compressedSize,
685
- compressSeconds,
686
- } = info;
674
+ const { hash: snapshotID } = info;
687
675
 
688
676
  // push a save-snapshot transcript entry
689
677
  addToTranscript(makeSaveSnapshotItem(snapshotID));
@@ -695,18 +683,6 @@ export function makeVatKeeper(
695
683
  // always starts with an initialize-worker or load-snapshot
696
684
  // pseudo-delivery
697
685
  addToTranscript(makeLoadSnapshotItem(snapshotID));
698
-
699
- kernelSlog.write({
700
- type: 'heap-snapshot-save',
701
- vatID,
702
- snapshotID,
703
- uncompressedSize,
704
- dbSaveSeconds,
705
- compressedSize,
706
- compressSeconds,
707
- endPosition,
708
- restartWorker,
709
- });
710
686
  }
711
687
 
712
688
  /**