@query-farm/vgi-rpc 0.7.3 → 0.7.5
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/dist/index.core.d.ts +12 -0
- package/dist/index.core.d.ts.map +1 -0
- package/dist/index.d.ts +1 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1103 -1105
- package/dist/index.js.map +10 -10
- package/dist/index.workerd.d.ts +2 -0
- package/dist/index.workerd.d.ts.map +1 -0
- package/package.json +6 -6
- package/src/arrow/impl-flechette/index.ts +1 -1
- package/src/arrow/impl-flechette/normalize-type.ts +1 -1
- package/src/index.core.ts +113 -0
- package/src/index.ts +5 -104
- package/src/index.workerd.ts +10 -0
package/dist/index.js
CHANGED
|
@@ -8168,1204 +8168,1197 @@ function mtlsAuthenticateSubject(options) {
|
|
|
8168
8168
|
}
|
|
8169
8169
|
return mtlsAuthenticate({ validate, header, checkExpiry });
|
|
8170
8170
|
}
|
|
8171
|
-
// src/
|
|
8172
|
-
var
|
|
8173
|
-
|
|
8174
|
-
|
|
8175
|
-
|
|
8176
|
-
|
|
8177
|
-
|
|
8178
|
-
|
|
8179
|
-
|
|
8180
|
-
|
|
8181
|
-
|
|
8182
|
-
|
|
8183
|
-
|
|
8184
|
-
|
|
8185
|
-
|
|
8186
|
-
|
|
8187
|
-
const keys = Object.keys(value).sort();
|
|
8188
|
-
const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
|
|
8189
|
-
return `{${parts.join(",")}}`;
|
|
8190
|
-
}
|
|
8191
|
-
throw new TypeError(`canonicalJson: unsupported type ${typeof value}`);
|
|
8171
|
+
// src/schema.ts
|
|
8172
|
+
var str = utf8();
|
|
8173
|
+
var bytes = binary();
|
|
8174
|
+
var int = int64();
|
|
8175
|
+
var int322 = int32();
|
|
8176
|
+
var int162 = int16();
|
|
8177
|
+
var int82 = int8();
|
|
8178
|
+
var uint82 = uint8();
|
|
8179
|
+
var uint162 = uint16();
|
|
8180
|
+
var uint322 = uint32();
|
|
8181
|
+
var uint642 = uint64();
|
|
8182
|
+
var float = float64();
|
|
8183
|
+
var float322 = float32();
|
|
8184
|
+
var bool2 = bool();
|
|
8185
|
+
function isField(x) {
|
|
8186
|
+
return x != null && typeof x.name === "string" && x.type != null && typeof x.nullable === "boolean";
|
|
8192
8187
|
}
|
|
8193
|
-
|
|
8194
|
-
|
|
8195
|
-
new Uint8Array(buf2).set(data);
|
|
8196
|
-
const digest = await crypto.subtle.digest("SHA-256", buf2);
|
|
8197
|
-
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8188
|
+
function isDataType(x) {
|
|
8189
|
+
return x != null && typeof x.typeId === "number";
|
|
8198
8190
|
}
|
|
8199
|
-
|
|
8200
|
-
const
|
|
8201
|
-
|
|
8202
|
-
|
|
8203
|
-
|
|
8204
|
-
|
|
8205
|
-
|
|
8206
|
-
|
|
8207
|
-
|
|
8191
|
+
function toSchema(spec) {
|
|
8192
|
+
const maybeFields = spec.fields;
|
|
8193
|
+
if (Array.isArray(maybeFields)) {
|
|
8194
|
+
const out = [];
|
|
8195
|
+
for (const f of maybeFields) {
|
|
8196
|
+
if (isField(f)) {
|
|
8197
|
+
out.push(f);
|
|
8198
|
+
} else {
|
|
8199
|
+
out.push(field(f.name, f.type, f.nullable ?? true, f.metadata));
|
|
8200
|
+
}
|
|
8208
8201
|
}
|
|
8202
|
+
return schema(out);
|
|
8209
8203
|
}
|
|
8210
|
-
const
|
|
8211
|
-
|
|
8212
|
-
|
|
8213
|
-
|
|
8214
|
-
|
|
8215
|
-
|
|
8216
|
-
|
|
8217
|
-
|
|
8218
|
-
}
|
|
8219
|
-
// src/launcher/launch.ts
|
|
8220
|
-
import { spawn } from "node:child_process";
|
|
8221
|
-
import { createWriteStream, unlinkSync as unlinkSync2 } from "node:fs";
|
|
8222
|
-
|
|
8223
|
-
// src/launcher/lock.ts
|
|
8224
|
-
import { closeSync, constants as FS, openSync, readSync, statSync, writeSync } from "node:fs";
|
|
8225
|
-
var POLL_MS = 50;
|
|
8226
|
-
var VERIFY_RETRIES = 5;
|
|
8227
|
-
function pidAlive(pid) {
|
|
8228
|
-
if (!Number.isInteger(pid) || pid <= 0)
|
|
8229
|
-
return false;
|
|
8230
|
-
try {
|
|
8231
|
-
process.kill(pid, 0);
|
|
8232
|
-
return true;
|
|
8233
|
-
} catch (err2) {
|
|
8234
|
-
return err2?.code === "EPERM";
|
|
8204
|
+
const fields = [];
|
|
8205
|
+
for (const [name, value] of Object.entries(spec)) {
|
|
8206
|
+
if (isField(value)) {
|
|
8207
|
+
fields.push(value);
|
|
8208
|
+
} else if (isDataType(value)) {
|
|
8209
|
+
fields.push(field(name, value, false));
|
|
8210
|
+
} else {
|
|
8211
|
+
throw new TypeError(`Invalid schema value for "${name}": expected DataType or Field, got ${typeof value}`);
|
|
8212
|
+
}
|
|
8235
8213
|
}
|
|
8214
|
+
return schema(fields);
|
|
8236
8215
|
}
|
|
8237
|
-
function
|
|
8238
|
-
|
|
8239
|
-
|
|
8240
|
-
|
|
8241
|
-
|
|
8242
|
-
|
|
8243
|
-
|
|
8244
|
-
|
|
8245
|
-
|
|
8246
|
-
|
|
8247
|
-
|
|
8248
|
-
|
|
8249
|
-
|
|
8250
|
-
|
|
8251
|
-
|
|
8252
|
-
|
|
8216
|
+
function inferParamTypes(spec) {
|
|
8217
|
+
const sch = toSchema(spec);
|
|
8218
|
+
if (sch.fields.length === 0)
|
|
8219
|
+
return;
|
|
8220
|
+
const result = {};
|
|
8221
|
+
for (const f of sch.fields) {
|
|
8222
|
+
let mapped;
|
|
8223
|
+
if (isUtf8(f.type))
|
|
8224
|
+
mapped = "str";
|
|
8225
|
+
else if (isBinary(f.type))
|
|
8226
|
+
mapped = "bytes";
|
|
8227
|
+
else if (isBool(f.type))
|
|
8228
|
+
mapped = "bool";
|
|
8229
|
+
else if (isFloat(f.type))
|
|
8230
|
+
mapped = "float";
|
|
8231
|
+
else if (isInt(f.type))
|
|
8232
|
+
mapped = "int";
|
|
8233
|
+
if (!mapped)
|
|
8234
|
+
return;
|
|
8235
|
+
result[f.name] = mapped;
|
|
8253
8236
|
}
|
|
8237
|
+
return result;
|
|
8254
8238
|
}
|
|
8255
|
-
|
|
8256
|
-
|
|
8257
|
-
|
|
8258
|
-
|
|
8259
|
-
|
|
8260
|
-
|
|
8261
|
-
|
|
8262
|
-
|
|
8263
|
-
|
|
8264
|
-
|
|
8265
|
-
|
|
8266
|
-
|
|
8239
|
+
|
|
8240
|
+
// src/protocol.ts
|
|
8241
|
+
var EMPTY_SCHEMA3 = schema([]);
|
|
8242
|
+
|
|
8243
|
+
class Protocol {
|
|
8244
|
+
name;
|
|
8245
|
+
protocolVersion;
|
|
8246
|
+
protocolVersionParts;
|
|
8247
|
+
_methods = new Map;
|
|
8248
|
+
constructor(name, options) {
|
|
8249
|
+
this.name = name;
|
|
8250
|
+
const raw = options?.protocolVersion;
|
|
8251
|
+
if (raw === undefined || raw === "") {
|
|
8252
|
+
this.protocolVersion = "";
|
|
8253
|
+
this.protocolVersionParts = null;
|
|
8254
|
+
} else {
|
|
8255
|
+
this.protocolVersion = raw;
|
|
8256
|
+
this.protocolVersionParts = parseProtocolVersion(raw);
|
|
8267
8257
|
}
|
|
8268
|
-
|
|
8269
|
-
|
|
8270
|
-
|
|
8271
|
-
|
|
8272
|
-
|
|
8273
|
-
|
|
8258
|
+
}
|
|
8259
|
+
unary(name, config) {
|
|
8260
|
+
const params = toSchema(config.params);
|
|
8261
|
+
this._methods.set(name, {
|
|
8262
|
+
name,
|
|
8263
|
+
type: "unary" /* UNARY */,
|
|
8264
|
+
paramsSchema: params,
|
|
8265
|
+
resultSchema: toSchema(config.result),
|
|
8266
|
+
handler: config.handler,
|
|
8267
|
+
doc: config.doc,
|
|
8268
|
+
defaults: config.defaults,
|
|
8269
|
+
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
8270
|
+
});
|
|
8271
|
+
return this;
|
|
8272
|
+
}
|
|
8273
|
+
producer(name, config) {
|
|
8274
|
+
const params = toSchema(config.params);
|
|
8275
|
+
this._methods.set(name, {
|
|
8276
|
+
name,
|
|
8277
|
+
type: "stream" /* STREAM */,
|
|
8278
|
+
paramsSchema: params,
|
|
8279
|
+
resultSchema: EMPTY_SCHEMA3,
|
|
8280
|
+
outputSchema: toSchema(config.outputSchema),
|
|
8281
|
+
inputSchema: EMPTY_SCHEMA3,
|
|
8282
|
+
producerInit: config.init,
|
|
8283
|
+
producerFn: config.produce,
|
|
8284
|
+
onCancel: config.onCancel,
|
|
8285
|
+
headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
|
|
8286
|
+
headerInit: config.headerInit,
|
|
8287
|
+
doc: config.doc,
|
|
8288
|
+
defaults: config.defaults,
|
|
8289
|
+
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
8290
|
+
});
|
|
8291
|
+
return this;
|
|
8292
|
+
}
|
|
8293
|
+
exchange(name, config) {
|
|
8294
|
+
const params = toSchema(config.params);
|
|
8295
|
+
this._methods.set(name, {
|
|
8296
|
+
name,
|
|
8297
|
+
type: "stream" /* STREAM */,
|
|
8298
|
+
paramsSchema: params,
|
|
8299
|
+
resultSchema: EMPTY_SCHEMA3,
|
|
8300
|
+
inputSchema: toSchema(config.inputSchema),
|
|
8301
|
+
outputSchema: toSchema(config.outputSchema),
|
|
8302
|
+
exchangeInit: config.init,
|
|
8303
|
+
exchangeFn: config.exchange,
|
|
8304
|
+
onCancel: config.onCancel,
|
|
8305
|
+
headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
|
|
8306
|
+
headerInit: config.headerInit,
|
|
8307
|
+
doc: config.doc,
|
|
8308
|
+
defaults: config.defaults,
|
|
8309
|
+
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
8310
|
+
});
|
|
8311
|
+
return this;
|
|
8312
|
+
}
|
|
8313
|
+
getMethods() {
|
|
8314
|
+
return new Map(this._methods);
|
|
8274
8315
|
}
|
|
8275
8316
|
}
|
|
8276
|
-
|
|
8317
|
+
// src/dispatch/stream.ts
|
|
8318
|
+
var EMPTY_SCHEMA4 = schema([]);
|
|
8319
|
+
async function dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, kind) {
|
|
8320
|
+
const isProducer = !!method.producerFn;
|
|
8321
|
+
let state;
|
|
8277
8322
|
try {
|
|
8278
|
-
|
|
8279
|
-
|
|
8280
|
-
|
|
8281
|
-
|
|
8282
|
-
} finally {
|
|
8283
|
-
closeSync(fd);
|
|
8323
|
+
if (isProducer) {
|
|
8324
|
+
state = await method.producerInit(params);
|
|
8325
|
+
} else {
|
|
8326
|
+
state = await method.exchangeInit(params);
|
|
8284
8327
|
}
|
|
8285
|
-
} catch {
|
|
8286
|
-
|
|
8287
|
-
|
|
8288
|
-
|
|
8289
|
-
const
|
|
8290
|
-
if (
|
|
8291
|
-
|
|
8328
|
+
} catch (error) {
|
|
8329
|
+
const errSchema = method.headerSchema ?? EMPTY_SCHEMA4;
|
|
8330
|
+
const errBatch = buildErrorBatch(errSchema, error, serverId, requestId);
|
|
8331
|
+
await writer.writeStream(errSchema, [errBatch]);
|
|
8332
|
+
const inputSchema2 = await reader.openNextStream();
|
|
8333
|
+
if (inputSchema2) {
|
|
8334
|
+
while (await reader.readNextBatch() !== null) {}
|
|
8292
8335
|
}
|
|
8293
|
-
|
|
8294
|
-
continue;
|
|
8295
|
-
const verifyPid = readPid(lockPath);
|
|
8296
|
-
if (verifyPid !== process.pid) {
|
|
8297
|
-
continue;
|
|
8298
|
-
}
|
|
8299
|
-
let released = false;
|
|
8300
|
-
return {
|
|
8301
|
-
path: lockPath,
|
|
8302
|
-
release() {
|
|
8303
|
-
if (released)
|
|
8304
|
-
return;
|
|
8305
|
-
released = true;
|
|
8306
|
-
clearStamp(lockPath);
|
|
8307
|
-
}
|
|
8308
|
-
};
|
|
8336
|
+
return;
|
|
8309
8337
|
}
|
|
8310
|
-
|
|
8311
|
-
|
|
8312
|
-
|
|
8313
|
-
|
|
8314
|
-
|
|
8315
|
-
|
|
8316
|
-
|
|
8317
|
-
|
|
8318
|
-
|
|
8319
|
-
|
|
8338
|
+
const outputSchema = state?.__outputSchema ?? method.outputSchema;
|
|
8339
|
+
const effectiveProducer = state?.__isProducer ?? isProducer;
|
|
8340
|
+
if (method.headerSchema && method.headerInit) {
|
|
8341
|
+
try {
|
|
8342
|
+
const headerOut = new OutputCollector(method.headerSchema, true, serverId, requestId, undefined, undefined, kind);
|
|
8343
|
+
const headerValues = method.headerInit(params, state, headerOut);
|
|
8344
|
+
const headerBatch = buildResultBatch(method.headerSchema, headerValues, serverId, requestId);
|
|
8345
|
+
const headerBatches = [...headerOut.batches.map((b) => b.batch), headerBatch];
|
|
8346
|
+
await writer.writeStream(method.headerSchema, headerBatches);
|
|
8347
|
+
} catch (error) {
|
|
8348
|
+
const errBatch = buildErrorBatch(method.headerSchema, error, serverId, requestId);
|
|
8349
|
+
await writer.writeStream(method.headerSchema, [errBatch]);
|
|
8350
|
+
const inputSchema2 = await reader.openNextStream();
|
|
8351
|
+
if (inputSchema2) {
|
|
8352
|
+
while (await reader.readNextBatch() !== null) {}
|
|
8353
|
+
}
|
|
8354
|
+
return;
|
|
8320
8355
|
}
|
|
8321
|
-
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
8322
8356
|
}
|
|
8323
|
-
|
|
8324
|
-
|
|
8325
|
-
|
|
8326
|
-
|
|
8327
|
-
|
|
8328
|
-
import * as path from "node:path";
|
|
8329
|
-
function socketPaths(stateDir, hashId) {
|
|
8330
|
-
return {
|
|
8331
|
-
lockPath: path.join(stateDir, `${hashId}.lock`),
|
|
8332
|
-
sockPath: path.join(stateDir, `${hashId}.sock`),
|
|
8333
|
-
metaPath: path.join(stateDir, `${hashId}.meta`)
|
|
8334
|
-
};
|
|
8335
|
-
}
|
|
8336
|
-
function defaultStateDir() {
|
|
8337
|
-
let base;
|
|
8338
|
-
if (process.platform === "win32") {
|
|
8339
|
-
base = path.join(tmpdir(), "vgi-rpc");
|
|
8340
|
-
} else {
|
|
8341
|
-
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
8342
|
-
if (xdg) {
|
|
8343
|
-
base = path.join(xdg, "vgi-rpc");
|
|
8344
|
-
} else {
|
|
8345
|
-
const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
|
|
8346
|
-
base = path.join(tmpdir(), `vgi-rpc-${uid}`);
|
|
8347
|
-
}
|
|
8357
|
+
const inputSchema = await reader.openNextStream();
|
|
8358
|
+
if (!inputSchema) {
|
|
8359
|
+
const errBatch = buildErrorBatch(outputSchema, new Error("Expected input stream but got EOF"), serverId, requestId);
|
|
8360
|
+
await writer.writeStream(outputSchema, [errBatch]);
|
|
8361
|
+
return;
|
|
8348
8362
|
}
|
|
8349
|
-
|
|
8350
|
-
|
|
8351
|
-
|
|
8352
|
-
|
|
8353
|
-
|
|
8354
|
-
|
|
8363
|
+
const stream = writer.openStream(outputSchema);
|
|
8364
|
+
const expectedInputSchema = state?.__inputSchema ?? method.inputSchema;
|
|
8365
|
+
try {
|
|
8366
|
+
while (true) {
|
|
8367
|
+
let inputBatch = await reader.readNextBatch();
|
|
8368
|
+
if (!inputBatch)
|
|
8369
|
+
break;
|
|
8370
|
+
if (inputBatch.metadata?.get(CANCEL_KEY)) {
|
|
8371
|
+
if (method.onCancel) {
|
|
8372
|
+
try {
|
|
8373
|
+
await method.onCancel(state);
|
|
8374
|
+
} catch (err2) {
|
|
8375
|
+
console.debug?.(`onCancel hook failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
8376
|
+
}
|
|
8377
|
+
}
|
|
8378
|
+
break;
|
|
8355
8379
|
}
|
|
8356
|
-
|
|
8357
|
-
|
|
8358
|
-
|
|
8380
|
+
if (expectedInputSchema && !effectiveProducer && inputBatch.schema !== expectedInputSchema) {
|
|
8381
|
+
try {
|
|
8382
|
+
inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
|
|
8383
|
+
} catch (e) {
|
|
8384
|
+
if (e instanceof TypeError)
|
|
8385
|
+
throw e;
|
|
8386
|
+
console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
|
|
8387
|
+
}
|
|
8388
|
+
}
|
|
8389
|
+
const out = new OutputCollector(outputSchema, effectiveProducer, serverId, requestId, undefined, undefined, kind);
|
|
8390
|
+
if (isProducer) {
|
|
8391
|
+
await method.producerFn(state, out);
|
|
8392
|
+
} else {
|
|
8393
|
+
await method.exchangeFn(state, inputBatch, out);
|
|
8394
|
+
}
|
|
8395
|
+
for (const emitted of out.batches) {
|
|
8396
|
+
let batch = emitted.batch;
|
|
8397
|
+
if (externalConfig) {
|
|
8398
|
+
batch = await maybeExternalizeBatch(batch, externalConfig);
|
|
8399
|
+
}
|
|
8400
|
+
if (emitted.metadata && emitted.metadata.size > 0) {
|
|
8401
|
+
batch = withBatchMetadata(batch, emitted.metadata);
|
|
8402
|
+
}
|
|
8403
|
+
await stream.write(batch);
|
|
8404
|
+
}
|
|
8405
|
+
if (out.finished) {
|
|
8406
|
+
break;
|
|
8359
8407
|
}
|
|
8360
8408
|
}
|
|
8409
|
+
} catch (error) {
|
|
8410
|
+
await stream.write(buildErrorBatch(outputSchema, error, serverId, requestId));
|
|
8361
8411
|
}
|
|
8362
|
-
|
|
8363
|
-
}
|
|
8364
|
-
function writeMeta(metaPath, workerArgv, cwd, sockPath) {
|
|
8365
|
-
const payload = {
|
|
8366
|
-
cmd: [...workerArgv],
|
|
8367
|
-
cwd,
|
|
8368
|
-
socket: sockPath,
|
|
8369
|
-
started_at: Date.now() / 1000,
|
|
8370
|
-
launcher_pid: process.pid
|
|
8371
|
-
};
|
|
8412
|
+
await stream.close();
|
|
8372
8413
|
try {
|
|
8373
|
-
|
|
8414
|
+
while (await reader.readNextBatch() !== null) {}
|
|
8374
8415
|
} catch {}
|
|
8375
8416
|
}
|
|
8376
|
-
|
|
8377
|
-
|
|
8378
|
-
|
|
8379
|
-
const
|
|
8380
|
-
|
|
8381
|
-
const sock = net.createConnection({ path: sockPath });
|
|
8382
|
-
const timer = setTimeout(() => {
|
|
8383
|
-
sock.destroy();
|
|
8384
|
-
resolve(false);
|
|
8385
|
-
}, timeoutMs);
|
|
8386
|
-
sock.once("connect", () => {
|
|
8387
|
-
clearTimeout(timer);
|
|
8388
|
-
sock.end();
|
|
8389
|
-
resolve(true);
|
|
8390
|
-
});
|
|
8391
|
-
sock.once("error", () => {
|
|
8392
|
-
clearTimeout(timer);
|
|
8393
|
-
resolve(false);
|
|
8394
|
-
});
|
|
8395
|
-
});
|
|
8396
|
-
}
|
|
8397
|
-
function tryReadMeta(metaPath) {
|
|
8417
|
+
|
|
8418
|
+
// src/dispatch/unary.ts
|
|
8419
|
+
async function dispatchUnary(method, params, writer, serverId, requestId, externalConfig, kind) {
|
|
8420
|
+
const schema2 = method.resultSchema;
|
|
8421
|
+
const out = new OutputCollector(schema2, true, serverId, requestId, undefined, undefined, kind);
|
|
8398
8422
|
try {
|
|
8399
|
-
const
|
|
8400
|
-
|
|
8401
|
-
|
|
8402
|
-
|
|
8403
|
-
|
|
8404
|
-
|
|
8405
|
-
|
|
8406
|
-
} catch {
|
|
8407
|
-
|
|
8423
|
+
const result = await method.handler(params, out);
|
|
8424
|
+
let resultBatch = buildResultBatch(schema2, result, serverId, requestId);
|
|
8425
|
+
if (externalConfig) {
|
|
8426
|
+
resultBatch = await maybeExternalizeBatch(resultBatch, externalConfig);
|
|
8427
|
+
}
|
|
8428
|
+
const batches = [...out.batches.map((b) => b.batch), resultBatch];
|
|
8429
|
+
await writer.writeStream(schema2, batches);
|
|
8430
|
+
} catch (error) {
|
|
8431
|
+
const batch = buildErrorBatch(schema2, error, serverId, requestId);
|
|
8432
|
+
await writer.writeStream(schema2, [batch]);
|
|
8408
8433
|
}
|
|
8409
8434
|
}
|
|
8410
|
-
|
|
8411
|
-
|
|
8412
|
-
|
|
8413
|
-
|
|
8414
|
-
|
|
8415
|
-
|
|
8416
|
-
|
|
8417
|
-
return
|
|
8435
|
+
|
|
8436
|
+
// src/wire/writer.ts
|
|
8437
|
+
var STDOUT_FD = 1;
|
|
8438
|
+
var _NODE_FS_MOD2 = "node:fs";
|
|
8439
|
+
var _writeSync = null;
|
|
8440
|
+
function _loadWriteSync2() {
|
|
8441
|
+
if (_writeSync)
|
|
8442
|
+
return _writeSync;
|
|
8443
|
+
const req = import.meta.require ?? globalThis.require ?? null;
|
|
8444
|
+
if (!req) {
|
|
8445
|
+
throw new Error("IpcStreamWriter requires Bun or Node.js CJS for sync node:fs.writeSync. " + "Subprocess transport is not available in this runtime.");
|
|
8418
8446
|
}
|
|
8419
|
-
|
|
8420
|
-
|
|
8421
|
-
|
|
8422
|
-
|
|
8423
|
-
|
|
8424
|
-
|
|
8425
|
-
|
|
8426
|
-
|
|
8427
|
-
|
|
8428
|
-
|
|
8429
|
-
|
|
8430
|
-
|
|
8431
|
-
|
|
8432
|
-
})
|
|
8447
|
+
const fs = req(_NODE_FS_MOD2);
|
|
8448
|
+
_writeSync = fs.writeSync.bind(fs);
|
|
8449
|
+
return _writeSync;
|
|
8450
|
+
}
|
|
8451
|
+
function writeAll(fd, data) {
|
|
8452
|
+
const writeSync = _loadWriteSync2();
|
|
8453
|
+
let offset = 0;
|
|
8454
|
+
while (offset < data.length) {
|
|
8455
|
+
try {
|
|
8456
|
+
const written = writeSync(fd, data, offset, data.length - offset);
|
|
8457
|
+
if (written <= 0)
|
|
8458
|
+
throw new Error(`writeSync returned ${written}`);
|
|
8459
|
+
offset += written;
|
|
8460
|
+
} catch (e) {
|
|
8461
|
+
if (e.code === "EAGAIN") {
|
|
8462
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, 1);
|
|
8463
|
+
continue;
|
|
8464
|
+
}
|
|
8465
|
+
throw e;
|
|
8466
|
+
}
|
|
8433
8467
|
}
|
|
8434
|
-
return rows;
|
|
8435
8468
|
}
|
|
8436
|
-
async function
|
|
8437
|
-
|
|
8438
|
-
|
|
8439
|
-
const skipped = [];
|
|
8440
|
-
const limit = options?.limit ?? null;
|
|
8441
|
-
const excludeHash = options?.excludeHash ?? null;
|
|
8442
|
-
let entries;
|
|
8443
|
-
try {
|
|
8444
|
-
entries = readdirSync(stateDir);
|
|
8445
|
-
} catch {
|
|
8446
|
-
return { cleaned, skippedInUse: skipped };
|
|
8469
|
+
async function socketWriteAll(socket, data) {
|
|
8470
|
+
if (socket.destroyed || socket.writableEnded) {
|
|
8471
|
+
throw new Error("socketWriteAll: socket is already closed");
|
|
8447
8472
|
}
|
|
8448
|
-
|
|
8449
|
-
|
|
8450
|
-
|
|
8451
|
-
|
|
8452
|
-
|
|
8453
|
-
|
|
8454
|
-
|
|
8455
|
-
|
|
8456
|
-
|
|
8457
|
-
|
|
8458
|
-
|
|
8459
|
-
|
|
8460
|
-
|
|
8461
|
-
|
|
8462
|
-
|
|
8473
|
+
const ok = socket.write(data);
|
|
8474
|
+
if (ok)
|
|
8475
|
+
return;
|
|
8476
|
+
await new Promise((resolve, reject) => {
|
|
8477
|
+
const cleanup = () => {
|
|
8478
|
+
socket.off("drain", onDrain);
|
|
8479
|
+
socket.off("error", onError);
|
|
8480
|
+
socket.off("close", onClose);
|
|
8481
|
+
};
|
|
8482
|
+
const onDrain = () => {
|
|
8483
|
+
cleanup();
|
|
8484
|
+
resolve();
|
|
8485
|
+
};
|
|
8486
|
+
const onError = (err2) => {
|
|
8487
|
+
cleanup();
|
|
8488
|
+
reject(err2);
|
|
8489
|
+
};
|
|
8490
|
+
const onClose = () => {
|
|
8491
|
+
cleanup();
|
|
8492
|
+
resolve();
|
|
8493
|
+
};
|
|
8494
|
+
socket.once("drain", onDrain);
|
|
8495
|
+
socket.once("error", onError);
|
|
8496
|
+
socket.once("close", onClose);
|
|
8497
|
+
});
|
|
8498
|
+
}
|
|
8499
|
+
|
|
8500
|
+
class IpcStreamWriter {
|
|
8501
|
+
target;
|
|
8502
|
+
constructor(fdOrSocket = STDOUT_FD) {
|
|
8503
|
+
if (typeof fdOrSocket === "number") {
|
|
8504
|
+
this.target = { kind: "fd", fd: fdOrSocket };
|
|
8505
|
+
} else {
|
|
8506
|
+
this.target = { kind: "socket", socket: fdOrSocket };
|
|
8463
8507
|
}
|
|
8464
|
-
|
|
8465
|
-
|
|
8466
|
-
|
|
8467
|
-
|
|
8468
|
-
|
|
8469
|
-
|
|
8470
|
-
|
|
8471
|
-
} catch {}
|
|
8472
|
-
}
|
|
8473
|
-
cleaned.push(hashId);
|
|
8474
|
-
} finally {
|
|
8475
|
-
release();
|
|
8508
|
+
}
|
|
8509
|
+
async writeStream(schema2, batches) {
|
|
8510
|
+
const bytes2 = serializeBatches(schema2, batches);
|
|
8511
|
+
if (this.target.kind === "fd") {
|
|
8512
|
+
writeAll(this.target.fd, bytes2);
|
|
8513
|
+
} else {
|
|
8514
|
+
await socketWriteAll(this.target.socket, bytes2);
|
|
8476
8515
|
}
|
|
8477
8516
|
}
|
|
8478
|
-
|
|
8517
|
+
openStream(schema2) {
|
|
8518
|
+
return new IncrementalStream(this.target, schema2);
|
|
8519
|
+
}
|
|
8479
8520
|
}
|
|
8480
8521
|
|
|
8481
|
-
|
|
8482
|
-
|
|
8483
|
-
|
|
8484
|
-
|
|
8485
|
-
|
|
8522
|
+
class IncrementalStream {
|
|
8523
|
+
encoder;
|
|
8524
|
+
target;
|
|
8525
|
+
closed = false;
|
|
8526
|
+
writeChain = Promise.resolve();
|
|
8527
|
+
constructor(target, schema2) {
|
|
8528
|
+
this.target = target;
|
|
8529
|
+
this.encoder = createIncrementalEncoder(schema2);
|
|
8530
|
+
this.enqueue(this.encoder.start());
|
|
8486
8531
|
}
|
|
8487
|
-
|
|
8488
|
-
|
|
8489
|
-
|
|
8490
|
-
|
|
8491
|
-
let lockPath;
|
|
8492
|
-
let sockPath;
|
|
8493
|
-
let metaPath;
|
|
8494
|
-
let hashId;
|
|
8495
|
-
if (config.socketPath !== undefined) {
|
|
8496
|
-
const { resolve } = await import("node:path");
|
|
8497
|
-
sockPath = resolve(config.socketPath);
|
|
8498
|
-
lockPath = `${sockPath}.lock`;
|
|
8499
|
-
metaPath = null;
|
|
8500
|
-
hashId = null;
|
|
8501
|
-
} else {
|
|
8502
|
-
hashId = await computeHash(config.workerArgv);
|
|
8503
|
-
const paths = socketPaths(stateDir, hashId);
|
|
8504
|
-
lockPath = paths.lockPath;
|
|
8505
|
-
sockPath = paths.sockPath;
|
|
8506
|
-
metaPath = paths.metaPath;
|
|
8532
|
+
async write(batch) {
|
|
8533
|
+
if (this.closed)
|
|
8534
|
+
throw new Error("Stream already closed");
|
|
8535
|
+
return this.enqueue(this.encoder.writeBatch(batch));
|
|
8507
8536
|
}
|
|
8508
|
-
|
|
8509
|
-
|
|
8510
|
-
|
|
8511
|
-
|
|
8512
|
-
|
|
8513
|
-
|
|
8514
|
-
|
|
8515
|
-
|
|
8516
|
-
|
|
8517
|
-
|
|
8518
|
-
|
|
8519
|
-
|
|
8520
|
-
|
|
8521
|
-
|
|
8522
|
-
|
|
8523
|
-
|
|
8524
|
-
|
|
8525
|
-
|
|
8526
|
-
const h = tryAcquireLock(p);
|
|
8527
|
-
return h ? () => h.release() : null;
|
|
8528
|
-
}, { limit: DEFAULT_GC_LIMIT, excludeHash: hashId });
|
|
8529
|
-
} catch {}
|
|
8530
|
-
}
|
|
8537
|
+
async close() {
|
|
8538
|
+
if (this.closed)
|
|
8539
|
+
return;
|
|
8540
|
+
this.closed = true;
|
|
8541
|
+
return this.enqueue(this.encoder.finish());
|
|
8542
|
+
}
|
|
8543
|
+
enqueue(bytes2) {
|
|
8544
|
+
const next = this.writeChain.then(() => {
|
|
8545
|
+
if (this.target.kind === "fd") {
|
|
8546
|
+
writeAll(this.target.fd, bytes2);
|
|
8547
|
+
return;
|
|
8548
|
+
}
|
|
8549
|
+
return socketWriteAll(this.target.socket, bytes2);
|
|
8550
|
+
});
|
|
8551
|
+
this.writeChain = next.catch(() => {
|
|
8552
|
+
return;
|
|
8553
|
+
});
|
|
8554
|
+
return next;
|
|
8531
8555
|
}
|
|
8532
8556
|
}
|
|
8533
|
-
|
|
8534
|
-
|
|
8535
|
-
|
|
8536
|
-
|
|
8537
|
-
const
|
|
8538
|
-
|
|
8539
|
-
|
|
8540
|
-
|
|
8541
|
-
|
|
8542
|
-
const sink = createWriteStream(workerStderr, { flags: "a" });
|
|
8543
|
-
proc.stderr.pipe(sink);
|
|
8557
|
+
|
|
8558
|
+
// src/server.ts
|
|
8559
|
+
var EMPTY_SCHEMA5 = schema([]);
|
|
8560
|
+
function randomStreamId() {
|
|
8561
|
+
const bytes2 = new Uint8Array(16);
|
|
8562
|
+
crypto.getRandomValues(bytes2);
|
|
8563
|
+
let out = "";
|
|
8564
|
+
for (let i = 0;i < bytes2.length; i++) {
|
|
8565
|
+
out += bytes2[i].toString(16).padStart(2, "0");
|
|
8544
8566
|
}
|
|
8545
|
-
|
|
8546
|
-
|
|
8547
|
-
|
|
8548
|
-
|
|
8549
|
-
|
|
8550
|
-
|
|
8551
|
-
|
|
8552
|
-
|
|
8553
|
-
|
|
8554
|
-
|
|
8555
|
-
|
|
8556
|
-
|
|
8567
|
+
return out;
|
|
8568
|
+
}
|
|
8569
|
+
|
|
8570
|
+
class VgiRpcServer {
|
|
8571
|
+
protocol;
|
|
8572
|
+
enableDescribe;
|
|
8573
|
+
serverId;
|
|
8574
|
+
_describePromise = null;
|
|
8575
|
+
protocolVersion;
|
|
8576
|
+
dispatchHook = null;
|
|
8577
|
+
externalConfig;
|
|
8578
|
+
onServeStart = null;
|
|
8579
|
+
serveStartFired = false;
|
|
8580
|
+
constructor(protocol, options) {
|
|
8581
|
+
this.protocol = protocol;
|
|
8582
|
+
this.enableDescribe = options?.enableDescribe ?? true;
|
|
8583
|
+
this.serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
8584
|
+
this.dispatchHook = options?.dispatchHook ?? null;
|
|
8585
|
+
this.externalConfig = options?.externalLocation;
|
|
8586
|
+
this.protocolVersion = options?.protocolVersion ?? "";
|
|
8587
|
+
this.onServeStart = options?.onServeStart ?? null;
|
|
8588
|
+
}
|
|
8589
|
+
async notifyTransport(kind) {
|
|
8590
|
+
if (this.serveStartFired)
|
|
8591
|
+
return;
|
|
8592
|
+
if (this.onServeStart) {
|
|
8593
|
+
await this.onServeStart(kind);
|
|
8557
8594
|
}
|
|
8558
|
-
|
|
8559
|
-
|
|
8560
|
-
|
|
8595
|
+
this.serveStartFired = true;
|
|
8596
|
+
}
|
|
8597
|
+
async describeInfo() {
|
|
8598
|
+
if (!this._describePromise) {
|
|
8599
|
+
this._describePromise = buildDescribeBatch(this.protocol.name, this.protocol.getMethods(), this.serverId, this.protocol.protocolVersion || undefined).then(({ batch, metadata }) => ({
|
|
8600
|
+
batch,
|
|
8601
|
+
protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? ""
|
|
8602
|
+
}));
|
|
8561
8603
|
}
|
|
8562
|
-
|
|
8563
|
-
|
|
8564
|
-
|
|
8604
|
+
return this._describePromise;
|
|
8605
|
+
}
|
|
8606
|
+
checkProtocolVersion(clientVersion) {
|
|
8607
|
+
const serverParts = this.protocol.protocolVersionParts;
|
|
8608
|
+
const serverVersion = this.protocol.protocolVersion;
|
|
8609
|
+
if (clientVersion === undefined) {
|
|
8610
|
+
throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
|
|
8611
|
+
` + ` Client: <not declared>
|
|
8612
|
+
` + ` Server: ${serverVersion}
|
|
8613
|
+
` + " Direction: the client did not send a vgi_rpc.protocol_version " + "metadata key. This is either a vgi-rpc framework bug or a " + "non-VGI client connecting to a VGI worker.");
|
|
8565
8614
|
}
|
|
8566
|
-
|
|
8567
|
-
|
|
8568
|
-
|
|
8569
|
-
|
|
8570
|
-
|
|
8571
|
-
|
|
8572
|
-
|
|
8615
|
+
let clientParts;
|
|
8616
|
+
try {
|
|
8617
|
+
clientParts = parseProtocolVersion(clientVersion);
|
|
8618
|
+
} catch {
|
|
8619
|
+
throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
|
|
8620
|
+
` + ` Client: ${clientVersion}
|
|
8621
|
+
` + ` Server: ${serverVersion}
|
|
8622
|
+
` + " Direction: client sent a malformed protocol_version. " + "Expected canonical semver MAJOR.MINOR.PATCH.");
|
|
8623
|
+
}
|
|
8624
|
+
if (clientParts[0] === serverParts[0] && clientParts[1] === serverParts[1]) {
|
|
8573
8625
|
return;
|
|
8574
8626
|
}
|
|
8575
|
-
|
|
8576
|
-
`
|
|
8627
|
+
const clientOlder = clientParts[0] < serverParts[0] || clientParts[0] === serverParts[0] && clientParts[1] < serverParts[1];
|
|
8628
|
+
const direction = clientOlder ? `client is too old; upgrade the VGI extension/client to a version supporting protocol_version ${serverVersion}.` : `server is too old; upgrade the VGI worker to a version supporting protocol_version ${clientVersion}.`;
|
|
8629
|
+
throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
|
|
8630
|
+
` + ` Client: ${clientVersion}
|
|
8631
|
+
` + ` Server: ${serverVersion}
|
|
8632
|
+
` + ` Direction: ${direction}`);
|
|
8577
8633
|
}
|
|
8578
|
-
|
|
8579
|
-
|
|
8580
|
-
|
|
8581
|
-
|
|
8582
|
-
|
|
8583
|
-
let ended = false;
|
|
8584
|
-
const queued = [];
|
|
8585
|
-
const waiters = [];
|
|
8586
|
-
let discardMode = false;
|
|
8587
|
-
const flushWaiter = () => {
|
|
8588
|
-
if (waiters.length === 0)
|
|
8589
|
-
return;
|
|
8590
|
-
if (queued.length > 0) {
|
|
8591
|
-
const w = waiters.shift();
|
|
8592
|
-
w?.({ done: false, value: queued.shift() ?? "" });
|
|
8593
|
-
} else if (ended) {
|
|
8594
|
-
const w = waiters.shift();
|
|
8595
|
-
w?.({ done: true, value: "" });
|
|
8596
|
-
}
|
|
8597
|
-
};
|
|
8598
|
-
stream.setEncoding?.("utf8");
|
|
8599
|
-
stream.on("data", (chunk) => {
|
|
8600
|
-
if (discardMode)
|
|
8601
|
-
return;
|
|
8602
|
-
buffer += String(chunk);
|
|
8603
|
-
for (;; ) {
|
|
8604
|
-
const nl = buffer.indexOf(`
|
|
8634
|
+
async run() {
|
|
8635
|
+
const stdin = process.stdin;
|
|
8636
|
+
if (process.stdin.isTTY || process.stdout.isTTY) {
|
|
8637
|
+
process.stderr.write("WARNING: This process communicates via Arrow IPC on stdin/stdout " + `and is not intended to be run interactively.
|
|
8638
|
+
` + "It should be launched as a subprocess by an RPC client " + `(e.g. vgi_rpc.connect()).
|
|
8605
8639
|
`);
|
|
8606
|
-
if (nl < 0)
|
|
8607
|
-
break;
|
|
8608
|
-
const line = buffer.slice(0, nl).replace(/\r$/, "");
|
|
8609
|
-
buffer = buffer.slice(nl + 1);
|
|
8610
|
-
queued.push(line);
|
|
8611
|
-
}
|
|
8612
|
-
flushWaiter();
|
|
8613
|
-
});
|
|
8614
|
-
stream.on("end", () => {
|
|
8615
|
-
ended = true;
|
|
8616
|
-
if (buffer.length > 0) {
|
|
8617
|
-
queued.push(buffer.replace(/\r$/, ""));
|
|
8618
|
-
buffer = "";
|
|
8619
8640
|
}
|
|
8620
|
-
|
|
8621
|
-
|
|
8622
|
-
|
|
8623
|
-
|
|
8624
|
-
|
|
8625
|
-
|
|
8626
|
-
|
|
8627
|
-
|
|
8628
|
-
|
|
8629
|
-
|
|
8630
|
-
|
|
8631
|
-
|
|
8632
|
-
}
|
|
8633
|
-
|
|
8634
|
-
discardMode = true;
|
|
8635
|
-
queued.length = 0;
|
|
8636
|
-
stream.resume?.();
|
|
8641
|
+
const reader = await IpcStreamReader.create(stdin);
|
|
8642
|
+
const writer = new IpcStreamWriter;
|
|
8643
|
+
try {
|
|
8644
|
+
while (true) {
|
|
8645
|
+
await this.notifyTransport("pipe" /* PIPE */);
|
|
8646
|
+
await this.serveOne(reader, writer);
|
|
8647
|
+
}
|
|
8648
|
+
} catch (e) {
|
|
8649
|
+
if (e.message?.includes("closed") || e.message?.includes("Expected Schema Message") || e.message?.includes("null or length 0") || e.code === "EPIPE" || e.code === "ERR_STREAM_PREMATURE_CLOSE" || e.code === "ERR_STREAM_DESTROYED" || e instanceof Error && e.message.includes("EOF")) {
|
|
8650
|
+
return;
|
|
8651
|
+
}
|
|
8652
|
+
throw e;
|
|
8653
|
+
} finally {
|
|
8654
|
+
await reader.cancel();
|
|
8637
8655
|
}
|
|
8638
|
-
}
|
|
8639
|
-
|
|
8640
|
-
|
|
8641
|
-
|
|
8642
|
-
|
|
8643
|
-
});
|
|
8644
|
-
}
|
|
8645
|
-
function delay(ms) {
|
|
8646
|
-
return new Promise((r) => setTimeout(r, Math.max(0, ms)));
|
|
8647
|
-
}
|
|
8648
|
-
// src/launcher/serve-unix.ts
|
|
8649
|
-
import { existsSync as existsSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
8650
|
-
import { createServer } from "node:net";
|
|
8651
|
-
import * as path2 from "node:path";
|
|
8652
|
-
|
|
8653
|
-
// src/dispatch/stream.ts
|
|
8654
|
-
var EMPTY_SCHEMA3 = schema([]);
|
|
8655
|
-
async function dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, kind) {
|
|
8656
|
-
const isProducer = !!method.producerFn;
|
|
8657
|
-
let state;
|
|
8658
|
-
try {
|
|
8659
|
-
if (isProducer) {
|
|
8660
|
-
state = await method.producerInit(params);
|
|
8661
|
-
} else {
|
|
8662
|
-
state = await method.exchangeInit(params);
|
|
8656
|
+
}
|
|
8657
|
+
async serveOne(reader, writer) {
|
|
8658
|
+
const stream = await reader.readStream();
|
|
8659
|
+
if (!stream) {
|
|
8660
|
+
throw new Error("EOF");
|
|
8663
8661
|
}
|
|
8664
|
-
|
|
8665
|
-
|
|
8666
|
-
|
|
8667
|
-
|
|
8668
|
-
|
|
8669
|
-
|
|
8670
|
-
while (await reader.readNextBatch() !== null) {}
|
|
8662
|
+
const { schema: schema2, batches } = stream;
|
|
8663
|
+
if (batches.length === 0) {
|
|
8664
|
+
const err2 = new RpcError("ProtocolError", "Request stream contains no batches", "");
|
|
8665
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA5, err2, this.serverId, null);
|
|
8666
|
+
await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
|
|
8667
|
+
return;
|
|
8671
8668
|
}
|
|
8672
|
-
|
|
8673
|
-
|
|
8674
|
-
|
|
8675
|
-
|
|
8676
|
-
if (method.headerSchema && method.headerInit) {
|
|
8669
|
+
const batch = batches[0];
|
|
8670
|
+
let methodName;
|
|
8671
|
+
let params;
|
|
8672
|
+
let requestId;
|
|
8677
8673
|
try {
|
|
8678
|
-
const
|
|
8679
|
-
|
|
8680
|
-
|
|
8681
|
-
|
|
8682
|
-
|
|
8683
|
-
|
|
8684
|
-
|
|
8685
|
-
|
|
8686
|
-
|
|
8687
|
-
if (inputSchema2) {
|
|
8688
|
-
while (await reader.readNextBatch() !== null) {}
|
|
8674
|
+
const parsed = parseRequest(schema2, batch);
|
|
8675
|
+
methodName = parsed.methodName;
|
|
8676
|
+
params = parsed.params;
|
|
8677
|
+
requestId = parsed.requestId;
|
|
8678
|
+
} catch (e) {
|
|
8679
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA5, e, this.serverId, null);
|
|
8680
|
+
await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
|
|
8681
|
+
if (e instanceof VersionError || e instanceof RpcError) {
|
|
8682
|
+
return;
|
|
8689
8683
|
}
|
|
8684
|
+
throw e;
|
|
8685
|
+
}
|
|
8686
|
+
if (methodName === DESCRIBE_METHOD_NAME && this.enableDescribe) {
|
|
8687
|
+
const { batch: batch2 } = await this.describeInfo();
|
|
8688
|
+
await writer.writeStream(batch2.schema, [batch2]);
|
|
8690
8689
|
return;
|
|
8691
8690
|
}
|
|
8692
|
-
|
|
8693
|
-
|
|
8694
|
-
|
|
8695
|
-
|
|
8696
|
-
|
|
8697
|
-
|
|
8698
|
-
|
|
8699
|
-
|
|
8700
|
-
|
|
8701
|
-
|
|
8702
|
-
|
|
8703
|
-
|
|
8704
|
-
|
|
8705
|
-
|
|
8706
|
-
|
|
8707
|
-
|
|
8708
|
-
|
|
8709
|
-
|
|
8710
|
-
} catch (err2) {
|
|
8711
|
-
console.debug?.(`onCancel hook failed: ${err2 instanceof Error ? err2.message : err2}`);
|
|
8712
|
-
}
|
|
8713
|
-
}
|
|
8714
|
-
break;
|
|
8715
|
-
}
|
|
8716
|
-
if (expectedInputSchema && !effectiveProducer && inputBatch.schema !== expectedInputSchema) {
|
|
8717
|
-
try {
|
|
8718
|
-
inputBatch = conformBatchToSchema(inputBatch, expectedInputSchema);
|
|
8719
|
-
} catch (e) {
|
|
8720
|
-
if (e instanceof TypeError)
|
|
8721
|
-
throw e;
|
|
8722
|
-
console.debug?.(`Schema conformance skipped: ${e instanceof Error ? e.message : e}`);
|
|
8723
|
-
}
|
|
8691
|
+
const methods = this.protocol.getMethods();
|
|
8692
|
+
const method = methods.get(methodName);
|
|
8693
|
+
if (!method) {
|
|
8694
|
+
const available = [...methods.keys()].sort();
|
|
8695
|
+
const err2 = new MethodNotImplementedError(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
|
|
8696
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA5, err2, this.serverId, requestId);
|
|
8697
|
+
await writer.writeStream(EMPTY_SCHEMA5, [errBatch]);
|
|
8698
|
+
return;
|
|
8699
|
+
}
|
|
8700
|
+
if (this.protocol.protocolVersionParts !== null) {
|
|
8701
|
+
try {
|
|
8702
|
+
const md = batch.metadata;
|
|
8703
|
+
this.checkProtocolVersion(md?.get(PROTOCOL_VERSION_KEY));
|
|
8704
|
+
} catch (exc) {
|
|
8705
|
+
const errSchema = method.type === "unary" /* UNARY */ ? method.resultSchema : EMPTY_SCHEMA5;
|
|
8706
|
+
const errBatch = buildErrorBatch(errSchema, exc, this.serverId, requestId);
|
|
8707
|
+
await writer.writeStream(errSchema, [errBatch]);
|
|
8708
|
+
return;
|
|
8724
8709
|
}
|
|
8725
|
-
|
|
8726
|
-
|
|
8727
|
-
|
|
8710
|
+
}
|
|
8711
|
+
const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
|
|
8712
|
+
let requestData;
|
|
8713
|
+
try {
|
|
8714
|
+
requestData = serializeBatch(batch);
|
|
8715
|
+
} catch {}
|
|
8716
|
+
let streamId;
|
|
8717
|
+
if (methodType === "stream") {
|
|
8718
|
+
streamId = randomStreamId();
|
|
8719
|
+
}
|
|
8720
|
+
const { protocolHash } = await this.describeInfo();
|
|
8721
|
+
const info = {
|
|
8722
|
+
method: methodName,
|
|
8723
|
+
methodType,
|
|
8724
|
+
serverId: this.serverId,
|
|
8725
|
+
requestId,
|
|
8726
|
+
protocol: this.protocol.name,
|
|
8727
|
+
protocolHash,
|
|
8728
|
+
protocolVersion: this.protocolVersion,
|
|
8729
|
+
kind: "pipe" /* PIPE */,
|
|
8730
|
+
principal: "",
|
|
8731
|
+
authDomain: "",
|
|
8732
|
+
authenticated: false,
|
|
8733
|
+
remoteAddr: "",
|
|
8734
|
+
requestData,
|
|
8735
|
+
streamId
|
|
8736
|
+
};
|
|
8737
|
+
const stats = {
|
|
8738
|
+
inputBatches: 0,
|
|
8739
|
+
outputBatches: 0,
|
|
8740
|
+
inputRows: 0,
|
|
8741
|
+
outputRows: 0,
|
|
8742
|
+
inputBytes: 0,
|
|
8743
|
+
outputBytes: 0
|
|
8744
|
+
};
|
|
8745
|
+
const token = this.dispatchHook?.onDispatchStart(info);
|
|
8746
|
+
let dispatchError;
|
|
8747
|
+
applyDefaults(params, method.defaults);
|
|
8748
|
+
try {
|
|
8749
|
+
if (method.type === "unary" /* UNARY */) {
|
|
8750
|
+
await dispatchUnary(method, params, writer, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
|
|
8728
8751
|
} else {
|
|
8729
|
-
await method.
|
|
8730
|
-
}
|
|
8731
|
-
for (const emitted of out.batches) {
|
|
8732
|
-
let batch = emitted.batch;
|
|
8733
|
-
if (externalConfig) {
|
|
8734
|
-
batch = await maybeExternalizeBatch(batch, externalConfig);
|
|
8735
|
-
}
|
|
8736
|
-
if (emitted.metadata && emitted.metadata.size > 0) {
|
|
8737
|
-
batch = withBatchMetadata(batch, emitted.metadata);
|
|
8738
|
-
}
|
|
8739
|
-
await stream.write(batch);
|
|
8740
|
-
}
|
|
8741
|
-
if (out.finished) {
|
|
8742
|
-
break;
|
|
8752
|
+
await dispatchStream(method, params, writer, reader, this.serverId, requestId, this.externalConfig, "pipe" /* PIPE */);
|
|
8743
8753
|
}
|
|
8754
|
+
} catch (e) {
|
|
8755
|
+
dispatchError = e instanceof Error ? e : new Error(String(e));
|
|
8756
|
+
throw e;
|
|
8757
|
+
} finally {
|
|
8758
|
+
this.dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
|
|
8744
8759
|
}
|
|
8745
|
-
} catch (error) {
|
|
8746
|
-
await stream.write(buildErrorBatch(outputSchema, error, serverId, requestId));
|
|
8747
8760
|
}
|
|
8748
|
-
await stream.close();
|
|
8749
|
-
try {
|
|
8750
|
-
while (await reader.readNextBatch() !== null) {}
|
|
8751
|
-
} catch {}
|
|
8752
8761
|
}
|
|
8753
|
-
|
|
8754
|
-
|
|
8755
|
-
|
|
8756
|
-
|
|
8757
|
-
|
|
8758
|
-
|
|
8759
|
-
|
|
8760
|
-
|
|
8761
|
-
|
|
8762
|
-
|
|
8762
|
+
// src/launcher/hash.ts
|
|
8763
|
+
var HASH_LEN = 16;
|
|
8764
|
+
function canonicalJson(value) {
|
|
8765
|
+
if (value === null)
|
|
8766
|
+
return "null";
|
|
8767
|
+
if (typeof value === "boolean")
|
|
8768
|
+
return value ? "true" : "false";
|
|
8769
|
+
if (typeof value === "number") {
|
|
8770
|
+
return JSON.stringify(value);
|
|
8771
|
+
}
|
|
8772
|
+
if (typeof value === "string")
|
|
8773
|
+
return JSON.stringify(value);
|
|
8774
|
+
if (Array.isArray(value)) {
|
|
8775
|
+
return `[${value.map(canonicalJson).join(",")}]`;
|
|
8776
|
+
}
|
|
8777
|
+
if (typeof value === "object") {
|
|
8778
|
+
const keys = Object.keys(value).sort();
|
|
8779
|
+
const parts = keys.map((k) => `${JSON.stringify(k)}:${canonicalJson(value[k])}`);
|
|
8780
|
+
return `{${parts.join(",")}}`;
|
|
8781
|
+
}
|
|
8782
|
+
throw new TypeError(`canonicalJson: unsupported type ${typeof value}`);
|
|
8783
|
+
}
|
|
8784
|
+
async function sha256Hex3(data) {
|
|
8785
|
+
const buf2 = new ArrayBuffer(data.byteLength);
|
|
8786
|
+
new Uint8Array(buf2).set(data);
|
|
8787
|
+
const digest = await crypto.subtle.digest("SHA-256", buf2);
|
|
8788
|
+
return Array.from(new Uint8Array(digest)).map((b) => b.toString(16).padStart(2, "0")).join("");
|
|
8789
|
+
}
|
|
8790
|
+
async function computeHash(workerArgv, cwd, env) {
|
|
8791
|
+
const cwdValue = cwd !== undefined ? cwd : process.cwd();
|
|
8792
|
+
const sourceEnv = env ?? process.env;
|
|
8793
|
+
const filteredEnv = {};
|
|
8794
|
+
for (const key of Object.keys(sourceEnv)) {
|
|
8795
|
+
if (key.startsWith("VGI_RPC_")) {
|
|
8796
|
+
const v = sourceEnv[key];
|
|
8797
|
+
if (v !== undefined)
|
|
8798
|
+
filteredEnv[key] = v;
|
|
8763
8799
|
}
|
|
8764
|
-
const batches = [...out.batches.map((b) => b.batch), resultBatch];
|
|
8765
|
-
await writer.writeStream(schema2, batches);
|
|
8766
|
-
} catch (error) {
|
|
8767
|
-
const batch = buildErrorBatch(schema2, error, serverId, requestId);
|
|
8768
|
-
await writer.writeStream(schema2, [batch]);
|
|
8769
8800
|
}
|
|
8801
|
+
const canonical = {
|
|
8802
|
+
cmd: [...workerArgv],
|
|
8803
|
+
cwd: cwdValue,
|
|
8804
|
+
env: filteredEnv
|
|
8805
|
+
};
|
|
8806
|
+
const payload = new TextEncoder().encode(canonicalJson(canonical));
|
|
8807
|
+
const hex = await sha256Hex3(payload);
|
|
8808
|
+
return hex.slice(0, HASH_LEN);
|
|
8770
8809
|
}
|
|
8810
|
+
// src/launcher/launch.ts
|
|
8811
|
+
import { spawn } from "node:child_process";
|
|
8812
|
+
import { createWriteStream, unlinkSync as unlinkSync2 } from "node:fs";
|
|
8771
8813
|
|
|
8772
|
-
// src/
|
|
8773
|
-
|
|
8774
|
-
var
|
|
8775
|
-
var
|
|
8776
|
-
function
|
|
8777
|
-
if (
|
|
8778
|
-
return
|
|
8779
|
-
|
|
8780
|
-
|
|
8781
|
-
|
|
8814
|
+
// src/launcher/lock.ts
|
|
8815
|
+
import { closeSync, constants as FS, openSync, readSync, statSync, writeSync } from "node:fs";
|
|
8816
|
+
var POLL_MS = 50;
|
|
8817
|
+
var VERIFY_RETRIES = 5;
|
|
8818
|
+
function pidAlive(pid) {
|
|
8819
|
+
if (!Number.isInteger(pid) || pid <= 0)
|
|
8820
|
+
return false;
|
|
8821
|
+
try {
|
|
8822
|
+
process.kill(pid, 0);
|
|
8823
|
+
return true;
|
|
8824
|
+
} catch (err2) {
|
|
8825
|
+
return err2?.code === "EPERM";
|
|
8782
8826
|
}
|
|
8783
|
-
const fs = req(_NODE_FS_MOD2);
|
|
8784
|
-
_writeSync = fs.writeSync.bind(fs);
|
|
8785
|
-
return _writeSync;
|
|
8786
8827
|
}
|
|
8787
|
-
function
|
|
8788
|
-
|
|
8789
|
-
|
|
8790
|
-
while (offset < data.length) {
|
|
8828
|
+
function readPid(path) {
|
|
8829
|
+
try {
|
|
8830
|
+
const fd = openSync(path, FS.O_RDONLY);
|
|
8791
8831
|
try {
|
|
8792
|
-
const
|
|
8793
|
-
|
|
8794
|
-
|
|
8795
|
-
|
|
8796
|
-
|
|
8797
|
-
|
|
8798
|
-
|
|
8799
|
-
|
|
8800
|
-
|
|
8801
|
-
throw e;
|
|
8832
|
+
const buf2 = Buffer.alloc(64);
|
|
8833
|
+
const n = readSync(fd, buf2, 0, buf2.length, 0);
|
|
8834
|
+
const text = buf2.subarray(0, n).toString("utf8").trim();
|
|
8835
|
+
if (text === "")
|
|
8836
|
+
return 0;
|
|
8837
|
+
const parsed = Number(text);
|
|
8838
|
+
return Number.isInteger(parsed) ? parsed : 0;
|
|
8839
|
+
} finally {
|
|
8840
|
+
closeSync(fd);
|
|
8802
8841
|
}
|
|
8842
|
+
} catch {
|
|
8843
|
+
return 0;
|
|
8803
8844
|
}
|
|
8804
8845
|
}
|
|
8805
|
-
|
|
8806
|
-
|
|
8807
|
-
|
|
8846
|
+
function tryStampPid(path) {
|
|
8847
|
+
const fd = openSync(path, FS.O_RDWR | FS.O_CREAT, 384);
|
|
8848
|
+
try {
|
|
8849
|
+
const stamp = Buffer.from(String(process.pid), "utf8");
|
|
8850
|
+
const { ftruncateSync } = __require("node:fs");
|
|
8851
|
+
ftruncateSync(fd, 0);
|
|
8852
|
+
let written = 0;
|
|
8853
|
+
while (written < stamp.length) {
|
|
8854
|
+
const n = writeSync(fd, stamp, written, stamp.length - written, 0 + written);
|
|
8855
|
+
if (n <= 0)
|
|
8856
|
+
throw new Error(`writeSync returned ${n}`);
|
|
8857
|
+
written += n;
|
|
8858
|
+
}
|
|
8859
|
+
const st = statSync(path);
|
|
8860
|
+
if (st.size !== stamp.length)
|
|
8861
|
+
return false;
|
|
8862
|
+
return true;
|
|
8863
|
+
} finally {
|
|
8864
|
+
closeSync(fd);
|
|
8808
8865
|
}
|
|
8809
|
-
|
|
8810
|
-
|
|
8811
|
-
|
|
8812
|
-
|
|
8813
|
-
|
|
8814
|
-
|
|
8815
|
-
|
|
8816
|
-
|
|
8817
|
-
|
|
8818
|
-
|
|
8819
|
-
|
|
8820
|
-
|
|
8821
|
-
|
|
8822
|
-
|
|
8823
|
-
|
|
8824
|
-
|
|
8825
|
-
|
|
8826
|
-
|
|
8827
|
-
|
|
8828
|
-
|
|
8866
|
+
}
|
|
8867
|
+
function clearStamp(path) {
|
|
8868
|
+
try {
|
|
8869
|
+
const fd = openSync(path, FS.O_RDWR);
|
|
8870
|
+
try {
|
|
8871
|
+
const { ftruncateSync } = __require("node:fs");
|
|
8872
|
+
ftruncateSync(fd, 0);
|
|
8873
|
+
} finally {
|
|
8874
|
+
closeSync(fd);
|
|
8875
|
+
}
|
|
8876
|
+
} catch {}
|
|
8877
|
+
}
|
|
8878
|
+
function tryAcquireLock(lockPath) {
|
|
8879
|
+
for (let attempt = 0;attempt < VERIFY_RETRIES; attempt++) {
|
|
8880
|
+
const existingPid = readPid(lockPath);
|
|
8881
|
+
if (existingPid > 0 && pidAlive(existingPid)) {
|
|
8882
|
+
return null;
|
|
8883
|
+
}
|
|
8884
|
+
if (!tryStampPid(lockPath))
|
|
8885
|
+
continue;
|
|
8886
|
+
const verifyPid = readPid(lockPath);
|
|
8887
|
+
if (verifyPid !== process.pid) {
|
|
8888
|
+
continue;
|
|
8889
|
+
}
|
|
8890
|
+
let released = false;
|
|
8891
|
+
return {
|
|
8892
|
+
path: lockPath,
|
|
8893
|
+
release() {
|
|
8894
|
+
if (released)
|
|
8895
|
+
return;
|
|
8896
|
+
released = true;
|
|
8897
|
+
clearStamp(lockPath);
|
|
8898
|
+
}
|
|
8829
8899
|
};
|
|
8830
|
-
|
|
8831
|
-
|
|
8832
|
-
socket.once("close", onClose);
|
|
8833
|
-
});
|
|
8900
|
+
}
|
|
8901
|
+
return null;
|
|
8834
8902
|
}
|
|
8835
|
-
|
|
8836
|
-
|
|
8837
|
-
|
|
8838
|
-
|
|
8839
|
-
if (
|
|
8840
|
-
|
|
8841
|
-
|
|
8842
|
-
|
|
8903
|
+
async function acquireLock(lockPath, timeoutMs) {
|
|
8904
|
+
const deadline = Date.now() + Math.max(0, timeoutMs);
|
|
8905
|
+
for (;; ) {
|
|
8906
|
+
const handle = tryAcquireLock(lockPath);
|
|
8907
|
+
if (handle)
|
|
8908
|
+
return handle;
|
|
8909
|
+
if (Date.now() >= deadline) {
|
|
8910
|
+
throw new Error(`failed to acquire ${lockPath} within ${timeoutMs}ms`);
|
|
8843
8911
|
}
|
|
8912
|
+
await new Promise((r) => setTimeout(r, POLL_MS));
|
|
8844
8913
|
}
|
|
8845
|
-
|
|
8846
|
-
|
|
8847
|
-
|
|
8848
|
-
|
|
8914
|
+
}
|
|
8915
|
+
|
|
8916
|
+
// src/launcher/state.ts
|
|
8917
|
+
import { existsSync, mkdirSync, readFileSync, statSync as statSync2, unlinkSync, writeFileSync } from "node:fs";
|
|
8918
|
+
import { tmpdir } from "node:os";
|
|
8919
|
+
import * as path from "node:path";
|
|
8920
|
+
function socketPaths(stateDir, hashId) {
|
|
8921
|
+
return {
|
|
8922
|
+
lockPath: path.join(stateDir, `${hashId}.lock`),
|
|
8923
|
+
sockPath: path.join(stateDir, `${hashId}.sock`),
|
|
8924
|
+
metaPath: path.join(stateDir, `${hashId}.meta`)
|
|
8925
|
+
};
|
|
8926
|
+
}
|
|
8927
|
+
function defaultStateDir() {
|
|
8928
|
+
let base;
|
|
8929
|
+
if (process.platform === "win32") {
|
|
8930
|
+
base = path.join(tmpdir(), "vgi-rpc");
|
|
8931
|
+
} else {
|
|
8932
|
+
const xdg = process.env.XDG_RUNTIME_DIR;
|
|
8933
|
+
if (xdg) {
|
|
8934
|
+
base = path.join(xdg, "vgi-rpc");
|
|
8849
8935
|
} else {
|
|
8850
|
-
|
|
8936
|
+
const uid = typeof process.geteuid === "function" ? process.geteuid() : 0;
|
|
8937
|
+
base = path.join(tmpdir(), `vgi-rpc-${uid}`);
|
|
8851
8938
|
}
|
|
8852
8939
|
}
|
|
8853
|
-
|
|
8854
|
-
|
|
8940
|
+
mkdirSync(base, { recursive: true, mode: 448 });
|
|
8941
|
+
if (process.platform !== "win32" && typeof process.geteuid === "function") {
|
|
8942
|
+
try {
|
|
8943
|
+
const st = statSync2(base);
|
|
8944
|
+
if (st.uid !== process.geteuid()) {
|
|
8945
|
+
throw new Error(`state directory ${base} is not owned by current user`);
|
|
8946
|
+
}
|
|
8947
|
+
} catch (err2) {
|
|
8948
|
+
if (err2?.code === "ENOENT") {} else {
|
|
8949
|
+
throw err2;
|
|
8950
|
+
}
|
|
8951
|
+
}
|
|
8855
8952
|
}
|
|
8953
|
+
return base;
|
|
8856
8954
|
}
|
|
8857
|
-
|
|
8858
|
-
|
|
8859
|
-
|
|
8860
|
-
|
|
8861
|
-
|
|
8862
|
-
|
|
8863
|
-
|
|
8864
|
-
|
|
8865
|
-
|
|
8866
|
-
|
|
8867
|
-
}
|
|
8868
|
-
|
|
8869
|
-
|
|
8870
|
-
|
|
8871
|
-
return
|
|
8872
|
-
|
|
8873
|
-
|
|
8874
|
-
|
|
8875
|
-
|
|
8876
|
-
|
|
8877
|
-
|
|
8878
|
-
|
|
8879
|
-
|
|
8880
|
-
|
|
8881
|
-
|
|
8882
|
-
|
|
8883
|
-
return;
|
|
8884
|
-
}
|
|
8885
|
-
return socketWriteAll(this.target.socket, bytes);
|
|
8955
|
+
function writeMeta(metaPath, workerArgv, cwd, sockPath) {
|
|
8956
|
+
const payload = {
|
|
8957
|
+
cmd: [...workerArgv],
|
|
8958
|
+
cwd,
|
|
8959
|
+
socket: sockPath,
|
|
8960
|
+
started_at: Date.now() / 1000,
|
|
8961
|
+
launcher_pid: process.pid
|
|
8962
|
+
};
|
|
8963
|
+
try {
|
|
8964
|
+
writeFileSync(metaPath, JSON.stringify(payload, null, 2), { encoding: "utf8", mode: 384 });
|
|
8965
|
+
} catch {}
|
|
8966
|
+
}
|
|
8967
|
+
async function probeSocket(sockPath, timeoutMs = 2000) {
|
|
8968
|
+
if (!existsSync(sockPath))
|
|
8969
|
+
return false;
|
|
8970
|
+
const net = await import("node:net");
|
|
8971
|
+
return new Promise((resolve) => {
|
|
8972
|
+
const sock = net.createConnection({ path: sockPath });
|
|
8973
|
+
const timer = setTimeout(() => {
|
|
8974
|
+
sock.destroy();
|
|
8975
|
+
resolve(false);
|
|
8976
|
+
}, timeoutMs);
|
|
8977
|
+
sock.once("connect", () => {
|
|
8978
|
+
clearTimeout(timer);
|
|
8979
|
+
sock.end();
|
|
8980
|
+
resolve(true);
|
|
8886
8981
|
});
|
|
8887
|
-
|
|
8888
|
-
|
|
8982
|
+
sock.once("error", () => {
|
|
8983
|
+
clearTimeout(timer);
|
|
8984
|
+
resolve(false);
|
|
8889
8985
|
});
|
|
8890
|
-
|
|
8891
|
-
}
|
|
8986
|
+
});
|
|
8892
8987
|
}
|
|
8893
|
-
|
|
8894
|
-
|
|
8895
|
-
|
|
8896
|
-
|
|
8897
|
-
|
|
8898
|
-
|
|
8899
|
-
|
|
8900
|
-
|
|
8901
|
-
|
|
8902
|
-
|
|
8903
|
-
|
|
8904
|
-
const externalConfig = options.externalLocation;
|
|
8905
|
-
const onServeStart = options.onServeStart ?? null;
|
|
8906
|
-
const backlog = options.backlog ?? 16;
|
|
8907
|
-
const announcementSink = options.announcementSink ?? process.stdout;
|
|
8908
|
-
if (existsSync2(sockPath)) {
|
|
8909
|
-
try {
|
|
8910
|
-
unlinkSync3(sockPath);
|
|
8911
|
-
} catch {}
|
|
8988
|
+
function tryReadMeta(metaPath) {
|
|
8989
|
+
try {
|
|
8990
|
+
const raw = readFileSync(metaPath, "utf8");
|
|
8991
|
+
const meta = JSON.parse(raw);
|
|
8992
|
+
return {
|
|
8993
|
+
cmd: Array.isArray(meta.cmd) ? meta.cmd.map(String) : [],
|
|
8994
|
+
cwd: typeof meta.cwd === "string" ? meta.cwd : "",
|
|
8995
|
+
startedAt: typeof meta.started_at === "number" ? meta.started_at : null
|
|
8996
|
+
};
|
|
8997
|
+
} catch {
|
|
8998
|
+
return { cmd: [], cwd: "", startedAt: null };
|
|
8912
8999
|
}
|
|
8913
|
-
|
|
8914
|
-
|
|
8915
|
-
|
|
8916
|
-
|
|
8917
|
-
|
|
8918
|
-
|
|
8919
|
-
|
|
8920
|
-
|
|
8921
|
-
return
|
|
9000
|
+
}
|
|
9001
|
+
async function statusRows(stateDir) {
|
|
9002
|
+
const { readdirSync } = await import("node:fs");
|
|
9003
|
+
const rows = [];
|
|
9004
|
+
let entries;
|
|
9005
|
+
try {
|
|
9006
|
+
entries = readdirSync(stateDir);
|
|
9007
|
+
} catch {
|
|
9008
|
+
return rows;
|
|
8922
9009
|
}
|
|
8923
|
-
|
|
8924
|
-
|
|
8925
|
-
|
|
8926
|
-
|
|
8927
|
-
|
|
8928
|
-
|
|
8929
|
-
|
|
8930
|
-
|
|
9010
|
+
for (const name of entries.sort()) {
|
|
9011
|
+
if (!name.endsWith(".lock"))
|
|
9012
|
+
continue;
|
|
9013
|
+
const hashId = name.slice(0, -5);
|
|
9014
|
+
const { sockPath, metaPath } = socketPaths(stateDir, hashId);
|
|
9015
|
+
const meta = tryReadMeta(metaPath);
|
|
9016
|
+
rows.push({
|
|
9017
|
+
hashId,
|
|
9018
|
+
cmd: meta.cmd,
|
|
9019
|
+
cwd: meta.cwd,
|
|
9020
|
+
socket: sockPath,
|
|
9021
|
+
startedAt: meta.startedAt,
|
|
9022
|
+
alive: await probeSocket(sockPath)
|
|
9023
|
+
});
|
|
8931
9024
|
}
|
|
8932
|
-
|
|
8933
|
-
|
|
8934
|
-
|
|
8935
|
-
|
|
8936
|
-
|
|
8937
|
-
const
|
|
8938
|
-
|
|
8939
|
-
|
|
8940
|
-
|
|
8941
|
-
|
|
8942
|
-
|
|
8943
|
-
|
|
8944
|
-
|
|
8945
|
-
if (idleTimer)
|
|
8946
|
-
clearTimeout(idleTimer);
|
|
8947
|
-
idleTimer = setTimeout(() => {
|
|
8948
|
-
if (activeConnections === 0 && !stopped) {
|
|
8949
|
-
shutdown();
|
|
8950
|
-
}
|
|
8951
|
-
}, idleTimeoutS * 1000);
|
|
9025
|
+
return rows;
|
|
9026
|
+
}
|
|
9027
|
+
async function gcStateDir(stateDir, tryAcquire, options) {
|
|
9028
|
+
const { readdirSync } = await import("node:fs");
|
|
9029
|
+
const cleaned = [];
|
|
9030
|
+
const skipped = [];
|
|
9031
|
+
const limit = options?.limit ?? null;
|
|
9032
|
+
const excludeHash = options?.excludeHash ?? null;
|
|
9033
|
+
let entries;
|
|
9034
|
+
try {
|
|
9035
|
+
entries = readdirSync(stateDir);
|
|
9036
|
+
} catch {
|
|
9037
|
+
return { cleaned, skippedInUse: skipped };
|
|
8952
9038
|
}
|
|
8953
|
-
|
|
8954
|
-
|
|
8955
|
-
|
|
8956
|
-
|
|
9039
|
+
let seen = 0;
|
|
9040
|
+
for (const name of entries.sort()) {
|
|
9041
|
+
if (!name.endsWith(".lock"))
|
|
9042
|
+
continue;
|
|
9043
|
+
if (limit !== null && seen >= limit)
|
|
9044
|
+
break;
|
|
9045
|
+
seen += 1;
|
|
9046
|
+
const hashId = name.slice(0, -5);
|
|
9047
|
+
if (excludeHash !== null && hashId === excludeHash)
|
|
9048
|
+
continue;
|
|
9049
|
+
const { lockPath, sockPath, metaPath } = socketPaths(stateDir, hashId);
|
|
9050
|
+
const release = await tryAcquire(lockPath);
|
|
9051
|
+
if (release === null) {
|
|
9052
|
+
skipped.push(hashId);
|
|
9053
|
+
continue;
|
|
8957
9054
|
}
|
|
8958
|
-
}
|
|
8959
|
-
async function shutdown() {
|
|
8960
|
-
if (stopped)
|
|
8961
|
-
return;
|
|
8962
|
-
stopped = true;
|
|
8963
|
-
disarmIdleTimer();
|
|
8964
|
-
await new Promise((resolve2) => {
|
|
8965
|
-
server.close(() => resolve2());
|
|
8966
|
-
});
|
|
8967
9055
|
try {
|
|
8968
|
-
|
|
8969
|
-
|
|
8970
|
-
resolveDone();
|
|
8971
|
-
}
|
|
8972
|
-
server.on("connection", (socket) => {
|
|
8973
|
-
activeConnections += 1;
|
|
8974
|
-
disarmIdleTimer();
|
|
8975
|
-
handleConnection(socket).catch((err2) => {
|
|
8976
|
-
process.stderr.write(`vgi-rpc/unix: connection failed: ${err2?.message ?? err2}
|
|
8977
|
-
`);
|
|
8978
|
-
}).finally(() => {
|
|
8979
|
-
activeConnections -= 1;
|
|
8980
|
-
socket.destroy();
|
|
8981
|
-
if (activeConnections === 0 && !stopped) {
|
|
8982
|
-
armIdleTimer();
|
|
9056
|
+
if (await probeSocket(sockPath)) {
|
|
9057
|
+
continue;
|
|
8983
9058
|
}
|
|
8984
|
-
|
|
8985
|
-
});
|
|
8986
|
-
server.on("error", (err2) => {
|
|
8987
|
-
if (stopped)
|
|
8988
|
-
return;
|
|
8989
|
-
rejectDone(err2);
|
|
8990
|
-
});
|
|
8991
|
-
async function handleConnection(socket) {
|
|
8992
|
-
const reader = await IpcStreamReader.create(socket);
|
|
8993
|
-
const writer = new IpcStreamWriter(socket);
|
|
8994
|
-
try {
|
|
8995
|
-
await notifyTransport();
|
|
8996
|
-
while (true) {
|
|
9059
|
+
for (const p of [sockPath, metaPath, lockPath]) {
|
|
8997
9060
|
try {
|
|
8998
|
-
|
|
8999
|
-
} catch
|
|
9000
|
-
const err2 = e;
|
|
9001
|
-
if (err2?.message?.includes("closed") || err2?.message?.includes("Expected Schema Message") || err2?.message?.includes("null or length 0") || err2?.message?.includes("EOF") || err2?.code === "EPIPE" || err2?.code === "ERR_STREAM_PREMATURE_CLOSE" || err2?.code === "ERR_STREAM_DESTROYED") {
|
|
9002
|
-
return;
|
|
9003
|
-
}
|
|
9004
|
-
throw e;
|
|
9005
|
-
}
|
|
9061
|
+
unlinkSync(p);
|
|
9062
|
+
} catch {}
|
|
9006
9063
|
}
|
|
9064
|
+
cleaned.push(hashId);
|
|
9007
9065
|
} finally {
|
|
9008
|
-
|
|
9009
|
-
await reader.cancel();
|
|
9010
|
-
} catch {}
|
|
9066
|
+
release();
|
|
9011
9067
|
}
|
|
9012
9068
|
}
|
|
9013
|
-
|
|
9014
|
-
|
|
9015
|
-
|
|
9016
|
-
|
|
9017
|
-
|
|
9018
|
-
|
|
9019
|
-
|
|
9020
|
-
|
|
9021
|
-
|
|
9022
|
-
|
|
9023
|
-
|
|
9024
|
-
|
|
9025
|
-
|
|
9026
|
-
|
|
9027
|
-
|
|
9028
|
-
|
|
9029
|
-
|
|
9030
|
-
|
|
9031
|
-
|
|
9032
|
-
|
|
9033
|
-
|
|
9034
|
-
|
|
9035
|
-
|
|
9036
|
-
|
|
9037
|
-
|
|
9038
|
-
|
|
9039
|
-
|
|
9040
|
-
|
|
9041
|
-
|
|
9042
|
-
|
|
9043
|
-
|
|
9044
|
-
|
|
9045
|
-
|
|
9046
|
-
|
|
9047
|
-
const method = methods.get(methodName);
|
|
9048
|
-
if (!method) {
|
|
9049
|
-
const available = [...methods.keys()].sort();
|
|
9050
|
-
const err2 = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
|
|
9051
|
-
const errBatch = buildErrorBatch(EMPTY_SCHEMA4, err2, serverId, requestId);
|
|
9052
|
-
await writer.writeStream(EMPTY_SCHEMA4, [errBatch]);
|
|
9053
|
-
return;
|
|
9069
|
+
return { cleaned, skippedInUse: skipped };
|
|
9070
|
+
}
|
|
9071
|
+
|
|
9072
|
+
// src/launcher/launch.ts
|
|
9073
|
+
var DEFAULT_GC_LIMIT = 16;
|
|
9074
|
+
async function launch(config) {
|
|
9075
|
+
if (!config.workerArgv || config.workerArgv.length === 0) {
|
|
9076
|
+
throw new Error("workerArgv must be non-empty");
|
|
9077
|
+
}
|
|
9078
|
+
const stateDir = config.stateDir ?? defaultStateDir();
|
|
9079
|
+
const idleTimeout = config.idleTimeout ?? 300;
|
|
9080
|
+
const connectTimeoutMs = (config.connectTimeout ?? 30) * 1000;
|
|
9081
|
+
const startupTimeoutMs = (config.workerStartupTimeout ?? 60) * 1000;
|
|
9082
|
+
let lockPath;
|
|
9083
|
+
let sockPath;
|
|
9084
|
+
let metaPath;
|
|
9085
|
+
let hashId;
|
|
9086
|
+
if (config.socketPath !== undefined) {
|
|
9087
|
+
const { resolve } = await import("node:path");
|
|
9088
|
+
sockPath = resolve(config.socketPath);
|
|
9089
|
+
lockPath = `${sockPath}.lock`;
|
|
9090
|
+
metaPath = null;
|
|
9091
|
+
hashId = null;
|
|
9092
|
+
} else {
|
|
9093
|
+
hashId = await computeHash(config.workerArgv);
|
|
9094
|
+
const paths = socketPaths(stateDir, hashId);
|
|
9095
|
+
lockPath = paths.lockPath;
|
|
9096
|
+
sockPath = paths.sockPath;
|
|
9097
|
+
metaPath = paths.metaPath;
|
|
9098
|
+
}
|
|
9099
|
+
const handle = await acquireLock(lockPath, connectTimeoutMs);
|
|
9100
|
+
try {
|
|
9101
|
+
if (await probeSocket(sockPath)) {
|
|
9102
|
+
return sockPath;
|
|
9054
9103
|
}
|
|
9055
|
-
const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
|
|
9056
|
-
let requestData;
|
|
9057
9104
|
try {
|
|
9058
|
-
|
|
9105
|
+
unlinkSync2(sockPath);
|
|
9059
9106
|
} catch {}
|
|
9060
|
-
|
|
9061
|
-
|
|
9062
|
-
|
|
9063
|
-
|
|
9064
|
-
|
|
9065
|
-
|
|
9066
|
-
|
|
9067
|
-
|
|
9068
|
-
|
|
9069
|
-
|
|
9070
|
-
|
|
9071
|
-
|
|
9072
|
-
|
|
9073
|
-
|
|
9074
|
-
requestData
|
|
9075
|
-
};
|
|
9076
|
-
const stats = {
|
|
9077
|
-
inputBatches: 0,
|
|
9078
|
-
outputBatches: 0,
|
|
9079
|
-
inputRows: 0,
|
|
9080
|
-
outputRows: 0,
|
|
9081
|
-
inputBytes: 0,
|
|
9082
|
-
outputBytes: 0
|
|
9083
|
-
};
|
|
9084
|
-
const token = dispatchHook?.onDispatchStart(info);
|
|
9085
|
-
let dispatchError;
|
|
9086
|
-
applyDefaults(params, method.defaults);
|
|
9087
|
-
try {
|
|
9088
|
-
if (method.type === "unary" /* UNARY */) {
|
|
9089
|
-
await dispatchUnary(method, params, writer, serverId, requestId, externalConfig, "unix" /* UNIX */);
|
|
9090
|
-
} else {
|
|
9091
|
-
await dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, "unix" /* UNIX */);
|
|
9092
|
-
}
|
|
9093
|
-
} catch (e) {
|
|
9094
|
-
dispatchError = e instanceof Error ? e : new Error(String(e));
|
|
9095
|
-
throw e;
|
|
9096
|
-
} finally {
|
|
9097
|
-
dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
|
|
9107
|
+
if (metaPath !== null) {
|
|
9108
|
+
writeMeta(metaPath, config.workerArgv, process.cwd(), sockPath);
|
|
9109
|
+
}
|
|
9110
|
+
await spawnWorker(config.workerArgv, sockPath, idleTimeout, config.workerStderr ?? null, startupTimeoutMs);
|
|
9111
|
+
return sockPath;
|
|
9112
|
+
} finally {
|
|
9113
|
+
handle.release();
|
|
9114
|
+
if (hashId !== null) {
|
|
9115
|
+
try {
|
|
9116
|
+
await gcStateDir(stateDir, async (p) => {
|
|
9117
|
+
const h = tryAcquireLock(p);
|
|
9118
|
+
return h ? () => h.release() : null;
|
|
9119
|
+
}, { limit: DEFAULT_GC_LIMIT, excludeHash: hashId });
|
|
9120
|
+
} catch {}
|
|
9098
9121
|
}
|
|
9099
9122
|
}
|
|
9100
|
-
|
|
9101
|
-
|
|
9102
|
-
|
|
9123
|
+
}
|
|
9124
|
+
async function spawnWorker(workerArgv, sockPath, idleTimeout, workerStderr, startupTimeoutMs) {
|
|
9125
|
+
const fullArgv = [...workerArgv, "--unix", sockPath, "--idle-timeout", String(idleTimeout)];
|
|
9126
|
+
const [cmd, ...rest] = fullArgv;
|
|
9127
|
+
const stderrTarget = workerStderr === null ? "ignore" : "pipe";
|
|
9128
|
+
const proc = spawn(cmd, rest, {
|
|
9129
|
+
stdio: ["ignore", "pipe", stderrTarget],
|
|
9130
|
+
detached: false
|
|
9103
9131
|
});
|
|
9104
|
-
|
|
9105
|
-
const
|
|
9106
|
-
|
|
9107
|
-
} catch {}
|
|
9108
|
-
options.onBound?.(sockPath);
|
|
9109
|
-
announcementSink.write(`UNIX:${sockPath}
|
|
9110
|
-
`);
|
|
9111
|
-
if (idleTimeoutS > 0) {
|
|
9112
|
-
setTimeout(() => {
|
|
9113
|
-
if (activeConnections === 0 && !stopped)
|
|
9114
|
-
armIdleTimer();
|
|
9115
|
-
}, startupGraceS * 1000).unref?.();
|
|
9132
|
+
if (workerStderr !== null && proc.stderr) {
|
|
9133
|
+
const sink = createWriteStream(workerStderr, { flags: "a" });
|
|
9134
|
+
proc.stderr.pipe(sink);
|
|
9116
9135
|
}
|
|
9117
|
-
|
|
9118
|
-
|
|
9119
|
-
|
|
9120
|
-
|
|
9121
|
-
|
|
9122
|
-
|
|
9123
|
-
|
|
9124
|
-
|
|
9125
|
-
|
|
9126
|
-
|
|
9127
|
-
|
|
9128
|
-
|
|
9129
|
-
var int82 = int8();
|
|
9130
|
-
var uint82 = uint8();
|
|
9131
|
-
var uint162 = uint16();
|
|
9132
|
-
var uint322 = uint32();
|
|
9133
|
-
var uint642 = uint64();
|
|
9134
|
-
var float = float64();
|
|
9135
|
-
var float322 = float32();
|
|
9136
|
-
var bool2 = bool();
|
|
9137
|
-
function isField(x) {
|
|
9138
|
-
return x != null && typeof x.name === "string" && x.type != null && typeof x.nullable === "boolean";
|
|
9139
|
-
}
|
|
9140
|
-
function isDataType(x) {
|
|
9141
|
-
return x != null && typeof x.typeId === "number";
|
|
9142
|
-
}
|
|
9143
|
-
function toSchema(spec) {
|
|
9144
|
-
const maybeFields = spec.fields;
|
|
9145
|
-
if (Array.isArray(maybeFields)) {
|
|
9146
|
-
const out = [];
|
|
9147
|
-
for (const f of maybeFields) {
|
|
9148
|
-
if (isField(f)) {
|
|
9149
|
-
out.push(f);
|
|
9150
|
-
} else {
|
|
9151
|
-
out.push(field(f.name, f.type, f.nullable ?? true, f.metadata));
|
|
9152
|
-
}
|
|
9136
|
+
const expectedPrefix = `UNIX:${sockPath}`;
|
|
9137
|
+
const reader = lineReader(proc.stdout);
|
|
9138
|
+
const deadline = Date.now() + startupTimeoutMs;
|
|
9139
|
+
while (Date.now() < deadline) {
|
|
9140
|
+
const remaining = deadline - Date.now();
|
|
9141
|
+
const result = await Promise.race([
|
|
9142
|
+
reader.next().then((r) => ({ kind: "line", value: r })),
|
|
9143
|
+
onceExit(proc).then((rc) => ({ kind: "exit", rc })),
|
|
9144
|
+
delay(remaining).then(() => ({ kind: "timeout" }))
|
|
9145
|
+
]);
|
|
9146
|
+
if (result.kind === "exit") {
|
|
9147
|
+
throw new Error(`worker exited before readiness (rc=${result.rc})`);
|
|
9153
9148
|
}
|
|
9154
|
-
|
|
9155
|
-
|
|
9156
|
-
|
|
9157
|
-
for (const [name, value] of Object.entries(spec)) {
|
|
9158
|
-
if (isField(value)) {
|
|
9159
|
-
fields.push(value);
|
|
9160
|
-
} else if (isDataType(value)) {
|
|
9161
|
-
fields.push(field(name, value, false));
|
|
9162
|
-
} else {
|
|
9163
|
-
throw new TypeError(`Invalid schema value for "${name}": expected DataType or Field, got ${typeof value}`);
|
|
9149
|
+
if (result.kind === "timeout") {
|
|
9150
|
+
proc.kill("SIGTERM");
|
|
9151
|
+
throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
|
|
9164
9152
|
}
|
|
9165
|
-
|
|
9166
|
-
|
|
9167
|
-
}
|
|
9168
|
-
|
|
9169
|
-
|
|
9170
|
-
|
|
9171
|
-
|
|
9172
|
-
|
|
9173
|
-
|
|
9174
|
-
|
|
9175
|
-
|
|
9176
|
-
mapped = "str";
|
|
9177
|
-
else if (isBinary(f.type))
|
|
9178
|
-
mapped = "bytes";
|
|
9179
|
-
else if (isBool(f.type))
|
|
9180
|
-
mapped = "bool";
|
|
9181
|
-
else if (isFloat(f.type))
|
|
9182
|
-
mapped = "float";
|
|
9183
|
-
else if (isInt(f.type))
|
|
9184
|
-
mapped = "int";
|
|
9185
|
-
if (!mapped)
|
|
9153
|
+
if (result.value.done) {
|
|
9154
|
+
const rc = await onceExit(proc);
|
|
9155
|
+
throw new Error(`worker exited before readiness (rc=${rc})`);
|
|
9156
|
+
}
|
|
9157
|
+
const line = result.value.value;
|
|
9158
|
+
if (line.startsWith("UNIX:")) {
|
|
9159
|
+
if (line !== expectedPrefix) {
|
|
9160
|
+
proc.kill("SIGTERM");
|
|
9161
|
+
throw new Error(`worker bound to unexpected path: ${JSON.stringify(line)} (expected ${JSON.stringify(expectedPrefix)})`);
|
|
9162
|
+
}
|
|
9163
|
+
reader.drainAndDiscard();
|
|
9186
9164
|
return;
|
|
9187
|
-
result[f.name] = mapped;
|
|
9188
|
-
}
|
|
9189
|
-
return result;
|
|
9190
|
-
}
|
|
9191
|
-
|
|
9192
|
-
// src/protocol.ts
|
|
9193
|
-
var EMPTY_SCHEMA5 = schema([]);
|
|
9194
|
-
|
|
9195
|
-
class Protocol {
|
|
9196
|
-
name;
|
|
9197
|
-
protocolVersion;
|
|
9198
|
-
protocolVersionParts;
|
|
9199
|
-
_methods = new Map;
|
|
9200
|
-
constructor(name, options) {
|
|
9201
|
-
this.name = name;
|
|
9202
|
-
const raw = options?.protocolVersion;
|
|
9203
|
-
if (raw === undefined || raw === "") {
|
|
9204
|
-
this.protocolVersion = "";
|
|
9205
|
-
this.protocolVersionParts = null;
|
|
9206
|
-
} else {
|
|
9207
|
-
this.protocolVersion = raw;
|
|
9208
|
-
this.protocolVersionParts = parseProtocolVersion(raw);
|
|
9209
9165
|
}
|
|
9166
|
+
process.env.VGI_RPC_LAUNCHER_DEBUG && process.stderr.write(`launcher: skipping pre-bind stdout line: ${JSON.stringify(line)}
|
|
9167
|
+
`);
|
|
9210
9168
|
}
|
|
9211
|
-
|
|
9212
|
-
|
|
9213
|
-
this._methods.set(name, {
|
|
9214
|
-
name,
|
|
9215
|
-
type: "unary" /* UNARY */,
|
|
9216
|
-
paramsSchema: params,
|
|
9217
|
-
resultSchema: toSchema(config.result),
|
|
9218
|
-
handler: config.handler,
|
|
9219
|
-
doc: config.doc,
|
|
9220
|
-
defaults: config.defaults,
|
|
9221
|
-
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
9222
|
-
});
|
|
9223
|
-
return this;
|
|
9224
|
-
}
|
|
9225
|
-
producer(name, config) {
|
|
9226
|
-
const params = toSchema(config.params);
|
|
9227
|
-
this._methods.set(name, {
|
|
9228
|
-
name,
|
|
9229
|
-
type: "stream" /* STREAM */,
|
|
9230
|
-
paramsSchema: params,
|
|
9231
|
-
resultSchema: EMPTY_SCHEMA5,
|
|
9232
|
-
outputSchema: toSchema(config.outputSchema),
|
|
9233
|
-
inputSchema: EMPTY_SCHEMA5,
|
|
9234
|
-
producerInit: config.init,
|
|
9235
|
-
producerFn: config.produce,
|
|
9236
|
-
onCancel: config.onCancel,
|
|
9237
|
-
headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
|
|
9238
|
-
headerInit: config.headerInit,
|
|
9239
|
-
doc: config.doc,
|
|
9240
|
-
defaults: config.defaults,
|
|
9241
|
-
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
9242
|
-
});
|
|
9243
|
-
return this;
|
|
9244
|
-
}
|
|
9245
|
-
exchange(name, config) {
|
|
9246
|
-
const params = toSchema(config.params);
|
|
9247
|
-
this._methods.set(name, {
|
|
9248
|
-
name,
|
|
9249
|
-
type: "stream" /* STREAM */,
|
|
9250
|
-
paramsSchema: params,
|
|
9251
|
-
resultSchema: EMPTY_SCHEMA5,
|
|
9252
|
-
inputSchema: toSchema(config.inputSchema),
|
|
9253
|
-
outputSchema: toSchema(config.outputSchema),
|
|
9254
|
-
exchangeInit: config.init,
|
|
9255
|
-
exchangeFn: config.exchange,
|
|
9256
|
-
onCancel: config.onCancel,
|
|
9257
|
-
headerSchema: config.headerSchema ? toSchema(config.headerSchema) : undefined,
|
|
9258
|
-
headerInit: config.headerInit,
|
|
9259
|
-
doc: config.doc,
|
|
9260
|
-
defaults: config.defaults,
|
|
9261
|
-
paramTypes: config.paramTypes ?? inferParamTypes(params)
|
|
9262
|
-
});
|
|
9263
|
-
return this;
|
|
9264
|
-
}
|
|
9265
|
-
getMethods() {
|
|
9266
|
-
return new Map(this._methods);
|
|
9267
|
-
}
|
|
9268
|
-
}
|
|
9269
|
-
// src/server.ts
|
|
9270
|
-
var EMPTY_SCHEMA6 = schema([]);
|
|
9271
|
-
function randomStreamId() {
|
|
9272
|
-
const bytes2 = new Uint8Array(16);
|
|
9273
|
-
crypto.getRandomValues(bytes2);
|
|
9274
|
-
let out = "";
|
|
9275
|
-
for (let i = 0;i < bytes2.length; i++) {
|
|
9276
|
-
out += bytes2[i].toString(16).padStart(2, "0");
|
|
9277
|
-
}
|
|
9278
|
-
return out;
|
|
9169
|
+
proc.kill("SIGTERM");
|
|
9170
|
+
throw new Error(`worker did not emit UNIX:<path> within ${startupTimeoutMs}ms`);
|
|
9279
9171
|
}
|
|
9280
|
-
|
|
9281
|
-
|
|
9282
|
-
|
|
9283
|
-
|
|
9284
|
-
|
|
9285
|
-
|
|
9286
|
-
|
|
9287
|
-
|
|
9288
|
-
externalConfig;
|
|
9289
|
-
onServeStart = null;
|
|
9290
|
-
serveStartFired = false;
|
|
9291
|
-
constructor(protocol, options) {
|
|
9292
|
-
this.protocol = protocol;
|
|
9293
|
-
this.enableDescribe = options?.enableDescribe ?? true;
|
|
9294
|
-
this.serverId = options?.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
9295
|
-
this.dispatchHook = options?.dispatchHook ?? null;
|
|
9296
|
-
this.externalConfig = options?.externalLocation;
|
|
9297
|
-
this.protocolVersion = options?.protocolVersion ?? "";
|
|
9298
|
-
this.onServeStart = options?.onServeStart ?? null;
|
|
9299
|
-
}
|
|
9300
|
-
async notifyTransport(kind) {
|
|
9301
|
-
if (this.serveStartFired)
|
|
9172
|
+
function lineReader(stream) {
|
|
9173
|
+
let buffer = "";
|
|
9174
|
+
let ended = false;
|
|
9175
|
+
const queued = [];
|
|
9176
|
+
const waiters = [];
|
|
9177
|
+
let discardMode = false;
|
|
9178
|
+
const flushWaiter = () => {
|
|
9179
|
+
if (waiters.length === 0)
|
|
9302
9180
|
return;
|
|
9303
|
-
if (
|
|
9304
|
-
|
|
9181
|
+
if (queued.length > 0) {
|
|
9182
|
+
const w = waiters.shift();
|
|
9183
|
+
w?.({ done: false, value: queued.shift() ?? "" });
|
|
9184
|
+
} else if (ended) {
|
|
9185
|
+
const w = waiters.shift();
|
|
9186
|
+
w?.({ done: true, value: "" });
|
|
9187
|
+
}
|
|
9188
|
+
};
|
|
9189
|
+
stream.setEncoding?.("utf8");
|
|
9190
|
+
stream.on("data", (chunk) => {
|
|
9191
|
+
if (discardMode)
|
|
9192
|
+
return;
|
|
9193
|
+
buffer += String(chunk);
|
|
9194
|
+
for (;; ) {
|
|
9195
|
+
const nl = buffer.indexOf(`
|
|
9196
|
+
`);
|
|
9197
|
+
if (nl < 0)
|
|
9198
|
+
break;
|
|
9199
|
+
const line = buffer.slice(0, nl).replace(/\r$/, "");
|
|
9200
|
+
buffer = buffer.slice(nl + 1);
|
|
9201
|
+
queued.push(line);
|
|
9202
|
+
}
|
|
9203
|
+
flushWaiter();
|
|
9204
|
+
});
|
|
9205
|
+
stream.on("end", () => {
|
|
9206
|
+
ended = true;
|
|
9207
|
+
if (buffer.length > 0) {
|
|
9208
|
+
queued.push(buffer.replace(/\r$/, ""));
|
|
9209
|
+
buffer = "";
|
|
9305
9210
|
}
|
|
9306
|
-
|
|
9211
|
+
flushWaiter();
|
|
9212
|
+
});
|
|
9213
|
+
stream.on("error", () => {
|
|
9214
|
+
ended = true;
|
|
9215
|
+
flushWaiter();
|
|
9216
|
+
});
|
|
9217
|
+
return {
|
|
9218
|
+
next() {
|
|
9219
|
+
return new Promise((resolve) => {
|
|
9220
|
+
waiters.push(resolve);
|
|
9221
|
+
flushWaiter();
|
|
9222
|
+
});
|
|
9223
|
+
},
|
|
9224
|
+
drainAndDiscard() {
|
|
9225
|
+
discardMode = true;
|
|
9226
|
+
queued.length = 0;
|
|
9227
|
+
stream.resume?.();
|
|
9228
|
+
}
|
|
9229
|
+
};
|
|
9230
|
+
}
|
|
9231
|
+
function onceExit(proc) {
|
|
9232
|
+
return new Promise((resolve) => {
|
|
9233
|
+
proc.once("exit", (code) => resolve(code));
|
|
9234
|
+
});
|
|
9235
|
+
}
|
|
9236
|
+
function delay(ms) {
|
|
9237
|
+
return new Promise((r) => setTimeout(r, Math.max(0, ms)));
|
|
9238
|
+
}
|
|
9239
|
+
// src/launcher/serve-unix.ts
|
|
9240
|
+
import { existsSync as existsSync2, unlinkSync as unlinkSync3 } from "node:fs";
|
|
9241
|
+
import { createServer } from "node:net";
|
|
9242
|
+
import * as path2 from "node:path";
|
|
9243
|
+
var EMPTY_SCHEMA6 = schema([]);
|
|
9244
|
+
async function serveUnix(protocol, options) {
|
|
9245
|
+
const sockPath = path2.resolve(options.unixPath);
|
|
9246
|
+
const idleTimeoutS = options.idleTimeout ?? 300;
|
|
9247
|
+
const startupGraceS = options.startupGraceSeconds ?? 5;
|
|
9248
|
+
const protocolVersion = options.protocolVersion ?? "";
|
|
9249
|
+
const serverId = options.serverId ?? crypto.randomUUID().replace(/-/g, "").slice(0, 12);
|
|
9250
|
+
const enableDescribe = options.enableDescribe ?? true;
|
|
9251
|
+
const dispatchHook = options.dispatchHook ?? null;
|
|
9252
|
+
const externalConfig = options.externalLocation;
|
|
9253
|
+
const onServeStart = options.onServeStart ?? null;
|
|
9254
|
+
const backlog = options.backlog ?? 16;
|
|
9255
|
+
const announcementSink = options.announcementSink ?? process.stdout;
|
|
9256
|
+
if (existsSync2(sockPath)) {
|
|
9257
|
+
try {
|
|
9258
|
+
unlinkSync3(sockPath);
|
|
9259
|
+
} catch {}
|
|
9307
9260
|
}
|
|
9308
|
-
|
|
9309
|
-
|
|
9310
|
-
|
|
9261
|
+
let describePromise = null;
|
|
9262
|
+
function describeInfo() {
|
|
9263
|
+
if (!describePromise) {
|
|
9264
|
+
describePromise = buildDescribeBatch(protocol.name, protocol.getMethods(), serverId).then(({ batch, metadata }) => ({
|
|
9311
9265
|
batch,
|
|
9312
9266
|
protocolHash: metadata.get("vgi_rpc.protocol_hash") ?? ""
|
|
9313
9267
|
}));
|
|
9314
9268
|
}
|
|
9315
|
-
return
|
|
9269
|
+
return describePromise;
|
|
9316
9270
|
}
|
|
9317
|
-
|
|
9318
|
-
|
|
9319
|
-
|
|
9320
|
-
|
|
9321
|
-
|
|
9322
|
-
|
|
9323
|
-
` + ` Server: ${serverVersion}
|
|
9324
|
-
` + " Direction: the client did not send a vgi_rpc.protocol_version " + "metadata key. This is either a vgi-rpc framework bug or a " + "non-VGI client connecting to a VGI worker.");
|
|
9325
|
-
}
|
|
9326
|
-
let clientParts;
|
|
9327
|
-
try {
|
|
9328
|
-
clientParts = parseProtocolVersion(clientVersion);
|
|
9329
|
-
} catch {
|
|
9330
|
-
throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
|
|
9331
|
-
` + ` Client: ${clientVersion}
|
|
9332
|
-
` + ` Server: ${serverVersion}
|
|
9333
|
-
` + " Direction: client sent a malformed protocol_version. " + "Expected canonical semver MAJOR.MINOR.PATCH.");
|
|
9271
|
+
let serveStartFired = false;
|
|
9272
|
+
async function notifyTransport() {
|
|
9273
|
+
if (serveStartFired)
|
|
9274
|
+
return;
|
|
9275
|
+
if (onServeStart) {
|
|
9276
|
+
await onServeStart("unix" /* UNIX */);
|
|
9334
9277
|
}
|
|
9335
|
-
|
|
9278
|
+
serveStartFired = true;
|
|
9279
|
+
}
|
|
9280
|
+
const server = createServer({ allowHalfOpen: false });
|
|
9281
|
+
let activeConnections = 0;
|
|
9282
|
+
let idleTimer = null;
|
|
9283
|
+
let resolveDone = () => {};
|
|
9284
|
+
let rejectDone = () => {};
|
|
9285
|
+
const done = new Promise((resolve2, reject) => {
|
|
9286
|
+
resolveDone = resolve2;
|
|
9287
|
+
rejectDone = reject;
|
|
9288
|
+
});
|
|
9289
|
+
let stopped = false;
|
|
9290
|
+
function armIdleTimer() {
|
|
9291
|
+
if (idleTimeoutS <= 0)
|
|
9336
9292
|
return;
|
|
9293
|
+
if (idleTimer)
|
|
9294
|
+
clearTimeout(idleTimer);
|
|
9295
|
+
idleTimer = setTimeout(() => {
|
|
9296
|
+
if (activeConnections === 0 && !stopped) {
|
|
9297
|
+
shutdown();
|
|
9298
|
+
}
|
|
9299
|
+
}, idleTimeoutS * 1000);
|
|
9300
|
+
}
|
|
9301
|
+
function disarmIdleTimer() {
|
|
9302
|
+
if (idleTimer) {
|
|
9303
|
+
clearTimeout(idleTimer);
|
|
9304
|
+
idleTimer = null;
|
|
9337
9305
|
}
|
|
9338
|
-
const clientOlder = clientParts[0] < serverParts[0] || clientParts[0] === serverParts[0] && clientParts[1] < serverParts[1];
|
|
9339
|
-
const direction = clientOlder ? `client is too old; upgrade the VGI extension/client to a version supporting protocol_version ${serverVersion}.` : `server is too old; upgrade the VGI worker to a version supporting protocol_version ${clientVersion}.`;
|
|
9340
|
-
throw new ProtocolVersionError(`VGI client/worker protocol_version mismatch.
|
|
9341
|
-
` + ` Client: ${clientVersion}
|
|
9342
|
-
` + ` Server: ${serverVersion}
|
|
9343
|
-
` + ` Direction: ${direction}`);
|
|
9344
9306
|
}
|
|
9345
|
-
async
|
|
9346
|
-
|
|
9347
|
-
|
|
9348
|
-
|
|
9349
|
-
|
|
9307
|
+
async function shutdown() {
|
|
9308
|
+
if (stopped)
|
|
9309
|
+
return;
|
|
9310
|
+
stopped = true;
|
|
9311
|
+
disarmIdleTimer();
|
|
9312
|
+
await new Promise((resolve2) => {
|
|
9313
|
+
server.close(() => resolve2());
|
|
9314
|
+
});
|
|
9315
|
+
try {
|
|
9316
|
+
unlinkSync3(sockPath);
|
|
9317
|
+
} catch {}
|
|
9318
|
+
resolveDone();
|
|
9319
|
+
}
|
|
9320
|
+
server.on("connection", (socket) => {
|
|
9321
|
+
activeConnections += 1;
|
|
9322
|
+
disarmIdleTimer();
|
|
9323
|
+
handleConnection(socket).catch((err2) => {
|
|
9324
|
+
process.stderr.write(`vgi-rpc/unix: connection failed: ${err2?.message ?? err2}
|
|
9350
9325
|
`);
|
|
9351
|
-
}
|
|
9352
|
-
|
|
9353
|
-
|
|
9326
|
+
}).finally(() => {
|
|
9327
|
+
activeConnections -= 1;
|
|
9328
|
+
socket.destroy();
|
|
9329
|
+
if (activeConnections === 0 && !stopped) {
|
|
9330
|
+
armIdleTimer();
|
|
9331
|
+
}
|
|
9332
|
+
});
|
|
9333
|
+
});
|
|
9334
|
+
server.on("error", (err2) => {
|
|
9335
|
+
if (stopped)
|
|
9336
|
+
return;
|
|
9337
|
+
rejectDone(err2);
|
|
9338
|
+
});
|
|
9339
|
+
async function handleConnection(socket) {
|
|
9340
|
+
const reader = await IpcStreamReader.create(socket);
|
|
9341
|
+
const writer = new IpcStreamWriter(socket);
|
|
9354
9342
|
try {
|
|
9343
|
+
await notifyTransport();
|
|
9355
9344
|
while (true) {
|
|
9356
|
-
|
|
9357
|
-
|
|
9358
|
-
|
|
9359
|
-
|
|
9360
|
-
|
|
9361
|
-
|
|
9345
|
+
try {
|
|
9346
|
+
await serveOnce(reader, writer);
|
|
9347
|
+
} catch (e) {
|
|
9348
|
+
const err2 = e;
|
|
9349
|
+
if (err2?.message?.includes("closed") || err2?.message?.includes("Expected Schema Message") || err2?.message?.includes("null or length 0") || err2?.message?.includes("EOF") || err2?.code === "EPIPE" || err2?.code === "ERR_STREAM_PREMATURE_CLOSE" || err2?.code === "ERR_STREAM_DESTROYED") {
|
|
9350
|
+
return;
|
|
9351
|
+
}
|
|
9352
|
+
throw e;
|
|
9353
|
+
}
|
|
9362
9354
|
}
|
|
9363
|
-
throw e;
|
|
9364
9355
|
} finally {
|
|
9365
|
-
|
|
9356
|
+
try {
|
|
9357
|
+
await reader.cancel();
|
|
9358
|
+
} catch {}
|
|
9366
9359
|
}
|
|
9367
9360
|
}
|
|
9368
|
-
async
|
|
9361
|
+
async function serveOnce(reader, writer) {
|
|
9369
9362
|
const stream = await reader.readStream();
|
|
9370
9363
|
if (!stream) {
|
|
9371
9364
|
throw new Error("EOF");
|
|
@@ -9373,7 +9366,7 @@ class VgiRpcServer {
|
|
|
9373
9366
|
const { schema: schema2, batches } = stream;
|
|
9374
9367
|
if (batches.length === 0) {
|
|
9375
9368
|
const err2 = new RpcError("ProtocolError", "Request stream contains no batches", "");
|
|
9376
|
-
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2,
|
|
9369
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, serverId, null);
|
|
9377
9370
|
await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
|
|
9378
9371
|
return;
|
|
9379
9372
|
}
|
|
@@ -9387,63 +9380,46 @@ class VgiRpcServer {
|
|
|
9387
9380
|
params = parsed.params;
|
|
9388
9381
|
requestId = parsed.requestId;
|
|
9389
9382
|
} catch (e) {
|
|
9390
|
-
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, e,
|
|
9383
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, e, serverId, null);
|
|
9391
9384
|
await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
|
|
9392
|
-
if (e instanceof VersionError || e instanceof RpcError)
|
|
9385
|
+
if (e instanceof VersionError || e instanceof RpcError)
|
|
9393
9386
|
return;
|
|
9394
|
-
}
|
|
9395
9387
|
throw e;
|
|
9396
9388
|
}
|
|
9397
|
-
if (methodName === DESCRIBE_METHOD_NAME &&
|
|
9398
|
-
const { batch:
|
|
9399
|
-
await writer.writeStream(
|
|
9389
|
+
if (methodName === DESCRIBE_METHOD_NAME && enableDescribe) {
|
|
9390
|
+
const { batch: descBatch } = await describeInfo();
|
|
9391
|
+
await writer.writeStream(descBatch.schema, [descBatch]);
|
|
9400
9392
|
return;
|
|
9401
9393
|
}
|
|
9402
|
-
const methods =
|
|
9394
|
+
const methods = protocol.getMethods();
|
|
9403
9395
|
const method = methods.get(methodName);
|
|
9404
9396
|
if (!method) {
|
|
9405
9397
|
const available = [...methods.keys()].sort();
|
|
9406
|
-
const err2 = new
|
|
9407
|
-
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2,
|
|
9398
|
+
const err2 = new Error(`Unknown method: '${methodName}'. Available methods: [${available.join(", ")}]`);
|
|
9399
|
+
const errBatch = buildErrorBatch(EMPTY_SCHEMA6, err2, serverId, requestId);
|
|
9408
9400
|
await writer.writeStream(EMPTY_SCHEMA6, [errBatch]);
|
|
9409
9401
|
return;
|
|
9410
9402
|
}
|
|
9411
|
-
if (this.protocol.protocolVersionParts !== null) {
|
|
9412
|
-
try {
|
|
9413
|
-
const md = batch.metadata;
|
|
9414
|
-
this.checkProtocolVersion(md?.get(PROTOCOL_VERSION_KEY));
|
|
9415
|
-
} catch (exc) {
|
|
9416
|
-
const errSchema = method.type === "unary" /* UNARY */ ? method.resultSchema : EMPTY_SCHEMA6;
|
|
9417
|
-
const errBatch = buildErrorBatch(errSchema, exc, this.serverId, requestId);
|
|
9418
|
-
await writer.writeStream(errSchema, [errBatch]);
|
|
9419
|
-
return;
|
|
9420
|
-
}
|
|
9421
|
-
}
|
|
9422
9403
|
const methodType = method.type === "unary" /* UNARY */ ? "unary" : "stream";
|
|
9423
9404
|
let requestData;
|
|
9424
9405
|
try {
|
|
9425
9406
|
requestData = serializeBatch(batch);
|
|
9426
9407
|
} catch {}
|
|
9427
|
-
|
|
9428
|
-
if (methodType === "stream") {
|
|
9429
|
-
streamId = randomStreamId();
|
|
9430
|
-
}
|
|
9431
|
-
const { protocolHash } = await this.describeInfo();
|
|
9408
|
+
const { protocolHash } = await describeInfo();
|
|
9432
9409
|
const info = {
|
|
9433
9410
|
method: methodName,
|
|
9434
9411
|
methodType,
|
|
9435
|
-
serverId
|
|
9412
|
+
serverId,
|
|
9436
9413
|
requestId,
|
|
9437
|
-
protocol:
|
|
9414
|
+
protocol: protocol.name,
|
|
9438
9415
|
protocolHash,
|
|
9439
|
-
protocolVersion
|
|
9440
|
-
kind: "
|
|
9416
|
+
protocolVersion,
|
|
9417
|
+
kind: "unix" /* UNIX */,
|
|
9441
9418
|
principal: "",
|
|
9442
9419
|
authDomain: "",
|
|
9443
9420
|
authenticated: false,
|
|
9444
9421
|
remoteAddr: "",
|
|
9445
|
-
requestData
|
|
9446
|
-
streamId
|
|
9422
|
+
requestData
|
|
9447
9423
|
};
|
|
9448
9424
|
const stats = {
|
|
9449
9425
|
inputBatches: 0,
|
|
@@ -9453,22 +9429,44 @@ class VgiRpcServer {
|
|
|
9453
9429
|
inputBytes: 0,
|
|
9454
9430
|
outputBytes: 0
|
|
9455
9431
|
};
|
|
9456
|
-
const token =
|
|
9432
|
+
const token = dispatchHook?.onDispatchStart(info);
|
|
9457
9433
|
let dispatchError;
|
|
9458
9434
|
applyDefaults(params, method.defaults);
|
|
9459
9435
|
try {
|
|
9460
9436
|
if (method.type === "unary" /* UNARY */) {
|
|
9461
|
-
await dispatchUnary(method, params, writer,
|
|
9437
|
+
await dispatchUnary(method, params, writer, serverId, requestId, externalConfig, "unix" /* UNIX */);
|
|
9462
9438
|
} else {
|
|
9463
|
-
await dispatchStream(method, params, writer, reader,
|
|
9439
|
+
await dispatchStream(method, params, writer, reader, serverId, requestId, externalConfig, "unix" /* UNIX */);
|
|
9464
9440
|
}
|
|
9465
9441
|
} catch (e) {
|
|
9466
9442
|
dispatchError = e instanceof Error ? e : new Error(String(e));
|
|
9467
9443
|
throw e;
|
|
9468
9444
|
} finally {
|
|
9469
|
-
|
|
9445
|
+
dispatchHook?.onDispatchEnd(token, info, stats, dispatchError);
|
|
9470
9446
|
}
|
|
9471
9447
|
}
|
|
9448
|
+
await new Promise((resolve2, reject) => {
|
|
9449
|
+
server.listen({ path: sockPath, backlog }, () => resolve2());
|
|
9450
|
+
server.once("error", (err2) => reject(err2));
|
|
9451
|
+
});
|
|
9452
|
+
try {
|
|
9453
|
+
const { chmodSync } = await import("node:fs");
|
|
9454
|
+
chmodSync(sockPath, 384);
|
|
9455
|
+
} catch {}
|
|
9456
|
+
options.onBound?.(sockPath);
|
|
9457
|
+
announcementSink.write(`UNIX:${sockPath}
|
|
9458
|
+
`);
|
|
9459
|
+
if (idleTimeoutS > 0) {
|
|
9460
|
+
setTimeout(() => {
|
|
9461
|
+
if (activeConnections === 0 && !stopped)
|
|
9462
|
+
armIdleTimer();
|
|
9463
|
+
}, startupGraceS * 1000).unref?.();
|
|
9464
|
+
}
|
|
9465
|
+
return {
|
|
9466
|
+
socketPath: sockPath,
|
|
9467
|
+
stop: shutdown,
|
|
9468
|
+
done
|
|
9469
|
+
};
|
|
9472
9470
|
}
|
|
9473
9471
|
export {
|
|
9474
9472
|
unpackStateToken,
|
|
@@ -9563,4 +9561,4 @@ export {
|
|
|
9563
9561
|
ARROW_CONTENT_TYPE
|
|
9564
9562
|
};
|
|
9565
9563
|
|
|
9566
|
-
//# debugId=
|
|
9564
|
+
//# debugId=4D9D32B49233679B64756E2164756E21
|