tywrap 0.3.0 → 0.5.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 +40 -5
- package/dist/cli.js +24 -6
- package/dist/cli.js.map +1 -1
- package/dist/config/index.d.ts.map +1 -1
- package/dist/config/index.js +19 -13
- package/dist/config/index.js.map +1 -1
- package/dist/core/analyzer.d.ts.map +1 -1
- package/dist/core/analyzer.js +0 -1
- package/dist/core/analyzer.js.map +1 -1
- package/dist/dev.d.ts +57 -0
- package/dist/dev.d.ts.map +1 -0
- package/dist/dev.js +743 -0
- package/dist/dev.js.map +1 -0
- package/dist/index.d.ts +5 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -6
- package/dist/index.js.map +1 -1
- package/dist/runtime/base.d.ts +7 -7
- package/dist/runtime/base.js +8 -8
- package/dist/runtime/bounded-context.d.ts +15 -31
- package/dist/runtime/bounded-context.d.ts.map +1 -1
- package/dist/runtime/bounded-context.js +16 -15
- package/dist/runtime/bounded-context.js.map +1 -1
- package/dist/runtime/http.d.ts +24 -5
- package/dist/runtime/http.d.ts.map +1 -1
- package/dist/runtime/http.js +57 -12
- package/dist/runtime/http.js.map +1 -1
- package/dist/runtime/node.d.ts +33 -7
- package/dist/runtime/node.d.ts.map +1 -1
- package/dist/runtime/node.js +144 -120
- package/dist/runtime/node.js.map +1 -1
- package/dist/runtime/optimized-node.d.ts +5 -4
- package/dist/runtime/optimized-node.d.ts.map +1 -1
- package/dist/runtime/optimized-node.js +5 -4
- package/dist/runtime/optimized-node.js.map +1 -1
- package/dist/runtime/pooled-transport.d.ts +2 -18
- package/dist/runtime/pooled-transport.d.ts.map +1 -1
- package/dist/runtime/pooled-transport.js +2 -29
- package/dist/runtime/pooled-transport.js.map +1 -1
- package/dist/runtime/process-io.d.ts +2 -18
- package/dist/runtime/process-io.d.ts.map +1 -1
- package/dist/runtime/process-io.js +3 -30
- package/dist/runtime/process-io.js.map +1 -1
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts +12 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.d.ts.map +1 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.js +12 -0
- package/dist/runtime/pyodide-bootstrap-core.generated.js.map +1 -0
- package/dist/runtime/pyodide-io.d.ts +7 -37
- package/dist/runtime/pyodide-io.d.ts.map +1 -1
- package/dist/runtime/pyodide-io.js +90 -214
- package/dist/runtime/pyodide-io.js.map +1 -1
- package/dist/runtime/pyodide.d.ts +25 -5
- package/dist/runtime/pyodide.d.ts.map +1 -1
- package/dist/runtime/pyodide.js +59 -10
- package/dist/runtime/pyodide.js.map +1 -1
- package/dist/runtime/{bridge-protocol.d.ts → rpc-client.d.ts} +59 -46
- package/dist/runtime/rpc-client.d.ts.map +1 -0
- package/dist/runtime/{bridge-protocol.js → rpc-client.js} +60 -42
- package/dist/runtime/rpc-client.js.map +1 -0
- package/dist/runtime/worker-pool.d.ts +8 -20
- package/dist/runtime/worker-pool.d.ts.map +1 -1
- package/dist/runtime/worker-pool.js +58 -59
- package/dist/runtime/worker-pool.js.map +1 -1
- package/dist/types/index.d.ts +8 -10
- package/dist/types/index.d.ts.map +1 -1
- package/dist/tywrap.d.ts +6 -0
- package/dist/tywrap.d.ts.map +1 -1
- package/dist/tywrap.js +10 -3
- package/dist/tywrap.js.map +1 -1
- package/package.json +22 -18
- package/runtime/__pycache__/safe_codec.cpython-311.pyc +0 -0
- package/runtime/__pycache__/tywrap_bridge_core.cpython-311.pyc +0 -0
- package/runtime/python_bridge.py +85 -702
- package/runtime/safe_codec.py +10 -2
- package/runtime/tywrap_bridge_core.py +875 -0
- package/src/cli.ts +27 -6
- package/src/config/index.ts +28 -15
- package/src/core/analyzer.ts +1 -2
- package/src/dev.ts +983 -0
- package/src/index.ts +7 -7
- package/src/runtime/base.ts +8 -8
- package/src/runtime/bounded-context.ts +17 -56
- package/src/runtime/http.ts +85 -13
- package/src/runtime/node.ts +172 -178
- package/src/runtime/optimized-node.ts +5 -4
- package/src/runtime/pooled-transport.ts +2 -57
- package/src/runtime/process-io.ts +3 -55
- package/src/runtime/pyodide-bootstrap-core.generated.ts +12 -0
- package/src/runtime/pyodide-io.ts +92 -243
- package/src/runtime/pyodide.ts +87 -10
- package/src/runtime/{bridge-protocol.ts → rpc-client.ts} +76 -49
- package/src/runtime/worker-pool.ts +67 -88
- package/src/types/index.ts +20 -12
- package/src/tywrap.ts +17 -3
- package/dist/runtime/bridge-core.d.ts +0 -65
- package/dist/runtime/bridge-core.d.ts.map +0 -1
- package/dist/runtime/bridge-core.js +0 -379
- package/dist/runtime/bridge-core.js.map +0 -1
- package/dist/runtime/bridge-protocol.d.ts.map +0 -1
- package/dist/runtime/bridge-protocol.js.map +0 -1
- package/src/runtime/bridge-core.ts +0 -494
|
@@ -1,22 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* RpcClient - the single correlated-RPC client for JS<->Python communication.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* It HOLDS a Transport and a codec (SafeCodec) and is, in turn, HELD by the
|
|
5
|
+
* bridge facades (NodeBridge/HttpBridge/PyodideBridge) via composition — it is
|
|
6
|
+
* NOT a base class bridges extend. It owns the one place where the wire frame
|
|
7
|
+
* is built and correlated: id generation, {id, protocol} stamping, codec
|
|
8
|
+
* encode/decode, and transport.send. It composes DisposableBase to obtain its
|
|
9
|
+
* lifecycle (init/dispose) and bounded execution (timeout/retry/abort), but it
|
|
10
|
+
* carries no PythonRuntime contract obligation — the facade implements
|
|
11
|
+
* PythonRuntime and delegates the four RPC methods to this client.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Why composition (not inheritance): inheritance previously forced the RPC
|
|
14
|
+
* contract onto a lifecycle base, leaking throwing RPC stubs into transports.
|
|
15
|
+
* Holding one RpcClient keeps exactly one encode/decode/correlation site and
|
|
16
|
+
* lets transports stay pure byte-movers.
|
|
15
17
|
*
|
|
16
18
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
17
19
|
*/
|
|
18
20
|
import type { BridgeInfo } from '../types/index.js';
|
|
19
|
-
import {
|
|
21
|
+
import { DisposableBase, type ExecuteOptions } from './bounded-context.js';
|
|
20
22
|
import { SafeCodec, type CodecOptions } from './safe-codec.js';
|
|
21
23
|
import { type Transport, type ProtocolMessage } from './transport.js';
|
|
22
24
|
export interface GetBridgeInfoOptions {
|
|
@@ -27,9 +29,9 @@ export interface GetBridgeInfoOptions {
|
|
|
27
29
|
refresh?: boolean;
|
|
28
30
|
}
|
|
29
31
|
/**
|
|
30
|
-
* Configuration options for
|
|
32
|
+
* Configuration options for RpcClient.
|
|
31
33
|
*/
|
|
32
|
-
export interface
|
|
34
|
+
export interface RpcClientOptions {
|
|
33
35
|
/** The transport to use for communication */
|
|
34
36
|
transport: Transport;
|
|
35
37
|
/** Codec options for validation/serialization */
|
|
@@ -38,60 +40,51 @@ export interface BridgeProtocolOptions {
|
|
|
38
40
|
defaultTimeoutMs?: number;
|
|
39
41
|
}
|
|
40
42
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* This class provides:
|
|
45
|
-
* - Automatic transport lifecycle management
|
|
46
|
-
* - Request encoding with guardrails (special float rejection, key validation)
|
|
47
|
-
* - Response decoding with Arrow support
|
|
48
|
-
* - Full RuntimeExecution interface implementation
|
|
49
|
-
*
|
|
50
|
-
* Subclasses should:
|
|
51
|
-
* 1. Create their transport in their constructor
|
|
52
|
-
* 2. Pass it to super() via BridgeProtocolOptions
|
|
53
|
-
* 3. Optionally override doInit() and doDispose() for additional setup/teardown
|
|
43
|
+
* RpcClient holds a SafeCodec + Transport and composes DisposableBase for
|
|
44
|
+
* lifecycle/bounded-execution. It is the one correlated-RPC client; bridge
|
|
45
|
+
* facades HOLD an instance and delegate their PythonRuntime methods to it.
|
|
54
46
|
*
|
|
55
47
|
* @example
|
|
56
48
|
* ```typescript
|
|
57
|
-
* class NodeBridge extends
|
|
49
|
+
* class NodeBridge extends DisposableBase implements PythonRuntime {
|
|
50
|
+
* private readonly rpc: RpcClient;
|
|
58
51
|
* constructor(options: NodeBridgeOptions) {
|
|
52
|
+
* super();
|
|
59
53
|
* const transport = new ProcessIO(options);
|
|
60
|
-
*
|
|
54
|
+
* this.rpc = new RpcClient({ transport, defaultTimeoutMs: options.timeout });
|
|
55
|
+
* this.trackResource(this.rpc);
|
|
61
56
|
* }
|
|
57
|
+
* // call/instantiate/callMethod/disposeInstance delegate to this.rpc.*
|
|
62
58
|
* }
|
|
63
59
|
* ```
|
|
64
60
|
*/
|
|
65
|
-
export declare class
|
|
61
|
+
export declare class RpcClient extends DisposableBase {
|
|
66
62
|
/** Codec instance for validation and serialization */
|
|
67
|
-
|
|
63
|
+
readonly codec: SafeCodec;
|
|
68
64
|
/** Transport instance for message passing */
|
|
69
|
-
|
|
65
|
+
readonly transport: Transport;
|
|
70
66
|
/** Default timeout for operations in milliseconds */
|
|
71
|
-
|
|
67
|
+
readonly defaultTimeoutMs: number;
|
|
72
68
|
/** Counter for generating unique request IDs */
|
|
73
69
|
private requestId;
|
|
74
70
|
/** Cached bridge diagnostics info (populated by getBridgeInfo). */
|
|
75
71
|
private bridgeInfoCache?;
|
|
76
72
|
/**
|
|
77
|
-
* Create a new
|
|
73
|
+
* Create a new RpcClient instance.
|
|
78
74
|
*
|
|
79
75
|
* @param options - Configuration options including transport and codec settings
|
|
80
76
|
*/
|
|
81
|
-
constructor(options:
|
|
77
|
+
constructor(options: RpcClientOptions);
|
|
82
78
|
/**
|
|
83
|
-
* Initialize the
|
|
84
|
-
*
|
|
85
|
-
* Initializes the underlying transport. Subclasses can override this
|
|
86
|
-
* to add additional initialization logic, but must call super.doInit().
|
|
79
|
+
* Initialize the client by initializing the underlying transport.
|
|
80
|
+
* Driven by the holding facade's lifecycle (facade.init() -> rpc.init()).
|
|
87
81
|
*/
|
|
88
82
|
protected doInit(): Promise<void>;
|
|
89
83
|
/**
|
|
90
|
-
* Dispose the
|
|
84
|
+
* Dispose the client.
|
|
91
85
|
*
|
|
92
|
-
* The transport is tracked as a resource and
|
|
93
|
-
*
|
|
94
|
-
* but should not need to dispose the transport manually.
|
|
86
|
+
* The transport is tracked as a resource and is disposed automatically by
|
|
87
|
+
* DisposableBase. Here we only clear the cached bridge info.
|
|
95
88
|
*/
|
|
96
89
|
protected doDispose(): Promise<void>;
|
|
97
90
|
/**
|
|
@@ -111,7 +104,7 @@ export declare class BridgeProtocol extends BoundedContext {
|
|
|
111
104
|
* @throws BridgeExecutionError if Python returns an error
|
|
112
105
|
* @throws BridgeTimeoutError if the operation times out
|
|
113
106
|
*/
|
|
114
|
-
|
|
107
|
+
sendMessage<T>(message: Omit<ProtocolMessage, 'id' | 'protocol'>, options?: ExecuteOptions<T>): Promise<T>;
|
|
115
108
|
/**
|
|
116
109
|
* Async version that uses decodeResponseAsync for Arrow support.
|
|
117
110
|
*
|
|
@@ -126,7 +119,27 @@ export declare class BridgeProtocol extends BoundedContext {
|
|
|
126
119
|
* @throws BridgeExecutionError if Python returns an error
|
|
127
120
|
* @throws BridgeTimeoutError if the operation times out
|
|
128
121
|
*/
|
|
129
|
-
|
|
122
|
+
sendMessageAsync<T>(message: Omit<ProtocolMessage, 'id' | 'protocol'>, options?: ExecuteOptions<T>): Promise<T>;
|
|
123
|
+
/**
|
|
124
|
+
* Send a message over an EXPLICIT transport (not this.transport) with the
|
|
125
|
+
* same id-generation + codec encode/decode as the normal send path, but
|
|
126
|
+
* WITHOUT this.execute() and WITHOUT auto-init.
|
|
127
|
+
*
|
|
128
|
+
* This is the warmup path: NodeBridge runs warmup commands inside
|
|
129
|
+
* transport.init() (worker spawn -> onWorkerReady), which happens DURING
|
|
130
|
+
* rpc.init(). Routing those through this.execute() would auto-init this same
|
|
131
|
+
* client and re-await the in-flight init() that is itself waiting on warmup,
|
|
132
|
+
* deadlocking. So sendOn deliberately skips lifecycle: it only unifies the
|
|
133
|
+
* id counter + codec, never the state machine.
|
|
134
|
+
*
|
|
135
|
+
* @param transport - The specific worker transport to send on
|
|
136
|
+
* @param message - The protocol message (without id/protocol; stamped here)
|
|
137
|
+
* @param opts - Optional timeout / abort signal
|
|
138
|
+
*/
|
|
139
|
+
sendOn<T>(transport: Transport, message: Omit<ProtocolMessage, 'id' | 'protocol'>, opts?: {
|
|
140
|
+
timeoutMs?: number;
|
|
141
|
+
signal?: AbortSignal;
|
|
142
|
+
}): Promise<T>;
|
|
130
143
|
/**
|
|
131
144
|
* Generate a unique request ID.
|
|
132
145
|
*
|
|
@@ -181,4 +194,4 @@ export declare class BridgeProtocol extends BoundedContext {
|
|
|
181
194
|
*/
|
|
182
195
|
getBridgeInfo(options?: GetBridgeInfoOptions): Promise<BridgeInfo>;
|
|
183
196
|
}
|
|
184
|
-
//# sourceMappingURL=
|
|
197
|
+
//# sourceMappingURL=rpc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../../src/runtime/rpc-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
/**
|
|
2
|
-
*
|
|
2
|
+
* RpcClient - the single correlated-RPC client for JS<->Python communication.
|
|
3
3
|
*
|
|
4
|
-
*
|
|
5
|
-
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
4
|
+
* It HOLDS a Transport and a codec (SafeCodec) and is, in turn, HELD by the
|
|
5
|
+
* bridge facades (NodeBridge/HttpBridge/PyodideBridge) via composition — it is
|
|
6
|
+
* NOT a base class bridges extend. It owns the one place where the wire frame
|
|
7
|
+
* is built and correlated: id generation, {id, protocol} stamping, codec
|
|
8
|
+
* encode/decode, and transport.send. It composes DisposableBase to obtain its
|
|
9
|
+
* lifecycle (init/dispose) and bounded execution (timeout/retry/abort), but it
|
|
10
|
+
* carries no PythonRuntime contract obligation — the facade implements
|
|
11
|
+
* PythonRuntime and delegates the four RPC methods to this client.
|
|
10
12
|
*
|
|
11
|
-
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
13
|
+
* Why composition (not inheritance): inheritance previously forced the RPC
|
|
14
|
+
* contract onto a lifecycle base, leaking throwing RPC stubs into transports.
|
|
15
|
+
* Holding one RpcClient keeps exactly one encode/decode/correlation site and
|
|
16
|
+
* lets transports stay pure byte-movers.
|
|
15
17
|
*
|
|
16
18
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
17
19
|
*/
|
|
18
|
-
import {
|
|
20
|
+
import { DisposableBase } from './bounded-context.js';
|
|
19
21
|
import { BridgeProtocolError } from './errors.js';
|
|
20
22
|
import { SafeCodec } from './safe-codec.js';
|
|
21
23
|
import { TYWRAP_PROTOCOL_VERSION } from './protocol.js';
|
|
@@ -94,34 +96,28 @@ function validateBridgeInfoPayload(value) {
|
|
|
94
96
|
};
|
|
95
97
|
}
|
|
96
98
|
// =============================================================================
|
|
97
|
-
//
|
|
99
|
+
// RPC CLIENT
|
|
98
100
|
// =============================================================================
|
|
99
101
|
/**
|
|
100
|
-
*
|
|
101
|
-
*
|
|
102
|
-
*
|
|
103
|
-
* This class provides:
|
|
104
|
-
* - Automatic transport lifecycle management
|
|
105
|
-
* - Request encoding with guardrails (special float rejection, key validation)
|
|
106
|
-
* - Response decoding with Arrow support
|
|
107
|
-
* - Full RuntimeExecution interface implementation
|
|
108
|
-
*
|
|
109
|
-
* Subclasses should:
|
|
110
|
-
* 1. Create their transport in their constructor
|
|
111
|
-
* 2. Pass it to super() via BridgeProtocolOptions
|
|
112
|
-
* 3. Optionally override doInit() and doDispose() for additional setup/teardown
|
|
102
|
+
* RpcClient holds a SafeCodec + Transport and composes DisposableBase for
|
|
103
|
+
* lifecycle/bounded-execution. It is the one correlated-RPC client; bridge
|
|
104
|
+
* facades HOLD an instance and delegate their PythonRuntime methods to it.
|
|
113
105
|
*
|
|
114
106
|
* @example
|
|
115
107
|
* ```typescript
|
|
116
|
-
* class NodeBridge extends
|
|
108
|
+
* class NodeBridge extends DisposableBase implements PythonRuntime {
|
|
109
|
+
* private readonly rpc: RpcClient;
|
|
117
110
|
* constructor(options: NodeBridgeOptions) {
|
|
111
|
+
* super();
|
|
118
112
|
* const transport = new ProcessIO(options);
|
|
119
|
-
*
|
|
113
|
+
* this.rpc = new RpcClient({ transport, defaultTimeoutMs: options.timeout });
|
|
114
|
+
* this.trackResource(this.rpc);
|
|
120
115
|
* }
|
|
116
|
+
* // call/instantiate/callMethod/disposeInstance delegate to this.rpc.*
|
|
121
117
|
* }
|
|
122
118
|
* ```
|
|
123
119
|
*/
|
|
124
|
-
export class
|
|
120
|
+
export class RpcClient extends DisposableBase {
|
|
125
121
|
/** Codec instance for validation and serialization */
|
|
126
122
|
codec;
|
|
127
123
|
/** Transport instance for message passing */
|
|
@@ -133,7 +129,7 @@ export class BridgeProtocol extends BoundedContext {
|
|
|
133
129
|
/** Cached bridge diagnostics info (populated by getBridgeInfo). */
|
|
134
130
|
bridgeInfoCache;
|
|
135
131
|
/**
|
|
136
|
-
* Create a new
|
|
132
|
+
* Create a new RpcClient instance.
|
|
137
133
|
*
|
|
138
134
|
* @param options - Configuration options including transport and codec settings
|
|
139
135
|
*/
|
|
@@ -149,25 +145,21 @@ export class BridgeProtocol extends BoundedContext {
|
|
|
149
145
|
// LIFECYCLE
|
|
150
146
|
// ===========================================================================
|
|
151
147
|
/**
|
|
152
|
-
* Initialize the
|
|
153
|
-
*
|
|
154
|
-
* Initializes the underlying transport. Subclasses can override this
|
|
155
|
-
* to add additional initialization logic, but must call super.doInit().
|
|
148
|
+
* Initialize the client by initializing the underlying transport.
|
|
149
|
+
* Driven by the holding facade's lifecycle (facade.init() -> rpc.init()).
|
|
156
150
|
*/
|
|
157
151
|
async doInit() {
|
|
158
152
|
await this.transport.init();
|
|
159
153
|
}
|
|
160
154
|
/**
|
|
161
|
-
* Dispose the
|
|
155
|
+
* Dispose the client.
|
|
162
156
|
*
|
|
163
|
-
* The transport is tracked as a resource and
|
|
164
|
-
*
|
|
165
|
-
* but should not need to dispose the transport manually.
|
|
157
|
+
* The transport is tracked as a resource and is disposed automatically by
|
|
158
|
+
* DisposableBase. Here we only clear the cached bridge info.
|
|
166
159
|
*/
|
|
167
160
|
async doDispose() {
|
|
168
161
|
this.bridgeInfoCache = undefined;
|
|
169
|
-
// Transport is tracked and will be disposed by
|
|
170
|
-
// Subclasses can override to add additional cleanup
|
|
162
|
+
// Transport is tracked and will be disposed by DisposableBase.
|
|
171
163
|
}
|
|
172
164
|
// ===========================================================================
|
|
173
165
|
// MESSAGE SENDING
|
|
@@ -233,6 +225,32 @@ export class BridgeProtocol extends BoundedContext {
|
|
|
233
225
|
return this.codec.decodeResponseAsync(responseStr);
|
|
234
226
|
}, options);
|
|
235
227
|
}
|
|
228
|
+
/**
|
|
229
|
+
* Send a message over an EXPLICIT transport (not this.transport) with the
|
|
230
|
+
* same id-generation + codec encode/decode as the normal send path, but
|
|
231
|
+
* WITHOUT this.execute() and WITHOUT auto-init.
|
|
232
|
+
*
|
|
233
|
+
* This is the warmup path: NodeBridge runs warmup commands inside
|
|
234
|
+
* transport.init() (worker spawn -> onWorkerReady), which happens DURING
|
|
235
|
+
* rpc.init(). Routing those through this.execute() would auto-init this same
|
|
236
|
+
* client and re-await the in-flight init() that is itself waiting on warmup,
|
|
237
|
+
* deadlocking. So sendOn deliberately skips lifecycle: it only unifies the
|
|
238
|
+
* id counter + codec, never the state machine.
|
|
239
|
+
*
|
|
240
|
+
* @param transport - The specific worker transport to send on
|
|
241
|
+
* @param message - The protocol message (without id/protocol; stamped here)
|
|
242
|
+
* @param opts - Optional timeout / abort signal
|
|
243
|
+
*/
|
|
244
|
+
async sendOn(transport, message, opts) {
|
|
245
|
+
const fullMessage = {
|
|
246
|
+
...message,
|
|
247
|
+
id: this.generateId(),
|
|
248
|
+
protocol: PROTOCOL_ID,
|
|
249
|
+
};
|
|
250
|
+
const encoded = this.codec.encodeRequest(fullMessage);
|
|
251
|
+
const responseStr = await transport.send(encoded, opts?.timeoutMs ?? this.defaultTimeoutMs, opts?.signal);
|
|
252
|
+
return this.codec.decodeResponseAsync(responseStr);
|
|
253
|
+
}
|
|
236
254
|
/**
|
|
237
255
|
* Generate a unique request ID.
|
|
238
256
|
*
|
|
@@ -243,7 +261,7 @@ export class BridgeProtocol extends BoundedContext {
|
|
|
243
261
|
return ++this.requestId;
|
|
244
262
|
}
|
|
245
263
|
// ===========================================================================
|
|
246
|
-
//
|
|
264
|
+
// RPC METHODS (delegated to by the facade's PythonRuntime implementation)
|
|
247
265
|
// ===========================================================================
|
|
248
266
|
/**
|
|
249
267
|
* Call a Python function.
|
|
@@ -341,4 +359,4 @@ export class BridgeProtocol extends BoundedContext {
|
|
|
341
359
|
return info;
|
|
342
360
|
}
|
|
343
361
|
}
|
|
344
|
-
//# sourceMappingURL=
|
|
362
|
+
//# sourceMappingURL=rpc-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../../src/runtime/rpc-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { DisposableBase } from './bounded-context.js';
|
|
10
10
|
import type { Transport } from './transport.js';
|
|
11
11
|
/**
|
|
12
12
|
* Configuration options for the WorkerPool.
|
|
@@ -71,7 +71,7 @@ export interface PooledWorker {
|
|
|
71
71
|
* await pool.dispose();
|
|
72
72
|
* ```
|
|
73
73
|
*/
|
|
74
|
-
export declare class WorkerPool extends
|
|
74
|
+
export declare class WorkerPool extends DisposableBase {
|
|
75
75
|
private readonly options;
|
|
76
76
|
private readonly workers;
|
|
77
77
|
private readonly waitQueue;
|
|
@@ -145,7 +145,6 @@ export declare class WorkerPool extends BoundedContext {
|
|
|
145
145
|
* - Process exited unexpectedly
|
|
146
146
|
* - Pipe errors (EPIPE)
|
|
147
147
|
* - Connection reset errors (ECONNRESET)
|
|
148
|
-
* - Request timeouts (the worker may still be busy with an uncancellable request)
|
|
149
148
|
*/
|
|
150
149
|
private isFatalWorkerError;
|
|
151
150
|
/**
|
|
@@ -171,11 +170,17 @@ export declare class WorkerPool extends BoundedContext {
|
|
|
171
170
|
* Find an available worker with capacity for another request.
|
|
172
171
|
*/
|
|
173
172
|
private findAvailableWorker;
|
|
173
|
+
private getMinimumWorkerDeficit;
|
|
174
|
+
private isShuttingDown;
|
|
175
|
+
private fillToMinimumWorkers;
|
|
176
|
+
private spawnWorkers;
|
|
174
177
|
/**
|
|
175
178
|
* Replace a removed worker in the background so the next caller does not pay
|
|
176
179
|
* the full worker cold-start penalty after a timeout or crash.
|
|
177
180
|
*/
|
|
178
181
|
private scheduleReplacementWorker;
|
|
182
|
+
private spawnWorkerToPool;
|
|
183
|
+
private publishAvailableWorker;
|
|
179
184
|
/**
|
|
180
185
|
* Create a new worker and add it to the pool.
|
|
181
186
|
*
|
|
@@ -187,22 +192,5 @@ export declare class WorkerPool extends BoundedContext {
|
|
|
187
192
|
* Wait in queue for a worker to become available.
|
|
188
193
|
*/
|
|
189
194
|
private waitForWorker;
|
|
190
|
-
/**
|
|
191
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
192
|
-
* Use the BridgeProtocol layer with a pooled worker's transport.
|
|
193
|
-
*/
|
|
194
|
-
call<T = unknown>(_module: string, _functionName: string, _args: unknown[], _kwargs?: Record<string, unknown>): Promise<T>;
|
|
195
|
-
/**
|
|
196
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
197
|
-
*/
|
|
198
|
-
instantiate<T = unknown>(_module: string, _className: string, _args: unknown[], _kwargs?: Record<string, unknown>): Promise<T>;
|
|
199
|
-
/**
|
|
200
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
201
|
-
*/
|
|
202
|
-
callMethod<T = unknown>(_handle: string, _methodName: string, _args: unknown[], _kwargs?: Record<string, unknown>): Promise<T>;
|
|
203
|
-
/**
|
|
204
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
205
|
-
*/
|
|
206
|
-
disposeInstance(_handle: string): Promise<void>;
|
|
207
195
|
}
|
|
208
196
|
//# sourceMappingURL=worker-pool.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/runtime/worker-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,eAAe,EAAE,MAAM,SAAS,CAAC;IAEjC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IAEnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,yDAAyD;IACzD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,SAAS,EAAE,SAAS,CAAC;IAErB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAW,SAAQ,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAMtB;IACF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,0EAA0E;IAC1E,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;;OAIG;gBACS,OAAO,EAAE,iBAAiB;IA+BtC;;;;;OAKG;cACa,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;
|
|
1
|
+
{"version":3,"file":"worker-pool.d.ts","sourceRoot":"","sources":["../../src/runtime/worker-pool.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AAEtD,OAAO,KAAK,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAMhD;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,4CAA4C;IAC5C,eAAe,EAAE,MAAM,SAAS,CAAC;IAEjC,4CAA4C;IAC5C,UAAU,EAAE,MAAM,CAAC;IAEnB,4EAA4E;IAC5E,UAAU,CAAC,EAAE,MAAM,CAAC;IAEpB,wDAAwD;IACxD,cAAc,CAAC,EAAE,MAAM,CAAC;IAExB,yDAAyD;IACzD,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAEhC;;;OAGG;IACH,aAAa,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;IAExD;;;;OAIG;IACH,wBAAwB,CAAC,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,IAAI,CAAC,CAAC;CACpE;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,wCAAwC;IACxC,SAAS,EAAE,SAAS,CAAC;IAErB,kEAAkE;IAClE,aAAa,EAAE,MAAM,CAAC;CACvB;AAoBD;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AACH,qBAAa,UAAW,SAAQ,cAAc;IAC5C,OAAO,CAAC,QAAQ,CAAC,OAAO,CAMtB;IACF,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAsB;IAC9C,OAAO,CAAC,QAAQ,CAAC,SAAS,CAAsB;IAChD,0EAA0E;IAC1E,OAAO,CAAC,gBAAgB,CAAK;IAE7B;;;;OAIG;gBACS,OAAO,EAAE,iBAAiB;IA+BtC;;;;;OAKG;cACa,MAAM,IAAI,OAAO,CAAC,IAAI,CAAC;IAKvC;;;;;;OAMG;cACa,SAAS,IAAI,OAAO,CAAC,IAAI,CAAC;IAgC1C;;;;;;;;;;;OAWG;IACG,OAAO,IAAI,OAAO,CAAC,YAAY,CAAC;IAqCtC;;;;;;;OAOG;IACH,OAAO,CAAC,MAAM,EAAE,YAAY,GAAG,IAAI;IAWnC;;;;;;;;;;;;;;;OAeG;IACG,UAAU,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,MAAM,EAAE,YAAY,KAAK,OAAO,CAAC,CAAC,CAAC,GAAG,OAAO,CAAC,CAAC,CAAC;IAyBzE;;;;;;;;OAQG;IACH,OAAO,CAAC,kBAAkB;IAa1B;;;;;OAKG;IACH,OAAO,CAAC,YAAY;IAkBpB;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,WAAW,IAAI,MAAM,CAExB;IAED;;OAEG;IACH,IAAI,aAAa,IAAI,MAAM,CAE1B;IAMD;;OAEG;IACH,OAAO,CAAC,mBAAmB;IAI3B,OAAO,CAAC,uBAAuB;IAI/B,OAAO,CAAC,cAAc;YAIR,oBAAoB;YAIpB,YAAY;IAY1B;;;OAGG;IACH,OAAO,CAAC,yBAAyB;YAmBnB,iBAAiB;IAc/B,OAAO,CAAC,sBAAsB;IAe9B;;;;;OAKG;YACW,YAAY;IAoC1B;;OAEG;IACH,OAAO,CAAC,aAAa;CAuBtB"}
|
|
@@ -6,7 +6,7 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @see https://github.com/bbopen/tywrap/issues/149
|
|
8
8
|
*/
|
|
9
|
-
import {
|
|
9
|
+
import { DisposableBase } from './bounded-context.js';
|
|
10
10
|
import { BridgeTimeoutError, BridgeExecutionError, BridgeProtocolError } from './errors.js';
|
|
11
11
|
// =============================================================================
|
|
12
12
|
// WORKER POOL
|
|
@@ -39,7 +39,7 @@ import { BridgeTimeoutError, BridgeExecutionError, BridgeProtocolError } from '.
|
|
|
39
39
|
* await pool.dispose();
|
|
40
40
|
* ```
|
|
41
41
|
*/
|
|
42
|
-
export class WorkerPool extends
|
|
42
|
+
export class WorkerPool extends DisposableBase {
|
|
43
43
|
options;
|
|
44
44
|
workers = [];
|
|
45
45
|
waitQueue = [];
|
|
@@ -84,13 +84,7 @@ export class WorkerPool extends BoundedContext {
|
|
|
84
84
|
*/
|
|
85
85
|
async doInit() {
|
|
86
86
|
// Pre-spawn minimum workers if configured
|
|
87
|
-
|
|
88
|
-
const spawns = [];
|
|
89
|
-
for (let i = 0; i < this.options.minWorkers; i++) {
|
|
90
|
-
spawns.push(this.createWorker());
|
|
91
|
-
}
|
|
92
|
-
await Promise.all(spawns);
|
|
93
|
-
}
|
|
87
|
+
await this.fillToMinimumWorkers();
|
|
94
88
|
}
|
|
95
89
|
/**
|
|
96
90
|
* Dispose the pool and all workers.
|
|
@@ -142,7 +136,7 @@ export class WorkerPool extends BoundedContext {
|
|
|
142
136
|
*/
|
|
143
137
|
async acquire() {
|
|
144
138
|
// Check for disposed state
|
|
145
|
-
if (this.isDisposed) {
|
|
139
|
+
if (this.isDisposed || this.state === 'disposing') {
|
|
146
140
|
throw new BridgeExecutionError('Pool has been disposed');
|
|
147
141
|
}
|
|
148
142
|
// Find an available worker (one with capacity)
|
|
@@ -159,7 +153,12 @@ export class WorkerPool extends BoundedContext {
|
|
|
159
153
|
this.pendingCreations++;
|
|
160
154
|
try {
|
|
161
155
|
const newWorker = await this.createWorker();
|
|
156
|
+
if (this.isShuttingDown()) {
|
|
157
|
+
this.removeWorker(newWorker);
|
|
158
|
+
throw new BridgeExecutionError('Pool has been disposed');
|
|
159
|
+
}
|
|
162
160
|
newWorker.inFlightCount++;
|
|
161
|
+
this.publishAvailableWorker(newWorker);
|
|
163
162
|
return newWorker;
|
|
164
163
|
}
|
|
165
164
|
finally {
|
|
@@ -184,15 +183,7 @@ export class WorkerPool extends BoundedContext {
|
|
|
184
183
|
}
|
|
185
184
|
// Decrement in-flight count (minimum 0)
|
|
186
185
|
worker.inFlightCount = Math.max(0, worker.inFlightCount - 1);
|
|
187
|
-
|
|
188
|
-
if (this.waitQueue.length > 0 && worker.inFlightCount < this.options.maxConcurrentPerWorker) {
|
|
189
|
-
const waiter = this.waitQueue.shift();
|
|
190
|
-
if (waiter) {
|
|
191
|
-
clearTimeout(waiter.timer);
|
|
192
|
-
worker.inFlightCount++;
|
|
193
|
-
waiter.resolve(worker);
|
|
194
|
-
}
|
|
195
|
-
}
|
|
186
|
+
this.publishAvailableWorker(worker);
|
|
196
187
|
}
|
|
197
188
|
/**
|
|
198
189
|
* Execute a function with an acquired worker, automatically releasing afterward.
|
|
@@ -242,14 +233,8 @@ export class WorkerPool extends BoundedContext {
|
|
|
242
233
|
* - Process exited unexpectedly
|
|
243
234
|
* - Pipe errors (EPIPE)
|
|
244
235
|
* - Connection reset errors (ECONNRESET)
|
|
245
|
-
* - Request timeouts (the worker may still be busy with an uncancellable request)
|
|
246
236
|
*/
|
|
247
237
|
isFatalWorkerError(error) {
|
|
248
|
-
// If a request times out, the underlying transport may still be executing it.
|
|
249
|
-
// Quarantine this worker so subsequent requests don't get stuck behind it.
|
|
250
|
-
if (error instanceof BridgeTimeoutError) {
|
|
251
|
-
return true;
|
|
252
|
-
}
|
|
253
238
|
if (error instanceof BridgeProtocolError) {
|
|
254
239
|
const msg = error.message.toLowerCase();
|
|
255
240
|
return (msg.includes('not running') ||
|
|
@@ -273,7 +258,9 @@ export class WorkerPool extends BoundedContext {
|
|
|
273
258
|
worker.transport.dispose().catch(() => {
|
|
274
259
|
// Ignore disposal errors for dead workers
|
|
275
260
|
});
|
|
276
|
-
this.
|
|
261
|
+
if (this.state === 'ready') {
|
|
262
|
+
this.scheduleReplacementWorker();
|
|
263
|
+
}
|
|
277
264
|
}
|
|
278
265
|
}
|
|
279
266
|
// ===========================================================================
|
|
@@ -306,6 +293,27 @@ export class WorkerPool extends BoundedContext {
|
|
|
306
293
|
findAvailableWorker() {
|
|
307
294
|
return this.workers.find(w => w.inFlightCount < this.options.maxConcurrentPerWorker);
|
|
308
295
|
}
|
|
296
|
+
getMinimumWorkerDeficit() {
|
|
297
|
+
return Math.max(0, this.options.minWorkers - (this.workers.length + this.pendingCreations));
|
|
298
|
+
}
|
|
299
|
+
isShuttingDown() {
|
|
300
|
+
return this.isDisposed || this.state === 'disposing';
|
|
301
|
+
}
|
|
302
|
+
async fillToMinimumWorkers() {
|
|
303
|
+
await this.spawnWorkers(this.getMinimumWorkerDeficit());
|
|
304
|
+
}
|
|
305
|
+
async spawnWorkers(count) {
|
|
306
|
+
if (count === 0) {
|
|
307
|
+
return;
|
|
308
|
+
}
|
|
309
|
+
this.pendingCreations += count;
|
|
310
|
+
try {
|
|
311
|
+
await Promise.all(Array.from({ length: count }, () => this.spawnWorkerToPool()));
|
|
312
|
+
}
|
|
313
|
+
finally {
|
|
314
|
+
this.pendingCreations = Math.max(0, this.pendingCreations - count);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
309
317
|
/**
|
|
310
318
|
* Replace a removed worker in the background so the next caller does not pay
|
|
311
319
|
* the full worker cold-start penalty after a timeout or crash.
|
|
@@ -319,11 +327,7 @@ export class WorkerPool extends BoundedContext {
|
|
|
319
327
|
}
|
|
320
328
|
this.pendingCreations++;
|
|
321
329
|
const replacementReady = this.options.onReplacementWorkerReady ?? this.options.onWorkerReady;
|
|
322
|
-
this.
|
|
323
|
-
.then(worker => {
|
|
324
|
-
// Reuse release() to wake queued callers if one is waiting.
|
|
325
|
-
this.release(worker);
|
|
326
|
-
})
|
|
330
|
+
this.spawnWorkerToPool(replacementReady)
|
|
327
331
|
.catch(() => {
|
|
328
332
|
// Ignore background replacement failures. A later acquire() can retry.
|
|
329
333
|
})
|
|
@@ -331,6 +335,29 @@ export class WorkerPool extends BoundedContext {
|
|
|
331
335
|
this.pendingCreations = Math.max(0, this.pendingCreations - 1);
|
|
332
336
|
});
|
|
333
337
|
}
|
|
338
|
+
async spawnWorkerToPool(onWorkerReady = this.options.onWorkerReady) {
|
|
339
|
+
if (this.isShuttingDown()) {
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
const worker = await this.createWorker(onWorkerReady);
|
|
343
|
+
if (this.isShuttingDown()) {
|
|
344
|
+
this.removeWorker(worker);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
this.publishAvailableWorker(worker);
|
|
348
|
+
}
|
|
349
|
+
publishAvailableWorker(worker) {
|
|
350
|
+
while (this.waitQueue.length > 0 &&
|
|
351
|
+
worker.inFlightCount < this.options.maxConcurrentPerWorker) {
|
|
352
|
+
const waiter = this.waitQueue.shift();
|
|
353
|
+
if (!waiter) {
|
|
354
|
+
return;
|
|
355
|
+
}
|
|
356
|
+
clearTimeout(waiter.timer);
|
|
357
|
+
worker.inFlightCount++;
|
|
358
|
+
waiter.resolve(worker);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
334
361
|
/**
|
|
335
362
|
* Create a new worker and add it to the pool.
|
|
336
363
|
*
|
|
@@ -387,33 +414,5 @@ export class WorkerPool extends BoundedContext {
|
|
|
387
414
|
this.waitQueue.push({ resolve, reject, timer });
|
|
388
415
|
});
|
|
389
416
|
}
|
|
390
|
-
// ===========================================================================
|
|
391
|
-
// RUNTIME EXECUTION (Not implemented - WorkerPool is just for worker management)
|
|
392
|
-
// ===========================================================================
|
|
393
|
-
/**
|
|
394
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
395
|
-
* Use the BridgeProtocol layer with a pooled worker's transport.
|
|
396
|
-
*/
|
|
397
|
-
async call(_module, _functionName, _args, _kwargs) {
|
|
398
|
-
throw new BridgeExecutionError('WorkerPool does not implement call() - use withWorker() to get a transport');
|
|
399
|
-
}
|
|
400
|
-
/**
|
|
401
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
402
|
-
*/
|
|
403
|
-
async instantiate(_module, _className, _args, _kwargs) {
|
|
404
|
-
throw new BridgeExecutionError('WorkerPool does not implement instantiate() - use withWorker() to get a transport');
|
|
405
|
-
}
|
|
406
|
-
/**
|
|
407
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
408
|
-
*/
|
|
409
|
-
async callMethod(_handle, _methodName, _args, _kwargs) {
|
|
410
|
-
throw new BridgeExecutionError('WorkerPool does not implement callMethod() - use withWorker() to get a transport');
|
|
411
|
-
}
|
|
412
|
-
/**
|
|
413
|
-
* Not implemented - WorkerPool does not execute Python calls directly.
|
|
414
|
-
*/
|
|
415
|
-
async disposeInstance(_handle) {
|
|
416
|
-
throw new BridgeExecutionError('WorkerPool does not implement disposeInstance() - use withWorker() to get a transport');
|
|
417
|
-
}
|
|
418
417
|
}
|
|
419
418
|
//# sourceMappingURL=worker-pool.js.map
|