@travetto/worker 4.0.0-rc.0 → 4.0.0-rc.2

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 CHANGED
@@ -18,153 +18,5 @@ This module provides the necessary primitives for handling dependent workers. A
18
18
  ## Execution Pools
19
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.
20
20
 
21
- Below is a pool that will convert images on demand, while queuing as needed.
22
-
23
- **Code: Image processing queue, with a fixed batch/pool size**
24
- ```typescript
25
- import { ExecUtil, ExecutionState } from '@travetto/base';
26
- import { Worker, WorkPool, WorkQueue } from '@travetto/worker';
27
-
28
- class ImageProcessor implements Worker<string> {
29
- active = false;
30
- proc: ExecutionState;
31
-
32
- get id(): number | undefined {
33
- return this.proc.process.pid;
34
- }
35
-
36
- async destroy(): Promise<void> {
37
- this.proc.process.kill();
38
- }
39
-
40
- async execute(path: string): Promise<void> {
41
- this.active = true;
42
- try {
43
- this.proc = ExecUtil.spawn('convert images', [path]);
44
- await this.proc;
45
- } catch {
46
- // Do nothing
47
- }
48
- this.active = false;
49
- }
50
- }
51
-
52
- export class ImageCompressor {
53
-
54
- changes: AsyncIterable<unknown>;
55
- pendingImages = new WorkQueue<string>();
56
-
57
- begin(): void {
58
- this.changes ??= WorkPool.runStream(() => new ImageProcessor(), this.pendingImages);
59
- }
60
-
61
- convert(...images: string[]): void {
62
- this.pendingImages.addAll(images);
63
- }
64
- }
65
- ```
66
-
67
- Once a pool is constructed, it can be shutdown by calling the `.shutdown()` method, and awaiting the result.
68
-
69
21
  ## IPC Support
70
22
  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.
71
-
72
- ### IPC as a Worker
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.
74
-
75
- **Code: Spawned Worker**
76
- ```typescript
77
- import { ExecutionState } from '@travetto/base';
78
-
79
- import { ParentCommChannel } from './comm/parent';
80
- import { Worker } from './pool';
81
-
82
- type Simple<V> = (ch: ParentCommChannel<V>) => Promise<unknown | void>;
83
- type Param<V, X> = (ch: ParentCommChannel<V>, input: X) => Promise<unknown | void>;
84
-
85
- const empty = async (): Promise<void> => { };
86
-
87
- /**
88
- * Spawned worker
89
- */
90
- export class WorkUtil {
91
- /**
92
- * Create a process channel worker from a given spawn config
93
- */
94
- static spawnedWorker<V, X>(
95
- worker: () => ExecutionState,
96
- init: Simple<V>,
97
- execute: Param<V, X>,
98
- destroy: Simple<V> = empty): Worker<X> {
99
- const channel = new ParentCommChannel<V>(worker());
100
- return {
101
- get id(): number | undefined { return channel.id; },
102
- get active(): boolean { return channel.active; },
103
- init: () => init(channel),
104
- execute: inp => execute(channel, inp),
105
- async destroy(): Promise<void> {
106
- await destroy(channel);
107
- await channel.destroy();
108
- },
109
- };
110
- }
111
- }
112
- ```
113
-
114
- 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:
115
-
116
- **Code: Spawning Pool**
117
- ```typescript
118
- import { ExecUtil } from '@travetto/base';
119
- import { WorkPool, WorkUtil } from '@travetto/worker';
120
-
121
- export async function main(): Promise<void> {
122
- await WorkPool.run(
123
- () => WorkUtil.spawnedWorker<{ data: number }, number>(
124
- () => ExecUtil.spawn('trv', ['main', '@travetto/worker/doc/spawned.ts']),
125
- ch => ch.once('ready'), // Wait for child to indicate it is ready
126
- async (channel, inp) => {
127
- const res = channel.once('response'); // Register response listener
128
- channel.send('request', { data: inp }); // Send request
129
-
130
- const { data } = await res; // Get answer
131
- console.log('Request complete', { input: inp, output: data });
132
-
133
- if (!(inp + inp === data)) {
134
- // Ensure the answer is double the input
135
- throw new Error(`Did not get the double: inp=${inp}, data=${data}`);
136
- }
137
- }
138
- ), [1, 2, 3, 4, 5]);
139
- }
140
- ```
141
-
142
- **Code: Spawned Worker**
143
- ```typescript
144
- import timers from 'node:timers/promises';
145
- import { ChildCommChannel } from '@travetto/worker';
146
-
147
- export async function main(): Promise<void> {
148
- const exec = new ChildCommChannel<{ data: string }>();
149
-
150
- exec.on('request', data =>
151
- exec.send('response', { data: (data.data + data.data) })); // When data is received, return double
152
-
153
- exec.send('ready'); // Indicate the child is ready to receive requests
154
-
155
- for await (const _ of timers.setInterval(5000)) {
156
- // Keep-alive
157
- }
158
- }
159
- ```
160
-
161
- **Terminal: Output**
162
- ```bash
163
- $ trv main doc/spawner.ts
164
-
165
- Request complete { input: 1, output: 2 }
166
- Request complete { input: 2, output: 4 }
167
- Request complete { input: 3, output: 6 }
168
- Request complete { input: 4, output: 8 }
169
- Request complete { input: 5, output: 10 }
170
- ```
package/__index__.ts CHANGED
@@ -7,4 +7,3 @@ export * from './src/support/timeout';
7
7
  export * from './src/support/error';
8
8
  export * from './src/queue';
9
9
  export * from './src/pool';
10
- export * from './src/util';
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@travetto/worker",
3
- "version": "4.0.0-rc.0",
3
+ "version": "4.0.0-rc.2",
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": "^4.0.0-rc.0",
28
+ "@travetto/base": "^4.0.0-rc.2",
29
29
  "generic-pool": "^3.9.0"
30
30
  },
31
31
  "travetto": {
@@ -1,8 +1,6 @@
1
1
  import { ChildProcess } from 'node:child_process';
2
2
  import { EventEmitter } from 'node:events';
3
3
 
4
- import { ExecUtil } from '@travetto/base';
5
-
6
4
  /**
7
5
  * Channel that represents communication between parent/child
8
6
  */
@@ -56,8 +54,8 @@ export class ProcessCommChannel<T extends NodeJS.Process | ChildProcess, V = unk
56
54
  console.debug('Sending', { pid: this.#parentId, id: this.id, eventType });
57
55
  if (!this.#proc) {
58
56
  throw new Error('this.proc was not defined');
59
- } else if (this.#proc.send) {
60
- this.#proc.send({ ...(data ?? {}), type: eventType });
57
+ } else if (this.#proc.send && this.#proc.connected) {
58
+ this.#proc.send({ ...(data ?? {}), type: eventType }, (err) => err && console.error(err));
61
59
  } else {
62
60
  throw new Error('this.proc.send was not defined');
63
61
  }
@@ -92,7 +90,8 @@ export class ProcessCommChannel<T extends NodeJS.Process | ChildProcess, V = unk
92
90
  if (this.#proc) {
93
91
  console.debug('Killing', { pid: this.#parentId, id: this.id });
94
92
  if (this.#proc !== process) {
95
- ExecUtil.kill(this.#proc);
93
+ // eslint-disable-next-line @typescript-eslint/consistent-type-assertions
94
+ (this.#proc as ChildProcess).kill();
96
95
  }
97
96
  this.#proc = undefined;
98
97
  }
@@ -1,6 +1,6 @@
1
1
  import { ChildProcess } from 'node:child_process';
2
2
 
3
- import { ShutdownManager, ExecutionState } from '@travetto/base';
3
+ import { ShutdownManager } from '@travetto/base';
4
4
 
5
5
  import { ProcessCommChannel } from './channel';
6
6
 
@@ -9,13 +9,12 @@ import { ProcessCommChannel } from './channel';
9
9
  */
10
10
  export class ParentCommChannel<U = unknown> extends ProcessCommChannel<ChildProcess, U> {
11
11
 
12
- #complete: ExecutionState['result'];
12
+ #complete: Promise<void>;
13
13
 
14
- constructor(state: ExecutionState) {
15
- super(state.process);
14
+ constructor(proc: ChildProcess) {
15
+ super(proc);
16
16
  ShutdownManager.onGracefulShutdown(() => this.destroy(), this);
17
- this.#complete = state.result
18
- .finally(() => { this.proc = undefined; });
17
+ this.#complete = new Promise<void>(r => proc.on('close', r)).finally(() => { this.proc = undefined; });
19
18
  }
20
19
 
21
20
  /**
package/src/pool.ts CHANGED
@@ -34,7 +34,7 @@ const isWorkerFactory = <I, O>(o: WorkerInput<I, O>): o is (() => Worker<I, O>)
34
34
  */
35
35
  export class WorkPool {
36
36
 
37
- static MAX_SIZE = os.cpus().length - 1;
37
+ static MAX_SIZE = os.availableParallelism();
38
38
  static DEFAULT_SIZE = Math.min(WorkPool.MAX_SIZE, 4);
39
39
 
40
40
  /** Build worker pool */
package/src/queue.ts CHANGED
@@ -67,6 +67,15 @@ export class WorkQueue<X> implements AsyncIterator<X>, AsyncIterable<X> {
67
67
  this.#ready.resolve();
68
68
  }
69
69
 
70
+ /**
71
+ * Throw an error from the queue, rejecting and terminating immediately
72
+ */
73
+ async throw(e?: Error): Promise<IteratorResult<X>> {
74
+ this.#done = true;
75
+ this.#ready.reject(e);
76
+ return { value: undefined, done: this.#done };
77
+ }
78
+
70
79
  /**
71
80
  * Get size, will change as items are added
72
81
  */
package/src/util.ts DELETED
@@ -1,35 +0,0 @@
1
- import { ExecutionState } from '@travetto/base';
2
-
3
- import { ParentCommChannel } from './comm/parent';
4
- import { Worker } from './pool';
5
-
6
- type Simple<V> = (ch: ParentCommChannel<V>) => Promise<unknown | void>;
7
- type Param<V, X> = (ch: ParentCommChannel<V>, input: X) => Promise<unknown | void>;
8
-
9
- const empty = async (): Promise<void> => { };
10
-
11
- /**
12
- * Spawned worker
13
- */
14
- export class WorkUtil {
15
- /**
16
- * Create a process channel worker from a given spawn config
17
- */
18
- static spawnedWorker<V, X>(
19
- worker: () => ExecutionState,
20
- init: Simple<V>,
21
- execute: Param<V, X>,
22
- destroy: Simple<V> = empty): Worker<X> {
23
- const channel = new ParentCommChannel<V>(worker());
24
- return {
25
- get id(): number | undefined { return channel.id; },
26
- get active(): boolean { return channel.active; },
27
- init: () => init(channel),
28
- execute: inp => execute(channel, inp),
29
- async destroy(): Promise<void> {
30
- await destroy(channel);
31
- await channel.destroy();
32
- },
33
- };
34
- }
35
- }