@travetto/worker 8.0.0-alpha.14 → 8.0.0-alpha.16
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/__index__.ts +2 -1
- package/package.json +2 -2
- package/src/pool.ts +39 -58
- package/src/types.ts +60 -0
package/__index__.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/worker",
|
|
3
|
-
"version": "8.0.0-alpha.
|
|
3
|
+
"version": "8.0.0-alpha.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Process management utilities, with a focus on inter-process communication",
|
|
6
6
|
"keywords": [
|
|
@@ -26,7 +26,7 @@
|
|
|
26
26
|
"directory": "module/worker"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@travetto/runtime": "^8.0.0-alpha.
|
|
29
|
+
"@travetto/runtime": "^8.0.0-alpha.16",
|
|
30
30
|
"generic-pool": "^3.9.0"
|
|
31
31
|
},
|
|
32
32
|
"travetto": {
|
package/src/pool.ts
CHANGED
|
@@ -1,32 +1,12 @@
|
|
|
1
1
|
import os from 'node:os';
|
|
2
|
-
import { type
|
|
2
|
+
import { type Pool, createPool } from 'generic-pool';
|
|
3
3
|
|
|
4
4
|
import { Env, Util, AsyncQueue } from '@travetto/runtime';
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
* Worker definition
|
|
11
|
-
*/
|
|
12
|
-
export interface Worker<I, O = unknown> {
|
|
13
|
-
active: boolean;
|
|
14
|
-
id: unknown;
|
|
15
|
-
init?(): Promise<unknown>;
|
|
16
|
-
execute: WorkerExecutor<I, O>;
|
|
17
|
-
destroy?(): Promise<void>;
|
|
18
|
-
release?(): unknown;
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
type WorkerFactoryInput<I, O = unknown> = Partial<Worker<I, O>> & { execute: WorkerExecutor<I, O> };
|
|
22
|
-
type WorkerInput<I, O> = (() => WorkerFactoryInput<I, O>) | WorkerExecutor<I, O>;
|
|
23
|
-
type WorkPoolConfig<I, O> = Options & {
|
|
24
|
-
onComplete?: (output: O, input: I, finishIdx: number) => void;
|
|
25
|
-
onError?(event: Error, input: I, finishIdx: number): (unknown | Promise<unknown>);
|
|
26
|
-
shutdown?: AbortSignal;
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
const isWorkerFactory = <I, O>(value: WorkerInput<I, O>): value is (() => WorkerFactoryInput<I, O>) => value.length === 0;
|
|
6
|
+
import {
|
|
7
|
+
isWorkerFactory, WorkPoolResultError, type IterableSource, type Worker, type WorkerInput,
|
|
8
|
+
type WorkPoolCompleteEvent, type WorkPoolConfig, type WorkPoolProgress
|
|
9
|
+
} from './types.ts';
|
|
30
10
|
|
|
31
11
|
/**
|
|
32
12
|
* Work pool support
|
|
@@ -36,21 +16,26 @@ export class WorkPool {
|
|
|
36
16
|
static MAX_SIZE = os.availableParallelism();
|
|
37
17
|
static DEFAULT_SIZE = Math.max(Math.trunc(WorkPool.MAX_SIZE * .75), 4);
|
|
38
18
|
|
|
19
|
+
static #shouldTrace(): boolean {
|
|
20
|
+
return (Env.DEBUG.value ?? '').includes('@travetto/worker');
|
|
21
|
+
}
|
|
22
|
+
|
|
39
23
|
/** Build worker pool */
|
|
40
24
|
static #buildPool<I, O>(input: WorkerInput<I, O>, options?: WorkPoolConfig<I, O>): Pool<Worker<I, O>> {
|
|
41
25
|
let pendingAcquires = 0;
|
|
42
26
|
|
|
43
|
-
const trace =
|
|
27
|
+
const trace = this.#shouldTrace();
|
|
44
28
|
|
|
45
29
|
// Create the pool
|
|
46
30
|
const pool = createPool({
|
|
47
31
|
async create() {
|
|
48
32
|
try {
|
|
49
33
|
pendingAcquires += 1;
|
|
34
|
+
const factoryInput = isWorkerFactory(input) ? await input() : { execute: input };
|
|
50
35
|
const worker: Worker<I, O> = {
|
|
51
36
|
id: Util.uuid(),
|
|
52
37
|
active: true,
|
|
53
|
-
...
|
|
38
|
+
...factoryInput,
|
|
54
39
|
};
|
|
55
40
|
await worker.init?.();
|
|
56
41
|
return worker;
|
|
@@ -89,26 +74,41 @@ export class WorkPool {
|
|
|
89
74
|
*/
|
|
90
75
|
static async run<I, O>(workerFactory: WorkerInput<I, O>, source: IterableSource<I>, options: WorkPoolConfig<I, O> = {}): Promise<void> {
|
|
91
76
|
|
|
92
|
-
const trace =
|
|
77
|
+
const trace = this.#shouldTrace();
|
|
93
78
|
const pending = new Set<Promise<unknown>>();
|
|
94
79
|
const errors: Error[] = [];
|
|
95
80
|
let inputIdx = 0;
|
|
96
|
-
let finishIdx = 0;
|
|
97
81
|
|
|
98
82
|
const pool = this.#buildPool(workerFactory, options);
|
|
99
83
|
|
|
84
|
+
const progress: WorkPoolProgress = {
|
|
85
|
+
completed: 0,
|
|
86
|
+
total: options.total ?? 0,
|
|
87
|
+
failed: 0,
|
|
88
|
+
};
|
|
89
|
+
|
|
100
90
|
for await (const nextInput of source) {
|
|
101
91
|
const worker = await pool.acquire()!;
|
|
102
92
|
|
|
103
93
|
if (trace) {
|
|
104
94
|
console.debug('Acquired', { pid: process.pid, worker: worker.id });
|
|
105
95
|
}
|
|
96
|
+
if (!options.total) {
|
|
97
|
+
progress.total = inputIdx + 1;
|
|
98
|
+
}
|
|
106
99
|
|
|
107
100
|
const completion = worker.execute(nextInput, inputIdx += 1)
|
|
108
|
-
.then(output =>
|
|
101
|
+
.then(output => {
|
|
102
|
+
const success = options.isSuccess?.(output) ?? true;
|
|
103
|
+
progress.failed += +!success;
|
|
104
|
+
progress.completed += 1;
|
|
105
|
+
return options.onComplete?.({ output, input: nextInput, success, progress });
|
|
106
|
+
})
|
|
109
107
|
.catch(error => {
|
|
110
108
|
errors.push(error);
|
|
111
|
-
|
|
109
|
+
progress.failed += 1;
|
|
110
|
+
progress.completed += 1;
|
|
111
|
+
options?.onError?.({ error, input: nextInput, progress });
|
|
112
112
|
}) // Catch error
|
|
113
113
|
.finally(async () => {
|
|
114
114
|
if (trace) {
|
|
@@ -133,40 +133,21 @@ export class WorkPool {
|
|
|
133
133
|
await Promise.all(Array.from(pending));
|
|
134
134
|
|
|
135
135
|
if (errors.length) {
|
|
136
|
-
throw errors
|
|
136
|
+
throw new WorkPoolResultError(errors);
|
|
137
137
|
}
|
|
138
138
|
}
|
|
139
139
|
|
|
140
|
-
/**
|
|
141
|
-
* Process a given input source as an async iterable
|
|
142
|
-
*/
|
|
143
|
-
static runStream<I, O>(worker: WorkerInput<I, O>, input: IterableSource<I>, options?: WorkPoolConfig<I, O>): AsyncIterable<O> {
|
|
144
|
-
const queue = new AsyncQueue<O>();
|
|
145
|
-
const result = this.run(worker, input, {
|
|
146
|
-
...options,
|
|
147
|
-
onComplete: (event, value, finishIdx) => {
|
|
148
|
-
queue.add(event);
|
|
149
|
-
options?.onComplete?.(event, value, finishIdx);
|
|
150
|
-
}
|
|
151
|
-
});
|
|
152
|
-
result.finally(() => queue.close());
|
|
153
|
-
return queue;
|
|
154
|
-
}
|
|
155
|
-
|
|
156
140
|
/**
|
|
157
141
|
* Process a given input source as an async iterable with progress information
|
|
158
142
|
*/
|
|
159
|
-
static
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
total: number;
|
|
163
|
-
}> {
|
|
164
|
-
const queue = new AsyncQueue<{ idx: number, value: O, total: number }>();
|
|
165
|
-
const result = this.run(worker, input, {
|
|
143
|
+
static runStream<I, O>(worker: WorkerInput<I, O>, source: IterableSource<I>, options?: WorkPoolConfig<I, O>): AsyncIterable<WorkPoolCompleteEvent<I, O>> {
|
|
144
|
+
const queue = new AsyncQueue<WorkPoolCompleteEvent<I, O>>();
|
|
145
|
+
const result = this.run(worker, source, {
|
|
166
146
|
...options,
|
|
167
|
-
onComplete: (event
|
|
168
|
-
|
|
169
|
-
|
|
147
|
+
onComplete: async (event) => {
|
|
148
|
+
await options?.onComplete?.(event);
|
|
149
|
+
queue.add(event);
|
|
150
|
+
return;
|
|
170
151
|
}
|
|
171
152
|
});
|
|
172
153
|
result.finally(() => queue.close());
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import type { Options } from 'generic-pool';
|
|
2
|
+
|
|
3
|
+
import { RuntimeError } from '@travetto/runtime';
|
|
4
|
+
|
|
5
|
+
export type IterableSource<I> = Iterable<I> | AsyncIterable<I>;
|
|
6
|
+
export type WorkerExecutor<I, O> = (input: I, idx: number) => Promise<O>;
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* WorkPool results error.
|
|
10
|
+
*
|
|
11
|
+
* Hold all the errors for a given work pool execution
|
|
12
|
+
*/
|
|
13
|
+
export class WorkPoolResultError extends RuntimeError<{ errors: Error[] }> {
|
|
14
|
+
constructor(errors: Error[]) {
|
|
15
|
+
super('WorkPool errors have occurred', { category: 'data', details: { errors } });
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* Worker definition
|
|
21
|
+
*/
|
|
22
|
+
export interface Worker<I, O = unknown> {
|
|
23
|
+
active: boolean;
|
|
24
|
+
id: unknown;
|
|
25
|
+
init?(): Promise<unknown>;
|
|
26
|
+
execute: WorkerExecutor<I, O>;
|
|
27
|
+
destroy?(): Promise<void>;
|
|
28
|
+
release?(): unknown;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export type WorkPoolProgress = {
|
|
32
|
+
total: number;
|
|
33
|
+
completed: number;
|
|
34
|
+
failed: number;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type WorkPoolCompleteEvent<I, O> = {
|
|
38
|
+
output: O;
|
|
39
|
+
input: I;
|
|
40
|
+
success: boolean;
|
|
41
|
+
progress: WorkPoolProgress;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
export type WorkPoolErrorEvent<I> = {
|
|
45
|
+
error: Error;
|
|
46
|
+
input: I;
|
|
47
|
+
progress: WorkPoolProgress;
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
export type WorkerFactoryInput<I, O = unknown> = Partial<Worker<I, O>> & { execute: WorkerExecutor<I, O> };
|
|
51
|
+
export type WorkerInput<I, O> = (() => (WorkerFactoryInput<I, O> | Promise<WorkerFactoryInput<I, O>>)) | WorkerExecutor<I, O>;
|
|
52
|
+
export type WorkPoolConfig<I, O> = Options & {
|
|
53
|
+
isSuccess?: (output: O) => boolean;
|
|
54
|
+
onComplete?: (event: WorkPoolCompleteEvent<I, O>) => void | Promise<void>;
|
|
55
|
+
onError?<R = unknown>(event: WorkPoolErrorEvent<I>): (R | Promise<R>);
|
|
56
|
+
shutdown?: AbortSignal;
|
|
57
|
+
total?: number;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
export const isWorkerFactory = <I, O>(value: WorkerInput<I, O>): value is (() => (WorkerFactoryInput<I, O> | Promise<WorkerFactoryInput<I, O>>)) => value.length === 0;
|