Lazy initialize sandboxes (workers) upon first need
parent
a5b8fce3eb
commit
5d9329a531
|
@ -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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
|
@ -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);
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
Loading…
Reference in New Issue