Skip to content

Commit afada33

Browse files
authored
chore(@jsii/kernel): various improvements (#3648)
Moved away from using objects as maps to using actual `Map` objects, which is expected to have better performance characteristics at that kind of scale (lower memory footprint, less churn, ...). Additionally, improved context information produced in error messages around callbacks, etc... Fixes #3638 --- By submitting this pull request, I confirm that my contribution is made under the terms of the [Apache 2.0 license]. [Apache 2.0 license]: https://www.apache.org/licenses/LICENSE-2.0
1 parent c51bf37 commit afada33

File tree

3 files changed

+118
-58
lines changed

3 files changed

+118
-58
lines changed

packages/@jsii/kernel/src/kernel.test.ts

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2191,11 +2191,11 @@ async function createCalculatorSandbox(name: string) {
21912191
return sandbox;
21922192
}
21932193

2194-
const cache: { [module: string]: string } = {};
2194+
const cache = new Map<string, string>();
21952195

21962196
async function preparePackage(module: string, useCache = true) {
2197-
if (module in cache && useCache) {
2198-
return cache[module];
2197+
if (cache.has(module) && useCache) {
2198+
return cache.get(module)!;
21992199
}
22002200

22012201
const staging = await fs.mkdtemp(
@@ -2227,7 +2227,8 @@ async function preparePackage(module: string, useCache = true) {
22272227
});
22282228
});
22292229
const dir = path.join(staging, (await fs.readdir(staging))[0]);
2230-
return (cache[module] = dir);
2230+
cache.set(module, dir);
2231+
return dir;
22312232
}
22322233

22332234
function findPackageRoot(pkg: string) {

packages/@jsii/kernel/src/kernel.ts

Lines changed: 104 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import * as tar from 'tar';
99

1010
import * as api from './api';
1111
import { TOKEN_REF } from './api';
12-
import { ObjectTable, tagJsiiConstructor } from './objects';
12+
import { jsiiTypeFqn, ObjectTable, tagJsiiConstructor } from './objects';
1313
import * as onExit from './on-exit';
1414
import * as wire from './serialization';
1515

@@ -19,11 +19,11 @@ export class Kernel {
1919
*/
2020
public traceEnabled = false;
2121

22-
private assemblies: { [name: string]: Assembly } = {};
22+
private readonly assemblies = new Map<string, Assembly>();
2323
private readonly objects = new ObjectTable(this._typeInfoForFqn.bind(this));
24-
private cbs: { [cbid: string]: Callback } = {};
25-
private waiting: { [cbid: string]: Callback } = {};
26-
private promises: { [prid: string]: AsyncInvocation } = {};
24+
private readonly cbs = new Map<string, Callback>();
25+
private readonly waiting = new Map<string, Callback>();
26+
private readonly promises = new Map<string, AsyncInvocation>();
2727
private nextid = 20000; // incrementing counter for objid, cbid, promiseid
2828
private syncInProgress?: string; // forbids async calls (begin) while processing sync calls (get/set/invoke)
2929
private installDir?: string;
@@ -86,7 +86,7 @@ export class Kernel {
8686

8787
// same version, no-op
8888
this._debug('look up already-loaded assembly', pkgname);
89-
const assm = this.assemblies[pkgname];
89+
const assm = this.assemblies.get(pkgname)!;
9090

9191
return {
9292
assembly: assm.metadata.name,
@@ -311,19 +311,27 @@ export class Kernel {
311311
throw new Error(`${method} is an async method, use "begin" instead`);
312312
}
313313

314+
const fqn = jsiiTypeFqn(obj);
314315
const ret = this._ensureSync(
315316
`method '${objref[TOKEN_REF]}.${method}'`,
316317
() => {
317318
return this._wrapSandboxCode(() =>
318-
fn.apply(obj, this._toSandboxValues(args, ti.parameters)),
319+
fn.apply(
320+
obj,
321+
this._toSandboxValues(
322+
args,
323+
`method ${fqn ? `${fqn}#` : ''}${method}`,
324+
ti.parameters,
325+
),
326+
),
319327
);
320328
},
321329
);
322330

323331
const result = this._fromSandbox(
324332
ret,
325333
ti.returns ?? 'void',
326-
`returned by method ${method}`,
334+
`returned by method ${fqn ? `${fqn}#` : ''}${method}`,
327335
);
328336
this._debug('invoke result', result);
329337

@@ -352,7 +360,14 @@ export class Kernel {
352360

353361
const ret = this._ensureSync(`method '${fqn}.${method}'`, () => {
354362
return this._wrapSandboxCode(() =>
355-
fn.apply(prototype, this._toSandboxValues(args, ti.parameters)),
363+
fn.apply(
364+
prototype,
365+
this._toSandboxValues(
366+
args,
367+
`static method ${fqn}.${method}`,
368+
ti.parameters,
369+
),
370+
),
356371
);
357372
});
358373

@@ -385,8 +400,17 @@ export class Kernel {
385400
throw new Error(`Method ${method} is expected to be an async method`);
386401
}
387402

403+
const fqn = jsiiTypeFqn(obj);
404+
388405
const promise = this._wrapSandboxCode(() =>
389-
fn.apply(obj, this._toSandboxValues(args, ti.parameters)),
406+
fn.apply(
407+
obj,
408+
this._toSandboxValues(
409+
args,
410+
`async method ${fqn ? `${fqn}#` : ''}${method}`,
411+
ti.parameters,
412+
),
413+
),
390414
) as Promise<any>;
391415

392416
// since we are planning to resolve this promise in a different scope
@@ -395,10 +419,10 @@ export class Kernel {
395419
promise.catch((_) => undefined);
396420

397421
const prid = this._makeprid();
398-
this.promises[prid] = {
422+
this.promises.set(prid, {
399423
promise,
400424
method: ti,
401-
};
425+
});
402426

403427
return { promiseid: prid };
404428
}
@@ -408,10 +432,11 @@ export class Kernel {
408432

409433
this._debug('end', promiseid);
410434

411-
const { promise, method } = this.promises[promiseid];
412-
if (promise == null) {
435+
const storedPromise = this.promises.get(promiseid);
436+
if (storedPromise == null) {
413437
throw new Error(`Cannot find promise with ID: ${promiseid}`);
414438
}
439+
const { promise, method } = storedPromise;
415440

416441
let result;
417442
try {
@@ -433,9 +458,9 @@ export class Kernel {
433458

434459
public callbacks(_req?: api.CallbacksRequest): api.CallbacksResponse {
435460
this._debug('callbacks');
436-
const ret = Object.keys(this.cbs).map((cbid) => {
437-
const cb = this.cbs[cbid];
438-
this.waiting[cbid] = cb; // move to waiting
461+
const ret = Array.from(this.cbs.entries()).map(([cbid, cb]) => {
462+
this.waiting.set(cbid, cb); // move to waiting
463+
this.cbs.delete(cbid); // remove from created
439464
const callback: api.Callback = {
440465
cbid,
441466
cookie: cb.override.cookie,
@@ -448,8 +473,6 @@ export class Kernel {
448473
return callback;
449474
});
450475

451-
// move all callbacks to the wait queue and clean the callback queue.
452-
this.cbs = {};
453476
return { callbacks: ret };
454477
}
455478

@@ -458,25 +481,25 @@ export class Kernel {
458481

459482
this._debug('complete', cbid, err, result);
460483

461-
if (!(cbid in this.waiting)) {
484+
const cb = this.waiting.get(cbid);
485+
if (!cb) {
462486
throw new Error(`Callback ${cbid} not found`);
463487
}
464488

465-
const cb = this.waiting[cbid];
466489
if (err) {
467490
this._debug('completed with error:', err);
468491
cb.fail(new Error(err));
469492
} else {
470493
const sandoxResult = this._toSandbox(
471494
result,
472495
cb.expectedReturnType ?? 'void',
473-
`returned by callback ${cbid}`,
496+
`returned by callback ${cb.toString()}`,
474497
);
475498
this._debug('completed with result:', sandoxResult);
476499
cb.succeed(sandoxResult);
477500
}
478501

479-
delete this.waiting[cbid];
502+
this.waiting.delete(cbid);
480503

481504
return { cbid };
482505
}
@@ -506,7 +529,7 @@ export class Kernel {
506529
}
507530

508531
private _addAssembly(assm: Assembly) {
509-
this.assemblies[assm.metadata.name] = assm;
532+
this.assemblies.set(assm.metadata.name, assm);
510533

511534
// add the __jsii__.fqn property on every constructor. this allows
512535
// traversing between the javascript and jsii worlds given any object.
@@ -576,7 +599,13 @@ export class Kernel {
576599
const ctor = ctorResult.ctor;
577600
const obj = this._wrapSandboxCode(
578601
() =>
579-
new ctor(...this._toSandboxValues(requestArgs, ctorResult.parameters)),
602+
new ctor(
603+
...this._toSandboxValues(
604+
requestArgs,
605+
`new ${fqn}`,
606+
ctorResult.parameters,
607+
),
608+
),
580609
);
581610
const objref = this.objects.registerObject(obj, fqn, req.interfaces ?? []);
582611

@@ -807,6 +836,10 @@ export class Kernel {
807836
methodInfo: spec.Method,
808837
) {
809838
const methodName = override.method;
839+
const fqn = jsiiTypeFqn(obj);
840+
const methodContext = `${methodInfo.async ? 'async ' : ''}method${
841+
fqn ? `${fqn}#` : methodName
842+
}`;
810843

811844
if (methodInfo.async) {
812845
// async method override
@@ -816,18 +849,22 @@ export class Kernel {
816849
writable: false,
817850
value: (...methodArgs: any[]) => {
818851
this._debug('invoke async method override', override);
819-
const args = this._toSandboxValues(methodArgs, methodInfo.parameters);
852+
const args = this._toSandboxValues(
853+
methodArgs,
854+
methodContext,
855+
methodInfo.parameters,
856+
);
820857
return new Promise<any>((succeed, fail) => {
821858
const cbid = this._makecbid();
822859
this._debug('adding callback to queue', cbid);
823-
this.cbs[cbid] = {
860+
this.cbs.set(cbid, {
824861
objref,
825862
override,
826863
args,
827864
expectedReturnType: methodInfo.returns ?? 'void',
828865
succeed,
829866
fail,
830-
};
867+
});
831868
});
832869
},
833870
});
@@ -853,7 +890,11 @@ export class Kernel {
853890
invoke: {
854891
objref,
855892
method: methodName,
856-
args: this._fromSandboxValues(methodArgs, methodInfo.parameters),
893+
args: this._fromSandboxValues(
894+
methodArgs,
895+
methodContext,
896+
methodInfo.parameters,
897+
),
857898
},
858899
});
859900
this._debug('Result', result);
@@ -936,7 +977,7 @@ export class Kernel {
936977
}
937978

938979
private _assemblyFor(assemblyName: string) {
939-
const assembly = this.assemblies[assemblyName];
980+
const assembly = this.assemblies.get(assemblyName);
940981
if (!assembly) {
941982
throw new Error(`Could not find assembly: ${assemblyName}`);
942983
}
@@ -966,7 +1007,7 @@ export class Kernel {
9661007
const components = fqn.split('.');
9671008
const moduleName = components[0];
9681009

969-
const assembly = this.assemblies[moduleName];
1010+
const assembly = this.assemblies.get(moduleName);
9701011
if (!assembly) {
9711012
throw new Error(`Module '${moduleName}' not found`);
9721013
}
@@ -1101,7 +1142,6 @@ export class Kernel {
11011142
}
11021143
return typeInfo;
11031144
}
1104-
11051145
private _toSandbox(
11061146
v: any,
11071147
expectedType: wire.OptionalValueOrVoid,
@@ -1140,41 +1180,61 @@ export class Kernel {
11401180
);
11411181
}
11421182

1143-
private _toSandboxValues(xs: any[], parameters?: spec.Parameter[]) {
1144-
return this._boxUnboxParameters(xs, parameters, this._toSandbox.bind(this));
1183+
private _toSandboxValues(
1184+
xs: readonly unknown[],
1185+
methodContext: string,
1186+
parameters: readonly spec.Parameter[] | undefined,
1187+
) {
1188+
return this._boxUnboxParameters(
1189+
xs,
1190+
methodContext,
1191+
parameters,
1192+
this._toSandbox.bind(this),
1193+
);
11451194
}
11461195

1147-
private _fromSandboxValues(xs: any[], parameters?: spec.Parameter[]) {
1196+
private _fromSandboxValues(
1197+
xs: readonly unknown[],
1198+
methodContext: string,
1199+
parameters: readonly spec.Parameter[] | undefined,
1200+
) {
11481201
return this._boxUnboxParameters(
11491202
xs,
1203+
methodContext,
11501204
parameters,
11511205
this._fromSandbox.bind(this),
11521206
);
11531207
}
11541208

11551209
private _boxUnboxParameters(
1156-
xs: any[],
1157-
parameters: spec.Parameter[] | undefined,
1210+
xs: readonly unknown[],
1211+
methodContext: string,
1212+
parameters: readonly spec.Parameter[] = [],
11581213
boxUnbox: (x: any, t: wire.OptionalValueOrVoid, context: string) => any,
11591214
) {
1160-
parameters = [...(parameters ?? [])];
1215+
const parametersCopy = [...parameters];
11611216
const variadic =
1162-
parameters.length > 0 && !!parameters[parameters.length - 1].variadic;
1217+
parametersCopy.length > 0 &&
1218+
!!parametersCopy[parametersCopy.length - 1].variadic;
11631219
// Repeat the last (variadic) type to match the number of actual arguments
1164-
while (variadic && parameters.length < xs.length) {
1165-
parameters.push(parameters[parameters.length - 1]);
1220+
while (variadic && parametersCopy.length < xs.length) {
1221+
parametersCopy.push(parametersCopy[parametersCopy.length - 1]);
11661222
}
1167-
if (xs.length > parameters.length) {
1223+
if (xs.length > parametersCopy.length) {
11681224
throw new Error(
11691225
`Argument list (${JSON.stringify(
11701226
xs,
11711227
)}) not same size as expected argument list (length ${
1172-
parameters.length
1228+
parametersCopy.length
11731229
})`,
11741230
);
11751231
}
11761232
return xs.map((x, i) =>
1177-
boxUnbox(x, parameters![i], `of parameter ${parameters![i].name}`),
1233+
boxUnbox(
1234+
x,
1235+
parametersCopy[i],
1236+
`passed to parameter ${parametersCopy[i].name} of ${methodContext}`,
1237+
),
11781238
);
11791239
}
11801240

@@ -1225,8 +1285,6 @@ export class Kernel {
12251285
* Executes arbitrary code in a VM sandbox.
12261286
*
12271287
* @param code JavaScript code to be executed in the VM
1228-
* @param sandbox a VM context to use for running the code
1229-
* @param sourceMaps source maps to be used in case an exception is thrown
12301288
* @param filename the file name to use for the executed code
12311289
*
12321290
* @returns the result of evaluating the code

0 commit comments

Comments
 (0)