@travetto/worker 4.0.0-rc.0 → 4.0.0-rc.1
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 +0 -148
- package/__index__.ts +0 -1
- package/package.json +2 -2
- package/src/comm/channel.ts +4 -5
- package/src/comm/parent.ts +5 -6
- package/src/pool.ts +1 -1
- package/src/queue.ts +9 -0
- package/src/util.ts +0 -35
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
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@travetto/worker",
|
|
3
|
-
"version": "4.0.0-rc.
|
|
3
|
+
"version": "4.0.0-rc.1",
|
|
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.
|
|
28
|
+
"@travetto/base": "^4.0.0-rc.1",
|
|
29
29
|
"generic-pool": "^3.9.0"
|
|
30
30
|
},
|
|
31
31
|
"travetto": {
|
package/src/comm/channel.ts
CHANGED
|
@@ -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
|
-
|
|
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
|
}
|
package/src/comm/parent.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import { ChildProcess } from 'node:child_process';
|
|
2
2
|
|
|
3
|
-
import { ShutdownManager
|
|
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:
|
|
12
|
+
#complete: Promise<void>;
|
|
13
13
|
|
|
14
|
-
constructor(
|
|
15
|
-
super(
|
|
14
|
+
constructor(proc: ChildProcess) {
|
|
15
|
+
super(proc);
|
|
16
16
|
ShutdownManager.onGracefulShutdown(() => this.destroy(), this);
|
|
17
|
-
this.#complete =
|
|
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.
|
|
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
|
-
}
|