Lazy initialize sandboxes (workers) upon first need

pull/109/head
Zef Hemel 2022-11-01 09:57:20 +01:00
parent a5b8fce3eb
commit 5d9329a531
6 changed files with 40 additions and 18 deletions

View File

@ -5,9 +5,10 @@ import { AssetBundle, AssetJson } from "./asset_bundle/bundle.ts";
export class Plug<HookT> { export class Plug<HookT> {
system: System<HookT>; system: System<HookT>;
sandbox: Sandbox; sandbox?: Sandbox;
public manifest?: Manifest<HookT>; public manifest?: Manifest<HookT>;
public assets?: AssetBundle; public assets?: AssetBundle;
private sandboxFactory: (plug: Plug<HookT>) => Sandbox;
readonly runtimeEnv: RuntimeEnvironment; readonly runtimeEnv: RuntimeEnvironment;
grantedPermissions: string[] = []; grantedPermissions: string[] = [];
name: string; name: string;
@ -20,21 +21,35 @@ export class Plug<HookT> {
) { ) {
this.system = system; this.system = system;
this.name = name; this.name = name;
this.sandbox = sandboxFactory(this); this.sandboxFactory = sandboxFactory;
// this.sandbox = sandboxFactory(this);
this.runtimeEnv = system.runtimeEnv; this.runtimeEnv = system.runtimeEnv;
this.version = new Date().getTime(); this.version = new Date().getTime();
} }
async load(manifest: Manifest<HookT>) { // Lazy load sandbox, guarantees that the sandbox is loaded
async ensureSandbox() {
if (!this.sandbox) {
console.log("Now starting sandbox for", this.name);
// Kick off worker
this.sandbox = this.sandboxFactory(this);
// Push in any dependencies
for (
const [dep, code] of Object.entries(this.manifest!.dependencies || {})
) {
await this.sandbox.loadDependency(dep, code);
}
await this.system.emit("sandboxInitialized", this.sandbox, this);
}
}
load(manifest: Manifest<HookT>) {
this.manifest = manifest; this.manifest = manifest;
this.assets = new AssetBundle( this.assets = new AssetBundle(
manifest.assets ? manifest.assets as AssetJson : {}, manifest.assets ? manifest.assets as AssetJson : {},
); );
// TODO: These need to be explicitly granted, not just taken // TODO: These need to be explicitly granted, not just taken
this.grantedPermissions = manifest.requiredPermissions || []; this.grantedPermissions = manifest.requiredPermissions || [];
for (const [dep, code] of Object.entries(manifest.dependencies || {})) {
await this.sandbox.loadDependency(dep, code);
}
} }
syscall(name: string, args: any[]): Promise<any> { syscall(name: string, args: any[]): Promise<any> {
@ -57,6 +72,8 @@ export class Plug<HookT> {
if (!funDef) { if (!funDef) {
throw new Error(`Function ${name} not found in manifest`); throw new Error(`Function ${name} not found in manifest`);
} }
await this.ensureSandbox();
const sandbox = this.sandbox!;
if (funDef.redirect) { if (funDef.redirect) {
// Function redirect, look up // Function redirect, look up
// deno-lint-ignore no-this-alias // deno-lint-ignore no-this-alias
@ -73,18 +90,20 @@ export class Plug<HookT> {
} }
return plug.invoke(name, args); return plug.invoke(name, args);
} }
if (!this.sandbox.isLoaded(name)) { if (!sandbox.isLoaded(name)) {
if (!this.canInvoke(name)) { if (!this.canInvoke(name)) {
throw new Error( throw new Error(
`Function ${name} is not available in ${this.runtimeEnv}`, `Function ${name} is not available in ${this.runtimeEnv}`,
); );
} }
await this.sandbox.load(name, funDef.code!); await sandbox.load(name, funDef.code!);
} }
return await this.sandbox.invoke(name, args); return await sandbox.invoke(name, args);
} }
stop() { stop() {
this.sandbox.stop(); if (this.sandbox) {
this.sandbox.stop();
}
} }
} }

View File

@ -147,11 +147,11 @@ Deno.test("Preload dependencies", async () => {
const system = new System("server"); const system = new System("server");
system.on({ system.on({
plugLoaded: async (plug) => { sandboxInitialized: async (sandbox) => {
for ( for (
const [modName, code] of Object.entries(globalModules.dependencies!) const [modName, code] of Object.entries(globalModules.dependencies!)
) { ) {
await plug.sandbox.loadDependency(modName, code as string); await sandbox.loadDependency(modName, code as string);
} }
}, },
}); });

View File

@ -6,7 +6,9 @@ export default function sandboxSyscalls(system: System<any>): SysCallMapping {
"sandbox.getLogs": (): LogEntry[] => { "sandbox.getLogs": (): LogEntry[] => {
let allLogs: LogEntry[] = []; let allLogs: LogEntry[] = [];
for (const plug of system.loadedPlugs.values()) { for (const plug of system.loadedPlugs.values()) {
allLogs = allLogs.concat(plug.sandbox.logBuffer); if (plug.sandbox) {
allLogs = allLogs.concat(plug.sandbox.logBuffer);
}
} }
allLogs = allLogs.sort((a, b) => a.date - b.date); allLogs = allLogs.sort((a, b) => a.date - b.date);
return allLogs; return allLogs;

View File

@ -1,6 +1,6 @@
import { Hook, Manifest, RuntimeEnvironment } from "./types.ts"; import { Hook, Manifest, RuntimeEnvironment } from "./types.ts";
import { EventEmitter } from "./event.ts"; import { EventEmitter } from "./event.ts";
import { SandboxFactory } from "./sandbox.ts"; import { Sandbox, SandboxFactory } from "./sandbox.ts";
import { Plug } from "./plug.ts"; import { Plug } from "./plug.ts";
export interface SysCallMapping { export interface SysCallMapping {
@ -11,6 +11,7 @@ export type SystemJSON<HookT> = Manifest<HookT>[];
export type SystemEvents<HookT> = { export type SystemEvents<HookT> = {
plugLoaded: (plug: Plug<HookT>) => void | Promise<void>; plugLoaded: (plug: Plug<HookT>) => void | Promise<void>;
sandboxInitialized(sandbox: Sandbox, plug: Plug<HookT>): void | Promise<void>;
plugUnloaded: (name: string) => void | Promise<void>; plugUnloaded: (name: string) => void | Promise<void>;
}; };

View File

@ -138,13 +138,13 @@ export class HttpServer {
this.system.addHook(new EndpointHook(this.app, "/_")); this.system.addHook(new EndpointHook(this.app, "/_"));
this.system.on({ this.system.on({
plugLoaded: async (plug) => { sandboxInitialized: async (sandbox) => {
for ( for (
const [modName, code] of Object.entries( const [modName, code] of Object.entries(
this.globalModules.dependencies!, this.globalModules.dependencies!,
) )
) { ) {
await plug.sandbox.loadDependency(modName, code as string); await sandbox.loadDependency(modName, code as string);
} }
}, },
}); });

View File

@ -245,13 +245,13 @@ export class Editor {
).json(); ).json();
this.system.on({ this.system.on({
plugLoaded: async (plug) => { sandboxInitialized: async (sandbox) => {
for ( for (
const [modName, code] of Object.entries( const [modName, code] of Object.entries(
globalModules.dependencies, globalModules.dependencies,
) )
) { ) {
await plug.sandbox.loadDependency(modName, code as string); await sandbox.loadDependency(modName, code as string);
} }
}, },
}); });