@travetto/worker 3.4.1 → 4.0.0-rc.0
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 +13 -20
- package/__index__.ts +1 -3
- package/package.json +2 -2
- package/src/comm/channel.ts +2 -2
- package/src/comm/parent.ts +2 -2
- package/src/pool.ts +117 -150
- package/src/{input/async-iterator.ts → queue.ts} +19 -10
- package/src/support/barrier.ts +7 -3
- package/src/support/timeout.ts +2 -1
- package/src/input/iterable.ts +0 -73
- package/src/input/types.ts +0 -17
package/README.md
CHANGED
|
@@ -16,16 +16,14 @@ yarn add @travetto/worker
|
|
|
16
16
|
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.
|
|
17
17
|
|
|
18
18
|
## Execution Pools
|
|
19
|
-
With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#
|
|
20
|
-
|
|
21
|
-
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.
|
|
19
|
+
With respect to managing multiple executions, [WorkPool](https://github.com/travetto/travetto/tree/main/module/worker/src/pool.ts#L35) is provided to allow for concurrent operation, and processing of jobs concurrently. To manage the flow of jobs, [WorkQueue](https://github.com/travetto/travetto/tree/main/module/worker/src/queue.ts#L6) is provided to support a wide range of use cases. [WorkQueue](https://github.com/travetto/travetto/tree/main/module/worker/src/queue.ts#L6) allows for manual control of iteration, which is useful for event driven work loads.
|
|
22
20
|
|
|
23
21
|
Below is a pool that will convert images on demand, while queuing as needed.
|
|
24
22
|
|
|
25
23
|
**Code: Image processing queue, with a fixed batch/pool size**
|
|
26
24
|
```typescript
|
|
27
25
|
import { ExecUtil, ExecutionState } from '@travetto/base';
|
|
28
|
-
import { Worker, WorkPool,
|
|
26
|
+
import { Worker, WorkPool, WorkQueue } from '@travetto/worker';
|
|
29
27
|
|
|
30
28
|
class ImageProcessor implements Worker<string> {
|
|
31
29
|
active = false;
|
|
@@ -51,20 +49,17 @@ class ImageProcessor implements Worker<string> {
|
|
|
51
49
|
}
|
|
52
50
|
}
|
|
53
51
|
|
|
54
|
-
export class ImageCompressor
|
|
55
|
-
|
|
56
|
-
pendingImages = new ManualAsyncIterator<string>();
|
|
52
|
+
export class ImageCompressor {
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
}
|
|
54
|
+
changes: AsyncIterable<unknown>;
|
|
55
|
+
pendingImages = new WorkQueue<string>();
|
|
61
56
|
|
|
62
57
|
begin(): void {
|
|
63
|
-
this.
|
|
58
|
+
this.changes ??= WorkPool.runStream(() => new ImageProcessor(), this.pendingImages);
|
|
64
59
|
}
|
|
65
60
|
|
|
66
61
|
convert(...images: string[]): void {
|
|
67
|
-
this.pendingImages.
|
|
62
|
+
this.pendingImages.addAll(images);
|
|
68
63
|
}
|
|
69
64
|
}
|
|
70
65
|
```
|
|
@@ -75,7 +70,7 @@ Once a pool is constructed, it can be shutdown by calling the `.shutdown()` meth
|
|
|
75
70
|
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.
|
|
76
71
|
|
|
77
72
|
### IPC as a Worker
|
|
78
|
-
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#
|
|
73
|
+
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#L35). The [WorkUtil](https://github.com/travetto/travetto/tree/main/module/worker/src/util.ts#L14) class provides a utility to facilitate this desire.
|
|
79
74
|
|
|
80
75
|
**Code: Spawned Worker**
|
|
81
76
|
```typescript
|
|
@@ -121,11 +116,11 @@ When creating your work, via process spawning, you will need to provide the scri
|
|
|
121
116
|
**Code: Spawning Pool**
|
|
122
117
|
```typescript
|
|
123
118
|
import { ExecUtil } from '@travetto/base';
|
|
124
|
-
import { WorkPool, WorkUtil
|
|
119
|
+
import { WorkPool, WorkUtil } from '@travetto/worker';
|
|
125
120
|
|
|
126
121
|
export async function main(): Promise<void> {
|
|
127
|
-
|
|
128
|
-
WorkUtil.spawnedWorker<{ data: number }, number>(
|
|
122
|
+
await WorkPool.run(
|
|
123
|
+
() => WorkUtil.spawnedWorker<{ data: number }, number>(
|
|
129
124
|
() => ExecUtil.spawn('trv', ['main', '@travetto/worker/doc/spawned.ts']),
|
|
130
125
|
ch => ch.once('ready'), // Wait for child to indicate it is ready
|
|
131
126
|
async (channel, inp) => {
|
|
@@ -140,15 +135,13 @@ export async function main(): Promise<void> {
|
|
|
140
135
|
throw new Error(`Did not get the double: inp=${inp}, data=${data}`);
|
|
141
136
|
}
|
|
142
137
|
}
|
|
143
|
-
)
|
|
144
|
-
);
|
|
145
|
-
await pool.process(new IterableWorkSet([1, 2, 3, 4, 5]));
|
|
138
|
+
), [1, 2, 3, 4, 5]);
|
|
146
139
|
}
|
|
147
140
|
```
|
|
148
141
|
|
|
149
142
|
**Code: Spawned Worker**
|
|
150
143
|
```typescript
|
|
151
|
-
import timers from 'timers/promises';
|
|
144
|
+
import timers from 'node:timers/promises';
|
|
152
145
|
import { ChildCommChannel } from '@travetto/worker';
|
|
153
146
|
|
|
154
147
|
export async function main(): Promise<void> {
|
package/__index__.ts
CHANGED
|
@@ -2,11 +2,9 @@ export * from './src/comm/channel';
|
|
|
2
2
|
export * from './src/comm/child';
|
|
3
3
|
export * from './src/comm/parent';
|
|
4
4
|
export * from './src/comm/types';
|
|
5
|
-
export * from './src/input/iterable';
|
|
6
|
-
export * from './src/input/async-iterator';
|
|
7
|
-
export * from './src/input/types';
|
|
8
5
|
export * from './src/support/barrier';
|
|
9
6
|
export * from './src/support/timeout';
|
|
10
7
|
export * from './src/support/error';
|
|
8
|
+
export * from './src/queue';
|
|
11
9
|
export * from './src/pool';
|
|
12
10
|
export * from './src/util';
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/worker",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "4.0.0-rc.0",
|
|
4
4
|
"description": "Process management utilities, with a focus on inter-process communication",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"exec",
|
|
@@ -25,7 +25,7 @@
|
|
|
25
25
|
"directory": "module/worker"
|
|
26
26
|
},
|
|
27
27
|
"dependencies": {
|
|
28
|
-
"@travetto/base": "^
|
|
28
|
+
"@travetto/base": "^4.0.0-rc.0",
|
|
29
29
|
"generic-pool": "^3.9.0"
|
|
30
30
|
},
|
|
31
31
|
"travetto": {
|
package/src/comm/channel.ts
CHANGED
package/src/comm/parent.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { ChildProcess } from 'child_process';
|
|
1
|
+
import { ChildProcess } from 'node:child_process';
|
|
2
2
|
|
|
3
3
|
import { ShutdownManager, ExecutionState } from '@travetto/base';
|
|
4
4
|
|
|
@@ -13,7 +13,7 @@ export class ParentCommChannel<U = unknown> extends ProcessCommChannel<ChildProc
|
|
|
13
13
|
|
|
14
14
|
constructor(state: ExecutionState) {
|
|
15
15
|
super(state.process);
|
|
16
|
-
ShutdownManager.
|
|
16
|
+
ShutdownManager.onGracefulShutdown(() => this.destroy(), this);
|
|
17
17
|
this.#complete = state.result
|
|
18
18
|
.finally(() => { this.proc = undefined; });
|
|
19
19
|
}
|
package/src/pool.ts
CHANGED
|
@@ -1,211 +1,178 @@
|
|
|
1
|
-
import os from 'os';
|
|
2
1
|
import gp from 'generic-pool';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import timers from 'node:timers/promises';
|
|
3
4
|
|
|
4
|
-
import {
|
|
5
|
+
import { Env, Util } from '@travetto/base';
|
|
5
6
|
|
|
6
|
-
import {
|
|
7
|
-
|
|
7
|
+
import { WorkQueue } from './queue';
|
|
8
|
+
|
|
9
|
+
type ItrSource<I> = Iterable<I> | AsyncIterable<I>;
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Worker definition
|
|
11
13
|
*/
|
|
12
|
-
export interface Worker<
|
|
13
|
-
active
|
|
14
|
-
id
|
|
14
|
+
export interface Worker<I, O = unknown> {
|
|
15
|
+
active?: boolean;
|
|
16
|
+
id?: unknown;
|
|
15
17
|
init?(): Promise<unknown>;
|
|
16
|
-
execute(input:
|
|
17
|
-
destroy(): Promise<void>;
|
|
18
|
+
execute(input: I, idx: number): Promise<O>;
|
|
19
|
+
destroy?(): Promise<void>;
|
|
18
20
|
release?(): unknown;
|
|
19
21
|
}
|
|
20
22
|
|
|
21
|
-
type
|
|
22
|
-
|
|
23
|
-
onComplete?: (
|
|
23
|
+
type WorkerInput<I, O> = (() => Worker<I, O>) | ((input: I, inputIdx: number) => Promise<O>);
|
|
24
|
+
type WorkPoolConfig<I, O> = gp.Options & {
|
|
25
|
+
onComplete?: (output: O, input: I, finishIdx: number) => void;
|
|
26
|
+
onError?(ev: Error, input: I, finishIdx: number): (unknown | Promise<unknown>);
|
|
27
|
+
shutdown?: AbortSignal;
|
|
24
28
|
};
|
|
25
29
|
|
|
26
|
-
|
|
30
|
+
const isWorkerFactory = <I, O>(o: WorkerInput<I, O>): o is (() => Worker<I, O>) => o.length === 0;
|
|
27
31
|
|
|
28
32
|
/**
|
|
29
33
|
* Work pool support
|
|
30
34
|
*/
|
|
31
|
-
export class WorkPool
|
|
35
|
+
export class WorkPool {
|
|
32
36
|
|
|
33
37
|
static MAX_SIZE = os.cpus().length - 1;
|
|
34
38
|
static DEFAULT_SIZE = Math.min(WorkPool.MAX_SIZE, 4);
|
|
35
39
|
|
|
36
|
-
/**
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
#pool: gp.Pool<Worker<X>>;
|
|
40
|
-
/**
|
|
41
|
-
* Number of acquisitions in process
|
|
42
|
-
*/
|
|
43
|
-
#pendingAcquires = 0;
|
|
44
|
-
/**
|
|
45
|
-
* List of errors during processing
|
|
46
|
-
*/
|
|
47
|
-
#errors: Error[] = [];
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Error count during creation
|
|
51
|
-
*/
|
|
52
|
-
#createErrors = 0;
|
|
40
|
+
/** Build worker pool */
|
|
41
|
+
static #buildPool<I, O>(worker: WorkerInput<I, O>, opts?: WorkPoolConfig<I, O>): gp.Pool<Worker<I, O>> {
|
|
42
|
+
let pendingAcquires = 0;
|
|
53
43
|
|
|
54
|
-
|
|
55
|
-
* Are we tracing
|
|
56
|
-
*/
|
|
57
|
-
#trace: boolean;
|
|
44
|
+
const trace = /@travetto\/worker/.test(Env.DEBUG.val ?? '');
|
|
58
45
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
46
|
+
// Create the pool
|
|
47
|
+
const pool = gp.createPool({
|
|
48
|
+
async create() {
|
|
49
|
+
try {
|
|
50
|
+
pendingAcquires += 1;
|
|
51
|
+
const res = isWorkerFactory(worker) ? await worker() : { execute: worker };
|
|
52
|
+
res.id ??= Util.shortHash(`${Math.random()}`);
|
|
53
|
+
|
|
54
|
+
if (res.init) {
|
|
55
|
+
await res.init();
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return res;
|
|
59
|
+
} finally {
|
|
60
|
+
pendingAcquires -= 1;
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
async destroy(x) {
|
|
64
|
+
if (trace) {
|
|
65
|
+
console.debug('Destroying', { pid: process.pid, worker: x.id });
|
|
66
|
+
}
|
|
67
|
+
return x.destroy?.();
|
|
68
|
+
},
|
|
69
|
+
validate: async (x: Worker<I, O>) => x.active ?? true
|
|
70
|
+
}, {
|
|
71
71
|
max: WorkPool.DEFAULT_SIZE,
|
|
72
72
|
min: 1,
|
|
73
73
|
evictionRunIntervalMillis: 5000,
|
|
74
74
|
...(opts ?? {}),
|
|
75
|
-
};
|
|
76
|
-
|
|
77
|
-
// Create the pool
|
|
78
|
-
this.#pool = gp.createPool({
|
|
79
|
-
create: () => this.#createAndTrack(getWorker, args),
|
|
80
|
-
destroy: x => this.destroy(x),
|
|
81
|
-
validate: async (x: Worker<X>) => x.active
|
|
82
|
-
}, args);
|
|
83
|
-
|
|
84
|
-
this.#shutdownCleanup = ShutdownManager.onShutdown(`worker.pool.${this.constructor.name}`, () => {
|
|
85
|
-
this.#shutdownCleanup = undefined;
|
|
86
|
-
this.shutdown();
|
|
87
75
|
});
|
|
88
76
|
|
|
89
|
-
this.#trace = !!GlobalEnv.debug?.includes('@travetto/worker');
|
|
90
|
-
}
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* Creates and tracks new worker
|
|
94
|
-
*/
|
|
95
|
-
async #createAndTrack(getWorker: () => Promise<Worker<X>> | Worker<X>, opts: gp.Options): Promise<Worker<X>> {
|
|
96
|
-
try {
|
|
97
|
-
this.#pendingAcquires += 1;
|
|
98
|
-
const res = await getWorker();
|
|
99
|
-
|
|
100
|
-
if (res.init) {
|
|
101
|
-
await res.init();
|
|
102
|
-
}
|
|
103
|
-
|
|
104
|
-
this.#createErrors = 0; // Reset errors on success
|
|
105
77
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
await ShutdownManager.exit(1);
|
|
78
|
+
// Listen for shutdown
|
|
79
|
+
opts?.shutdown?.addEventListener('abort', async () => {
|
|
80
|
+
while (pendingAcquires) {
|
|
81
|
+
await timers.setTimeout(10);
|
|
111
82
|
}
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
}
|
|
116
|
-
}
|
|
83
|
+
await pool.drain();
|
|
84
|
+
await pool.clear();
|
|
85
|
+
});
|
|
117
86
|
|
|
118
|
-
|
|
119
|
-
* Destroy the worker
|
|
120
|
-
*/
|
|
121
|
-
async destroy(worker: Worker<X>): Promise<void> {
|
|
122
|
-
if (this.#trace) {
|
|
123
|
-
console.debug('Destroying', { pid: process.pid, worker: worker.id });
|
|
124
|
-
}
|
|
125
|
-
return worker.destroy();
|
|
87
|
+
return pool;
|
|
126
88
|
}
|
|
127
89
|
|
|
128
90
|
/**
|
|
129
|
-
*
|
|
91
|
+
* Process a given input source and worker, and fire on completion
|
|
130
92
|
*/
|
|
131
|
-
async
|
|
132
|
-
if (this.#trace) {
|
|
133
|
-
console.debug('Releasing', { pid: process.pid, worker: worker.id });
|
|
134
|
-
}
|
|
135
|
-
try {
|
|
136
|
-
if (worker.active) {
|
|
137
|
-
try {
|
|
138
|
-
await worker.release?.();
|
|
139
|
-
} catch { }
|
|
140
|
-
await this.#pool.release(worker);
|
|
141
|
-
} else {
|
|
142
|
-
await this.#pool.destroy(worker);
|
|
143
|
-
}
|
|
144
|
-
} catch { }
|
|
145
|
-
}
|
|
93
|
+
static async run<I, O>(workerFactory: WorkerInput<I, O>, src: ItrSource<I>, opts: WorkPoolConfig<I, O> = {}): Promise<void> {
|
|
146
94
|
|
|
147
|
-
|
|
148
|
-
* Process a given input source
|
|
149
|
-
*/
|
|
150
|
-
async process(src: WorkSet<X>, cfg: WorkPoolProcessConfig<X> = {}): Promise<void> {
|
|
95
|
+
const trace = /@travetto\/worker/.test(Env.DEBUG.val ?? '');
|
|
151
96
|
const pending = new Set<Promise<unknown>>();
|
|
152
|
-
|
|
97
|
+
const errors: Error[] = [];
|
|
98
|
+
let inputIdx = 0;
|
|
99
|
+
let finishIdx = 0;
|
|
153
100
|
|
|
154
|
-
|
|
155
|
-
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
|
156
|
-
cfg.onComplete({ value: undefined as X, idx: 0, total: src.size });
|
|
157
|
-
}
|
|
101
|
+
const pool = this.#buildPool(workerFactory, opts);
|
|
158
102
|
|
|
159
|
-
|
|
160
|
-
const worker =
|
|
161
|
-
|
|
103
|
+
for await (const nextInput of src) {
|
|
104
|
+
const worker = await pool.acquire()!;
|
|
105
|
+
|
|
106
|
+
if (trace) {
|
|
162
107
|
console.debug('Acquired', { pid: process.pid, worker: worker.id });
|
|
163
108
|
}
|
|
164
|
-
const nextInput = await src.next();
|
|
165
109
|
|
|
166
|
-
const completion = worker.execute(nextInput)
|
|
167
|
-
.
|
|
168
|
-
.
|
|
169
|
-
|
|
110
|
+
const completion = worker.execute(nextInput, inputIdx += 1)
|
|
111
|
+
.then(v => opts.onComplete?.(v, nextInput, finishIdx += 1))
|
|
112
|
+
.catch(err => {
|
|
113
|
+
errors.push(err);
|
|
114
|
+
opts?.onError?.(err, nextInput, finishIdx += 1);
|
|
115
|
+
}) // Catch error
|
|
116
|
+
.finally(async () => {
|
|
117
|
+
if (trace) {
|
|
118
|
+
console.debug('Releasing', { pid: process.pid, worker: worker.id });
|
|
119
|
+
}
|
|
120
|
+
try {
|
|
121
|
+
if (worker.active ?? true) {
|
|
122
|
+
try {
|
|
123
|
+
await worker.release?.();
|
|
124
|
+
} catch { }
|
|
125
|
+
await pool.release(worker);
|
|
126
|
+
} else {
|
|
127
|
+
await pool.destroy(worker);
|
|
128
|
+
}
|
|
129
|
+
} catch { }
|
|
130
|
+
});
|
|
170
131
|
|
|
171
132
|
completion.finally(() => pending.delete(completion));
|
|
172
|
-
|
|
173
133
|
pending.add(completion);
|
|
174
134
|
}
|
|
175
135
|
|
|
176
|
-
|
|
177
|
-
await Promise.all(Array.from(pending));
|
|
136
|
+
await Promise.all(Array.from(pending));
|
|
178
137
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
}
|
|
182
|
-
} finally {
|
|
183
|
-
if (cfg.shutdownOnComplete !== false) {
|
|
184
|
-
await this.shutdown();
|
|
185
|
-
}
|
|
138
|
+
if (errors.length) {
|
|
139
|
+
throw errors[0];
|
|
186
140
|
}
|
|
187
141
|
}
|
|
188
142
|
|
|
189
143
|
/**
|
|
190
|
-
* Process a given input source as an async iterable
|
|
144
|
+
* Process a given input source as an async iterable
|
|
191
145
|
*/
|
|
192
|
-
|
|
193
|
-
const itr = new
|
|
194
|
-
const res = this.
|
|
146
|
+
static runStream<I, O>(worker: WorkerInput<I, O>, input: ItrSource<I>, opts?: WorkPoolConfig<I, O>): AsyncIterable<O> {
|
|
147
|
+
const itr = new WorkQueue<O>();
|
|
148
|
+
const res = this.run(worker, input, {
|
|
149
|
+
...opts,
|
|
150
|
+
onComplete: (ev, inp, finishIdx) => {
|
|
151
|
+
itr.add(ev);
|
|
152
|
+
opts?.onComplete?.(ev, inp, finishIdx);
|
|
153
|
+
}
|
|
154
|
+
});
|
|
195
155
|
res.finally(() => itr.close());
|
|
196
156
|
return itr;
|
|
197
157
|
}
|
|
198
158
|
|
|
199
159
|
/**
|
|
200
|
-
*
|
|
160
|
+
* Process a given input source as an async iterable with progress information
|
|
201
161
|
*/
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
}
|
|
208
|
-
|
|
209
|
-
|
|
162
|
+
static runStreamProgress<I, O>(worker: WorkerInput<I, O>, input: ItrSource<I>, total: number, opts?: WorkPoolConfig<I, O>): AsyncIterable<{
|
|
163
|
+
idx: number;
|
|
164
|
+
value: O;
|
|
165
|
+
total: number;
|
|
166
|
+
}> {
|
|
167
|
+
const itr = new WorkQueue<{ idx: number, value: O, total: number }>();
|
|
168
|
+
const res = this.run(worker, input, {
|
|
169
|
+
...opts,
|
|
170
|
+
onComplete: (ev, inp, finishIdx) => {
|
|
171
|
+
itr.add({ value: ev, idx: finishIdx, total });
|
|
172
|
+
opts?.onComplete?.(ev, inp, finishIdx);
|
|
173
|
+
}
|
|
174
|
+
});
|
|
175
|
+
res.finally(() => itr.close());
|
|
176
|
+
return itr;
|
|
210
177
|
}
|
|
211
|
-
}
|
|
178
|
+
}
|
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { Util } from '@travetto/base';
|
|
2
2
|
|
|
3
3
|
/**
|
|
4
|
-
*
|
|
4
|
+
* WorkQueue, a manual async iterator. Items are added manually, and consumed asynchronously
|
|
5
5
|
*/
|
|
6
|
-
export class
|
|
6
|
+
export class WorkQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
|
|
7
7
|
|
|
8
8
|
#queue: X[] = [];
|
|
9
9
|
#done = false;
|
|
@@ -17,6 +17,9 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X
|
|
|
17
17
|
this.#queue.push(...initial);
|
|
18
18
|
this.#size = this.#queue.length;
|
|
19
19
|
signal?.addEventListener('abort', () => this.close());
|
|
20
|
+
if (signal?.aborted) {
|
|
21
|
+
this.close();
|
|
22
|
+
}
|
|
20
23
|
}
|
|
21
24
|
|
|
22
25
|
// Allow for iteration
|
|
@@ -39,14 +42,20 @@ export class ManualAsyncIterator<X> implements AsyncIterator<X>, AsyncIterable<X
|
|
|
39
42
|
* Queue next event to fire
|
|
40
43
|
* @param {boolean} immediate Determines if item(s) should be append or prepended to the queue
|
|
41
44
|
*/
|
|
42
|
-
add(item: X
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
add(item: X, immediate = false): void {
|
|
46
|
+
this.#queue[immediate ? 'unshift' : 'push'](item);
|
|
47
|
+
this.#size += 1;
|
|
48
|
+
this.#ready.resolve();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
/**
|
|
52
|
+
* Queue a list of data to stream
|
|
53
|
+
* @param {boolean} immediate Determines if item(s) should be append or prepended to the queue
|
|
54
|
+
*/
|
|
55
|
+
addAll(items: Iterable<X>): void {
|
|
56
|
+
const copy = [...items];
|
|
57
|
+
this.#queue.push(...copy);
|
|
58
|
+
this.#size += copy.length;
|
|
50
59
|
this.#ready.resolve();
|
|
51
60
|
}
|
|
52
61
|
|
package/src/support/barrier.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { setTimeout } from 'timers/promises';
|
|
1
|
+
import { setTimeout } from 'node:timers/promises';
|
|
2
2
|
|
|
3
|
-
import {
|
|
3
|
+
import { TimeSpan, Util } from '@travetto/base';
|
|
4
4
|
|
|
5
5
|
import { Timeout } from './timeout';
|
|
6
6
|
|
|
@@ -19,7 +19,11 @@ export class Barrier {
|
|
|
19
19
|
static listenForUnhandled(): Promise<never> & { cancel: () => void } {
|
|
20
20
|
const uncaught = Util.resolvablePromise<never>();
|
|
21
21
|
const uncaughtWithCancel: typeof uncaught & { cancel?: () => void } = uncaught;
|
|
22
|
-
const
|
|
22
|
+
const onError = (err: Error): void => { setTimeout(1).then(() => uncaught.reject(err)); };
|
|
23
|
+
process.on('unhandledRejection', onError).on('uncaughtException', onError);
|
|
24
|
+
const cancel = (): void => {
|
|
25
|
+
process.off('unhandledRejection', onError).off('unhandledException', onError);
|
|
26
|
+
};
|
|
23
27
|
uncaughtWithCancel.cancel = (): void => {
|
|
24
28
|
cancel(); // Remove the handler
|
|
25
29
|
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions
|
package/src/support/timeout.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { clearTimeout } from 'node:timers';
|
|
1
2
|
import { TimeSpan, TimeUtil, Util } from '@travetto/base';
|
|
2
3
|
import { ExecutionError } from './error';
|
|
3
4
|
|
|
@@ -6,7 +7,7 @@ import { ExecutionError } from './error';
|
|
|
6
7
|
*/
|
|
7
8
|
export class Timeout extends ExecutionError {
|
|
8
9
|
|
|
9
|
-
#id:
|
|
10
|
+
#id: ReturnType<typeof setTimeout> | undefined;
|
|
10
11
|
#promise = Util.resolvablePromise();
|
|
11
12
|
#duration: number;
|
|
12
13
|
|
package/src/input/iterable.ts
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
import { WorkSet } from './types';
|
|
2
|
-
|
|
3
|
-
type Itr<T> = Iterator<T> | AsyncIterator<T>;
|
|
4
|
-
|
|
5
|
-
const hasAsyncItr = (o: unknown): o is AsyncIterable<unknown> => !!o && typeof o === 'object' && Symbol.asyncIterator in o;
|
|
6
|
-
const hasItr = (o: unknown): o is Iterable<unknown> => !!o && typeof o === 'object' && Symbol.iterator in o;
|
|
7
|
-
|
|
8
|
-
/**
|
|
9
|
-
* Basic input source given an iterable input
|
|
10
|
-
*/
|
|
11
|
-
export class IterableWorkSet<X> implements WorkSet<X> {
|
|
12
|
-
|
|
13
|
-
#src: Itr<X>;
|
|
14
|
-
#ondeck?: X;
|
|
15
|
-
#done = false;
|
|
16
|
-
#size?: number;
|
|
17
|
-
|
|
18
|
-
constructor(src: Iterable<X> | AsyncIterable<X> | (() => Generator<X>) | (() => AsyncGenerator<X>) | Itr<X>) {
|
|
19
|
-
if ('next' in src) {
|
|
20
|
-
this.#src = src;
|
|
21
|
-
} else {
|
|
22
|
-
if (hasAsyncItr(src)) {
|
|
23
|
-
this.#src = src[Symbol.asyncIterator]();
|
|
24
|
-
} else if (hasItr(src)) {
|
|
25
|
-
this.#src = src[Symbol.iterator]();
|
|
26
|
-
} else {
|
|
27
|
-
this.#src = src();
|
|
28
|
-
}
|
|
29
|
-
}
|
|
30
|
-
if (Array.isArray(src)) {
|
|
31
|
-
this.#size = src.length;
|
|
32
|
-
} else if (src instanceof Set) {
|
|
33
|
-
this.#size = src.size;
|
|
34
|
-
}
|
|
35
|
-
}
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Fetch next item from the iterable
|
|
39
|
-
*/
|
|
40
|
-
async #primeNext(): Promise<void> {
|
|
41
|
-
const res = await this.#src.next();
|
|
42
|
-
this.#done = !!res.done;
|
|
43
|
-
this.#ondeck = res.value;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
/**
|
|
47
|
-
* Determine if the iterable has more data
|
|
48
|
-
*/
|
|
49
|
-
async hasNext(): Promise<boolean> {
|
|
50
|
-
if (this.#ondeck === undefined) {
|
|
51
|
-
await this.#primeNext();
|
|
52
|
-
}
|
|
53
|
-
return !this.#done;
|
|
54
|
-
}
|
|
55
|
-
|
|
56
|
-
/**
|
|
57
|
-
* Fetch next item
|
|
58
|
-
*/
|
|
59
|
-
async next(): Promise<X> {
|
|
60
|
-
await this.hasNext();
|
|
61
|
-
|
|
62
|
-
const out = this.#ondeck!;
|
|
63
|
-
this.#ondeck = undefined;
|
|
64
|
-
return out;
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
/**
|
|
68
|
-
* Get size, if defined
|
|
69
|
-
*/
|
|
70
|
-
get size(): number | undefined {
|
|
71
|
-
return this.#size;
|
|
72
|
-
}
|
|
73
|
-
}
|
package/src/input/types.ts
DELETED
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Definition for an work set
|
|
3
|
-
*/
|
|
4
|
-
export interface WorkSet<X> {
|
|
5
|
-
/**
|
|
6
|
-
* Determines if there is more work to do
|
|
7
|
-
*/
|
|
8
|
-
hasNext(): boolean | Promise<boolean>;
|
|
9
|
-
/**
|
|
10
|
-
* Get next item
|
|
11
|
-
*/
|
|
12
|
-
next(): X | Promise<X>;
|
|
13
|
-
/**
|
|
14
|
-
* Total size of work set
|
|
15
|
-
*/
|
|
16
|
-
size?: number;
|
|
17
|
-
}
|