@travetto/worker 3.0.0-rc.1 → 3.0.0-rc.4
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.
- package/README.md +16 -16
- package/{index.ts → __index__.ts} +0 -0
- package/package.json +8 -6
- package/src/comm/channel.ts +1 -1
- package/src/comm/parent.ts +2 -3
- package/src/input/async-iterator.ts +16 -1
- package/src/input/iterable.ts +13 -0
- package/src/input/types.ts +4 -0
- package/src/pool.ts +78 -26
- package/src/support/barrier.ts +22 -2
- package/src/support/timeout.ts +2 -2
- package/src/util.ts +1 -1
package/README.md
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
<!-- This file was generated by @travetto/doc and should not be modified directly -->
|
|
2
|
-
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/worker/
|
|
2
|
+
<!-- Please modify https://github.com/travetto/travetto/tree/main/module/worker/DOC.ts and execute "npx trv doc" to rebuild -->
|
|
3
3
|
# Worker
|
|
4
4
|
## Process management utilities, with a focus on inter-process communication
|
|
5
5
|
|
|
@@ -11,7 +11,7 @@ npm install @travetto/worker
|
|
|
11
11
|
This module provides the necessary primitives for handling dependent workers. A worker can be an individual actor or could be a pool of workers. Node provides ipc (inter-process communication) functionality out of the box. This module builds upon that by providing enhanced event management, richer process management, as well as constructs for orchestrating a conversation between two processes.
|
|
12
12
|
|
|
13
13
|
## Execution Pools
|
|
14
|
-
With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#
|
|
14
|
+
With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L31) is provided to allow for concurrent operation, and processing of jobs concurrently. To manage the flow of jobs, there are various [WorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/types.ts#L4) implementation that allow for a wide range of use cases.
|
|
15
15
|
|
|
16
16
|
The only provided [WorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/types.ts#L4) is the [IterableWorkSet](https://github.com/travetto/travetto/tree/main/module/worker/src/input/iterable.ts#L11) which supports all `Iterable` and `Iterator` sources. Additionally, the module provides [ManualAsyncIterator](https://github.com/travetto/travetto/tree/main/module/worker/src/input/async-iterator.ts#L6) which allows for manual control of iteration, which is useful for event driven work loads.
|
|
17
17
|
|
|
@@ -19,7 +19,7 @@ Below is a pool that will convert images on demand, while queuing as needed.
|
|
|
19
19
|
|
|
20
20
|
**Code: Image processing queue, with a fixed batch/pool size**
|
|
21
21
|
```typescript
|
|
22
|
-
import { ExecUtil, ExecutionState } from '@travetto/
|
|
22
|
+
import { ExecUtil, ExecutionState } from '@travetto/base';
|
|
23
23
|
import { Worker, WorkPool, IterableWorkSet, ManualAsyncIterator } from '@travetto/worker';
|
|
24
24
|
|
|
25
25
|
class ImageProcessor implements Worker<string> {
|
|
@@ -46,7 +46,7 @@ class ImageProcessor implements Worker<string> {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
export class ImageCompressor extends WorkPool<string
|
|
49
|
+
export class ImageCompressor extends WorkPool<string> {
|
|
50
50
|
|
|
51
51
|
pendingImages = new ManualAsyncIterator<string>();
|
|
52
52
|
|
|
@@ -68,14 +68,14 @@ Once a pool is constructed, it can be shutdown by calling the `.shutdown()` meth
|
|
|
68
68
|
|
|
69
69
|
## IPC Support
|
|
70
70
|
|
|
71
|
-
Within the `comm` package, there is support for two primary communication elements: [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) and [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#
|
|
71
|
+
Within the `comm` package, there is support for two primary communication elements: [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) and [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10). Usually [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10) indicates it is the owner of the sub process. [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) indicates that it has been created/spawned/forked by the parent and will communicate back to it's parent. This generally means that a [ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10) can be destroyed (i.e. killing the subprocess) where a [ChildCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/child.ts#L6) can only exit the process, but the channel cannot be destroyed.
|
|
72
72
|
|
|
73
73
|
### IPC as a Worker
|
|
74
|
-
A common pattern is to want to model a sub process as a worker, to be a valid candidate in a [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#
|
|
74
|
+
A common pattern is to want to model a sub process as a worker, to be a valid candidate in a [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L31). The [WorkUtil](https://github.com/travetto/travetto/tree/main/module/worker/src/util.ts#L14) class provides a utility to facilitate this desire.
|
|
75
75
|
|
|
76
76
|
**Code: Spawned Worker**
|
|
77
77
|
```typescript
|
|
78
|
-
import { ExecutionState } from '@travetto/
|
|
78
|
+
import { ExecutionState } from '@travetto/base';
|
|
79
79
|
|
|
80
80
|
import { ParentCommChannel } from './comm/parent';
|
|
81
81
|
import { Worker } from './pool';
|
|
@@ -112,17 +112,17 @@ export class WorkUtil {
|
|
|
112
112
|
}
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
-
When creating your work, via process spawning, you will need to provide the script (and any other features you would like in `SpawnConfig`). Additionally you must, at a minimum, provide functionality to run whenever an input element is up for grabs in the input source. This method will be provided the communication channel ([ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#
|
|
115
|
+
When creating your work, via process spawning, you will need to provide the script (and any other features you would like in `SpawnConfig`). Additionally you must, at a minimum, provide functionality to run whenever an input element is up for grabs in the input source. This method will be provided the communication channel ([ParentCommChannel](https://github.com/travetto/travetto/tree/main/module/worker/src/comm/parent.ts#L10)) and the input value. A simple example could look like:
|
|
116
116
|
|
|
117
117
|
**Code: Spawning Pool**
|
|
118
118
|
```typescript
|
|
119
|
+
import { ExecUtil } from '@travetto/base';
|
|
119
120
|
import { WorkPool, WorkUtil, IterableWorkSet } from '@travetto/worker';
|
|
120
|
-
import { ExecUtil, PathUtil } from '@travetto/boot';
|
|
121
121
|
|
|
122
122
|
export async function main(): Promise<void> {
|
|
123
123
|
const pool = new WorkPool(() =>
|
|
124
|
-
WorkUtil.spawnedWorker<{ data: string },
|
|
125
|
-
() => ExecUtil.
|
|
124
|
+
WorkUtil.spawnedWorker<{ data: string }, number>(
|
|
125
|
+
() => ExecUtil.spawn('trv', ['main', 'support/main.spawned.ts']),
|
|
126
126
|
ch => ch.once('ready'), // Wait for child to indicate it is ready
|
|
127
127
|
async (channel, inp) => {
|
|
128
128
|
const res = channel.once('response'); // Register response listener
|
|
@@ -131,21 +131,20 @@ export async function main(): Promise<void> {
|
|
|
131
131
|
const { data } = await res; // Get answer
|
|
132
132
|
console.log('Request complete', { input: inp, output: data });
|
|
133
133
|
|
|
134
|
-
if (!(inp + inp === data)) {
|
|
134
|
+
if (!(`${inp + inp}` === data)) {
|
|
135
135
|
// Ensure the answer is double the input
|
|
136
136
|
throw new Error('Did not get the double');
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
)
|
|
140
140
|
);
|
|
141
|
-
|
|
142
|
-
await pool.process(new IterableWorkSet([1, 2, 3, 4, 5])).then(x => pool.shutdown());
|
|
141
|
+
await pool.process(new IterableWorkSet([1, 2, 3, 4, 5]));
|
|
143
142
|
}
|
|
144
143
|
```
|
|
145
144
|
|
|
146
145
|
**Code: Spawned Worker**
|
|
147
146
|
```typescript
|
|
148
|
-
import
|
|
147
|
+
import timers from 'timers/promises';
|
|
149
148
|
import { ChildCommChannel } from '@travetto/worker';
|
|
150
149
|
|
|
151
150
|
export async function main(): Promise<void> {
|
|
@@ -163,11 +162,12 @@ export async function main(): Promise<void> {
|
|
|
163
162
|
|
|
164
163
|
**Terminal: Output**
|
|
165
164
|
```bash
|
|
166
|
-
$
|
|
165
|
+
$ trv main ./support/main.spawner.ts
|
|
167
166
|
|
|
168
167
|
Request complete { input: 1, output: 2 }
|
|
169
168
|
Request complete { input: 2, output: 4 }
|
|
170
169
|
Request complete { input: 3, output: 6 }
|
|
171
170
|
Request complete { input: 4, output: 8 }
|
|
172
171
|
Request complete { input: 5, output: 10 }
|
|
172
|
+
[s[r[u
|
|
173
173
|
```
|
|
File without changes
|
package/package.json
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/worker",
|
|
3
|
-
"
|
|
4
|
-
"version": "3.0.0-rc.1",
|
|
3
|
+
"version": "3.0.0-rc.4",
|
|
5
4
|
"description": "Process management utilities, with a focus on inter-process communication",
|
|
6
5
|
"keywords": [
|
|
7
6
|
"exec",
|
|
@@ -17,18 +16,21 @@
|
|
|
17
16
|
"name": "Travetto Framework"
|
|
18
17
|
},
|
|
19
18
|
"files": [
|
|
20
|
-
"
|
|
19
|
+
"__index__.ts",
|
|
21
20
|
"src"
|
|
22
21
|
],
|
|
23
|
-
"main": "
|
|
22
|
+
"main": "__index__.ts",
|
|
24
23
|
"repository": {
|
|
25
24
|
"url": "https://github.com/travetto/travetto.git",
|
|
26
25
|
"directory": "module/worker"
|
|
27
26
|
},
|
|
28
27
|
"dependencies": {
|
|
29
|
-
"@travetto/base": "^3.0.0-rc.
|
|
28
|
+
"@travetto/base": "^3.0.0-rc.4",
|
|
30
29
|
"@types/generic-pool": "^3.1.11",
|
|
31
|
-
"generic-pool": "^3.
|
|
30
|
+
"generic-pool": "^3.9.0"
|
|
31
|
+
},
|
|
32
|
+
"travetto": {
|
|
33
|
+
"displayName": "Worker"
|
|
32
34
|
},
|
|
33
35
|
"publishConfig": {
|
|
34
36
|
"access": "public"
|
package/src/comm/channel.ts
CHANGED
package/src/comm/parent.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { ChildProcess } from 'child_process';
|
|
2
2
|
|
|
3
|
-
import { ExecutionState } from '@travetto/
|
|
4
|
-
import { ShutdownManager } from '@travetto/base';
|
|
3
|
+
import { ShutdownManager, ExecutionState } from '@travetto/base';
|
|
5
4
|
|
|
6
5
|
import { ProcessCommChannel } from './channel';
|
|
7
6
|
|
|
@@ -14,7 +13,7 @@ export class ParentCommChannel<U = unknown> extends ProcessCommChannel<ChildProc
|
|
|
14
13
|
|
|
15
14
|
constructor(state: ExecutionState) {
|
|
16
15
|
super(state.process);
|
|
17
|
-
ShutdownManager.onShutdown({ close: () => this.destroy() });
|
|
16
|
+
ShutdownManager.onShutdown(this, { close: () => this.destroy() });
|
|
18
17
|
this.#complete = state.result
|
|
19
18
|
.finally(() => { this.proc = undefined; });
|
|
20
19
|
}
|
|
@@ -3,17 +3,24 @@ import { Util } from '@travetto/base';
|
|
|
3
3
|
/**
|
|
4
4
|
* Manual async iterator. Items are added manually, and consumed asynchronously
|
|
5
5
|
*/
|
|
6
|
-
export class ManualAsyncIterator<X> implements AsyncIterator<X> {
|
|
6
|
+
export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
7
7
|
|
|
8
8
|
#queue: X[] = [];
|
|
9
9
|
#done = false;
|
|
10
10
|
#ready = Util.resolvablePromise();
|
|
11
|
+
#size: number;
|
|
11
12
|
|
|
12
13
|
/**
|
|
13
14
|
* Initial set of items
|
|
14
15
|
*/
|
|
15
16
|
constructor(initial: Iterable<X> = []) {
|
|
16
17
|
this.#queue.push(...initial);
|
|
18
|
+
this.#size = this.#queue.length;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
// Allow for iteration
|
|
22
|
+
[Symbol.asyncIterator](): AsyncIterator<X> {
|
|
23
|
+
return this;
|
|
17
24
|
}
|
|
18
25
|
|
|
19
26
|
/**
|
|
@@ -38,6 +45,7 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X> {
|
|
|
38
45
|
} else {
|
|
39
46
|
this.#queue.unshift(...item);
|
|
40
47
|
}
|
|
48
|
+
this.#size += item.length;
|
|
41
49
|
this.#ready.resolve();
|
|
42
50
|
}
|
|
43
51
|
|
|
@@ -48,4 +56,11 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X> {
|
|
|
48
56
|
this.#done = true;
|
|
49
57
|
this.#ready.resolve();
|
|
50
58
|
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Get size, will change as items are added
|
|
62
|
+
*/
|
|
63
|
+
get size(): number {
|
|
64
|
+
return this.#size;
|
|
65
|
+
}
|
|
51
66
|
}
|
package/src/input/iterable.ts
CHANGED
|
@@ -13,6 +13,7 @@ export class IterableWorkSet<X> implements WorkSet<X> {
|
|
|
13
13
|
#src: Itr<X>;
|
|
14
14
|
#ondeck?: X;
|
|
15
15
|
#done = false;
|
|
16
|
+
#size?: number;
|
|
16
17
|
|
|
17
18
|
constructor(src: Iterable<X> | AsyncIterable<X> | (() => Generator<X>) | (() => AsyncGenerator<X>) | Itr<X>) {
|
|
18
19
|
if ('next' in src) {
|
|
@@ -26,6 +27,11 @@ export class IterableWorkSet<X> implements WorkSet<X> {
|
|
|
26
27
|
this.#src = src();
|
|
27
28
|
}
|
|
28
29
|
}
|
|
30
|
+
if (Array.isArray(src)) {
|
|
31
|
+
this.#size = src.length;
|
|
32
|
+
} else if (src instanceof Set) {
|
|
33
|
+
this.#size = src.size;
|
|
34
|
+
}
|
|
29
35
|
}
|
|
30
36
|
|
|
31
37
|
/**
|
|
@@ -57,4 +63,11 @@ export class IterableWorkSet<X> implements WorkSet<X> {
|
|
|
57
63
|
this.#ondeck = undefined;
|
|
58
64
|
return out;
|
|
59
65
|
}
|
|
66
|
+
|
|
67
|
+
/**
|
|
68
|
+
* Get size, if defined
|
|
69
|
+
*/
|
|
70
|
+
get size(): number | undefined {
|
|
71
|
+
return this.#size;
|
|
72
|
+
}
|
|
60
73
|
}
|
package/src/input/types.ts
CHANGED
package/src/pool.ts
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
|
-
import
|
|
2
|
-
import
|
|
1
|
+
import os from 'os';
|
|
2
|
+
import gp from 'generic-pool';
|
|
3
3
|
|
|
4
|
-
import { ShutdownManager,
|
|
4
|
+
import { GlobalEnv, ShutdownManager, TimeUtil } from '@travetto/base';
|
|
5
5
|
|
|
6
6
|
import { WorkSet } from './input/types';
|
|
7
|
+
import { ManualAsyncIterator } from '../src/input/async-iterator';
|
|
7
8
|
|
|
8
9
|
/**
|
|
9
10
|
* Worker definition
|
|
@@ -17,17 +18,24 @@ export interface Worker<X> {
|
|
|
17
18
|
release?(): unknown;
|
|
18
19
|
}
|
|
19
20
|
|
|
21
|
+
type WorkPoolProcessConfig<X> = {
|
|
22
|
+
shutdownOnComplete?: boolean;
|
|
23
|
+
onComplete?: (ev: WorkCompletionEvent<X>) => (void | Promise<void>);
|
|
24
|
+
};
|
|
25
|
+
|
|
26
|
+
type WorkCompletionEvent<X> = { idx: number, total?: number, value: X };
|
|
27
|
+
|
|
20
28
|
/**
|
|
21
29
|
* Work pool support
|
|
22
30
|
*/
|
|
23
|
-
export class WorkPool<X
|
|
31
|
+
export class WorkPool<X> {
|
|
24
32
|
|
|
25
33
|
static DEFAULT_SIZE = Math.min(os.cpus().length - 1, 4);
|
|
26
34
|
|
|
27
35
|
/**
|
|
28
36
|
* Generic-pool pool
|
|
29
37
|
*/
|
|
30
|
-
#pool: gp.Pool<
|
|
38
|
+
#pool: gp.Pool<Worker<X>>;
|
|
31
39
|
/**
|
|
32
40
|
* Number of acquisitions in process
|
|
33
41
|
*/
|
|
@@ -42,12 +50,22 @@ export class WorkPool<X, T extends Worker<X>> {
|
|
|
42
50
|
*/
|
|
43
51
|
#createErrors = 0;
|
|
44
52
|
|
|
53
|
+
/**
|
|
54
|
+
* Are we tracing
|
|
55
|
+
*/
|
|
56
|
+
#trace: boolean;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Cleanup for shutdown
|
|
60
|
+
*/
|
|
61
|
+
#shutdownCleanup?: () => void;
|
|
62
|
+
|
|
45
63
|
/**
|
|
46
64
|
*
|
|
47
65
|
* @param getWorker Produces a new worker for the pool
|
|
48
66
|
* @param opts Pool options
|
|
49
67
|
*/
|
|
50
|
-
constructor(getWorker: () => Promise<
|
|
68
|
+
constructor(getWorker: () => Promise<Worker<X>> | Worker<X>, opts?: gp.Options) {
|
|
51
69
|
const args = {
|
|
52
70
|
max: WorkPool.DEFAULT_SIZE,
|
|
53
71
|
min: 1,
|
|
@@ -59,16 +77,21 @@ export class WorkPool<X, T extends Worker<X>> {
|
|
|
59
77
|
this.#pool = gp.createPool({
|
|
60
78
|
create: () => this.#createAndTrack(getWorker, args),
|
|
61
79
|
destroy: x => this.destroy(x),
|
|
62
|
-
validate: async (x:
|
|
80
|
+
validate: async (x: Worker<X>) => x.active
|
|
63
81
|
}, args);
|
|
64
82
|
|
|
65
|
-
ShutdownManager.onShutdown(`worker.pool.${this.constructor.name}`, () =>
|
|
83
|
+
this.#shutdownCleanup = ShutdownManager.onShutdown(`worker.pool.${this.constructor.name}`, () => {
|
|
84
|
+
this.#shutdownCleanup = undefined;
|
|
85
|
+
this.shutdown();
|
|
86
|
+
});
|
|
87
|
+
|
|
88
|
+
this.#trace = !!GlobalEnv.debug?.includes('@travetto/worker');
|
|
66
89
|
}
|
|
67
90
|
|
|
68
91
|
/**
|
|
69
92
|
* Creates and tracks new worker
|
|
70
93
|
*/
|
|
71
|
-
async #createAndTrack(getWorker: () => Promise<
|
|
94
|
+
async #createAndTrack(getWorker: () => Promise<Worker<X>> | Worker<X>, opts: gp.Options): Promise<Worker<X>> {
|
|
72
95
|
try {
|
|
73
96
|
this.#pendingAcquires += 1;
|
|
74
97
|
const res = await getWorker();
|
|
@@ -83,7 +106,7 @@ export class WorkPool<X, T extends Worker<X>> {
|
|
|
83
106
|
} catch (err) {
|
|
84
107
|
if (this.#createErrors++ > opts.max!) { // If error count is bigger than pool size, we broke
|
|
85
108
|
console.error('Failed in creating pool', { error: err });
|
|
86
|
-
|
|
109
|
+
await ShutdownManager.exit(1);
|
|
87
110
|
}
|
|
88
111
|
throw err;
|
|
89
112
|
} finally {
|
|
@@ -94,23 +117,25 @@ export class WorkPool<X, T extends Worker<X>> {
|
|
|
94
117
|
/**
|
|
95
118
|
* Destroy the worker
|
|
96
119
|
*/
|
|
97
|
-
async destroy(worker:
|
|
98
|
-
|
|
120
|
+
async destroy(worker: Worker<X>): Promise<void> {
|
|
121
|
+
if (this.#trace) {
|
|
122
|
+
console.debug('Destroying', { pid: process.pid, worker: worker.id });
|
|
123
|
+
}
|
|
99
124
|
return worker.destroy();
|
|
100
125
|
}
|
|
101
126
|
|
|
102
127
|
/**
|
|
103
128
|
* Free worker on completion
|
|
104
129
|
*/
|
|
105
|
-
async release(worker:
|
|
106
|
-
|
|
130
|
+
async release(worker: Worker<X>): Promise<void> {
|
|
131
|
+
if (this.#trace) {
|
|
132
|
+
console.debug('Releasing', { pid: process.pid, worker: worker.id });
|
|
133
|
+
}
|
|
107
134
|
try {
|
|
108
135
|
if (worker.active) {
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
} catch { }
|
|
113
|
-
}
|
|
136
|
+
try {
|
|
137
|
+
await worker.release?.();
|
|
138
|
+
} catch { }
|
|
114
139
|
await this.#pool.release(worker);
|
|
115
140
|
} else {
|
|
116
141
|
await this.#pool.destroy(worker);
|
|
@@ -121,36 +146,63 @@ export class WorkPool<X, T extends Worker<X>> {
|
|
|
121
146
|
/**
|
|
122
147
|
* Process a given input source
|
|
123
148
|
*/
|
|
124
|
-
async process(src: WorkSet<X>): Promise<void> {
|
|
149
|
+
async process(src: WorkSet<X>, cfg: WorkPoolProcessConfig<X> = {}): Promise<void> {
|
|
125
150
|
const pending = new Set<Promise<unknown>>();
|
|
151
|
+
let count = 0;
|
|
152
|
+
|
|
153
|
+
if (src.size && cfg.onComplete) {
|
|
154
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
155
|
+
cfg.onComplete({ value: undefined as X, idx: 0, total: src.size });
|
|
156
|
+
}
|
|
126
157
|
|
|
127
158
|
while (await src.hasNext()) {
|
|
128
159
|
const worker = (await this.#pool.acquire())!;
|
|
129
|
-
|
|
160
|
+
if (this.#trace) {
|
|
161
|
+
console.debug('Acquired', { pid: process.pid, worker: worker.id });
|
|
162
|
+
}
|
|
130
163
|
const nextInput = await src.next();
|
|
131
164
|
|
|
132
165
|
const completion = worker.execute(nextInput)
|
|
133
166
|
.catch(err => this.#errors.push(err)) // Catch error
|
|
134
|
-
.finally(() => this.release(worker))
|
|
167
|
+
.finally(() => this.release(worker))
|
|
168
|
+
.finally(() => cfg.onComplete?.({ value: nextInput, idx: count += 1, total: src.size }));
|
|
135
169
|
|
|
136
170
|
completion.finally(() => pending.delete(completion));
|
|
137
171
|
|
|
138
172
|
pending.add(completion);
|
|
139
173
|
}
|
|
140
174
|
|
|
141
|
-
|
|
175
|
+
try {
|
|
176
|
+
await Promise.all(Array.from(pending));
|
|
142
177
|
|
|
143
|
-
|
|
144
|
-
|
|
178
|
+
if (this.#errors.length) {
|
|
179
|
+
throw this.#errors[0];
|
|
180
|
+
}
|
|
181
|
+
} finally {
|
|
182
|
+
if (cfg.shutdownOnComplete !== false) {
|
|
183
|
+
await this.shutdown();
|
|
184
|
+
}
|
|
145
185
|
}
|
|
146
186
|
}
|
|
147
187
|
|
|
188
|
+
/**
|
|
189
|
+
* Process a given input source as an async iterable, emitting on completion
|
|
190
|
+
*/
|
|
191
|
+
iterateProcess(src: WorkSet<X>, shutdownOnComplete?: boolean): AsyncIterable<WorkCompletionEvent<X>> {
|
|
192
|
+
const itr = new ManualAsyncIterator<WorkCompletionEvent<X>>();
|
|
193
|
+
const res = this.process(src, { onComplete: ev => itr.add(ev), shutdownOnComplete });
|
|
194
|
+
res.finally(() => itr.close());
|
|
195
|
+
return itr;
|
|
196
|
+
}
|
|
197
|
+
|
|
148
198
|
/**
|
|
149
199
|
* Shutdown pool
|
|
150
200
|
*/
|
|
151
201
|
async shutdown(): Promise<void> {
|
|
202
|
+
this.#shutdownCleanup?.();
|
|
203
|
+
|
|
152
204
|
while (this.#pendingAcquires) {
|
|
153
|
-
await
|
|
205
|
+
await TimeUtil.wait(10);
|
|
154
206
|
}
|
|
155
207
|
await this.#pool.drain();
|
|
156
208
|
await this.#pool.clear();
|
package/src/support/barrier.ts
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { setTimeout } from 'timers/promises';
|
|
2
|
+
|
|
3
|
+
import { ShutdownManager, TimeSpan, Util } from '@travetto/base';
|
|
4
|
+
|
|
2
5
|
import { Timeout } from './timeout';
|
|
3
6
|
|
|
4
7
|
function canCancel(o: unknown): o is { cancel(): unknown } {
|
|
@@ -10,6 +13,23 @@ function canCancel(o: unknown): o is { cancel(): unknown } {
|
|
|
10
13
|
* Build an execution barrier to handle various limitations
|
|
11
14
|
*/
|
|
12
15
|
export class Barrier {
|
|
16
|
+
/**
|
|
17
|
+
* Listen for an unhandled event, as a promise
|
|
18
|
+
*/
|
|
19
|
+
static listenForUnhandled(): Promise<never> & { cancel: () => void } {
|
|
20
|
+
const uncaught = Util.resolvablePromise<never>();
|
|
21
|
+
const uncaughtWithCancel: typeof uncaught & { cancel?: () => void } = uncaught;
|
|
22
|
+
const cancel = ShutdownManager.onUnhandled(err => { setTimeout(1).then(() => uncaught.reject(err)); return true; }, 0);
|
|
23
|
+
uncaughtWithCancel.cancel = (): void => {
|
|
24
|
+
cancel(); // Remove the handler
|
|
25
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
26
|
+
uncaughtWithCancel.resolve(undefined as never); // Close the promise
|
|
27
|
+
};
|
|
28
|
+
|
|
29
|
+
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
30
|
+
return uncaughtWithCancel as (Promise<never> & { cancel: () => void });
|
|
31
|
+
}
|
|
32
|
+
|
|
13
33
|
#support: string[] = [];
|
|
14
34
|
#barriers = new Map<string, Promise<unknown>>([]);
|
|
15
35
|
|
|
@@ -21,7 +41,7 @@ export class Barrier {
|
|
|
21
41
|
this.add(new Timeout(timeout).wait(), true);
|
|
22
42
|
}
|
|
23
43
|
if (unhandled) {
|
|
24
|
-
this.add(
|
|
44
|
+
this.add(Barrier.listenForUnhandled());
|
|
25
45
|
}
|
|
26
46
|
}
|
|
27
47
|
|
package/src/support/timeout.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { TimeSpan, Util } from '@travetto/base';
|
|
1
|
+
import { TimeSpan, TimeUtil, Util } from '@travetto/base';
|
|
2
2
|
import { ExecutionError } from './error';
|
|
3
3
|
|
|
4
4
|
/**
|
|
@@ -12,7 +12,7 @@ export class Timeout extends ExecutionError {
|
|
|
12
12
|
|
|
13
13
|
constructor(duration: number | TimeSpan, op: string = 'Operation') {
|
|
14
14
|
super(`${op} timed out after ${duration}${typeof duration === 'number' ? 'ms' : ''}`);
|
|
15
|
-
this.#duration =
|
|
15
|
+
this.#duration = TimeUtil.timeToMs(duration);
|
|
16
16
|
}
|
|
17
17
|
|
|
18
18
|
/**
|
package/src/util.ts
CHANGED