@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 CHANGED
@@ -1,2 +1,3 @@
1
1
  export * from './src/ipc.ts';
2
- export * from './src/pool.ts';
2
+ export * from './src/pool.ts';
3
+ export * from './src/types.ts';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/worker",
3
- "version": "8.0.0-alpha.14",
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.14",
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 Options, type Pool, createPool } from 'generic-pool';
2
+ import { type Pool, createPool } from 'generic-pool';
3
3
 
4
4
  import { Env, Util, AsyncQueue } from '@travetto/runtime';
5
5
 
6
- type IterableSource<I> = Iterable<I> | AsyncIterable<I>;
7
- type WorkerExecutor<I, O> = (input: I, idx: number) => Promise<O>;
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 = /@travetto\/worker/.test(Env.DEBUG.value ?? '');
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
- ...isWorkerFactory(input) ? input() : { execute: input }
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 = /@travetto\/worker/.test(Env.DEBUG.value ?? '');
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 => options.onComplete?.(output, nextInput, finishIdx += 1))
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
- options?.onError?.(error, nextInput, finishIdx += 1);
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[0];
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 runStreamProgress<I, O>(worker: WorkerInput<I, O>, input: IterableSource<I>, total: number, options?: WorkPoolConfig<I, O>): AsyncIterable<{
160
- idx: number;
161
- value: O;
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, value, finishIdx) => {
168
- queue.add({ value: event, idx: finishIdx, total });
169
- options?.onComplete?.(event, value, finishIdx);
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;