Isolate and context re-use.
This commit is contained in:
parent
290dde125e
commit
c508a435d6
|
@ -1,5 +1,5 @@
|
||||||
import { IdentityContext } from "@budibase/types"
|
import { IdentityContext } from "@budibase/types"
|
||||||
import { ExecutionTimeTracker } from "../timers"
|
import { Isolate, Context } from "isolated-vm"
|
||||||
|
|
||||||
// keep this out of Budibase types, don't want to expose context info
|
// keep this out of Budibase types, don't want to expose context info
|
||||||
export type ContextMap = {
|
export type ContextMap = {
|
||||||
|
@ -10,5 +10,6 @@ export type ContextMap = {
|
||||||
isScim?: boolean
|
isScim?: boolean
|
||||||
automationId?: string
|
automationId?: string
|
||||||
isMigrating?: boolean
|
isMigrating?: boolean
|
||||||
jsExecutionTracker?: ExecutionTimeTracker
|
jsIsolate: Isolate
|
||||||
|
jsContext: Context
|
||||||
}
|
}
|
||||||
|
|
|
@ -20,41 +20,3 @@ export function cleanup() {
|
||||||
}
|
}
|
||||||
intervals = []
|
intervals = []
|
||||||
}
|
}
|
||||||
|
|
||||||
export class ExecutionTimeoutError extends Error {
|
|
||||||
public readonly name = "ExecutionTimeoutError"
|
|
||||||
}
|
|
||||||
|
|
||||||
export class ExecutionTimeTracker {
|
|
||||||
static withLimit(limitMs: number) {
|
|
||||||
return new ExecutionTimeTracker(limitMs)
|
|
||||||
}
|
|
||||||
|
|
||||||
constructor(readonly limitMs: number) {}
|
|
||||||
|
|
||||||
private totalTimeMs = 0
|
|
||||||
|
|
||||||
track<T>(f: () => T): T {
|
|
||||||
this.checkLimit()
|
|
||||||
const start = process.hrtime.bigint()
|
|
||||||
try {
|
|
||||||
return f()
|
|
||||||
} finally {
|
|
||||||
const end = process.hrtime.bigint()
|
|
||||||
this.totalTimeMs += Number(end - start) / 1e6
|
|
||||||
this.checkLimit()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
get elapsedMS() {
|
|
||||||
return this.totalTimeMs
|
|
||||||
}
|
|
||||||
|
|
||||||
checkLimit() {
|
|
||||||
if (this.totalTimeMs > this.limitMs) {
|
|
||||||
throw new ExecutionTimeoutError(
|
|
||||||
`Execution time limit of ${this.limitMs}ms exceeded: ${this.totalTimeMs}ms`
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
@ -1,76 +1,41 @@
|
||||||
import ivm from "isolated-vm"
|
import ivm from "isolated-vm"
|
||||||
import env from "./environment"
|
import env from "./environment"
|
||||||
import { setJSRunner } from "@budibase/string-templates"
|
import { setJSRunner } from "@budibase/string-templates"
|
||||||
import { context, timers } from "@budibase/backend-core"
|
import { context } from "@budibase/backend-core"
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
|
import { readFileSync } from "fs"
|
||||||
|
|
||||||
type TrackerFn = <T>(f: () => T) => T
|
const helpersSource = readFileSync(
|
||||||
|
"node_modules/@budibase/string-templates/dist/bundle.mjs",
|
||||||
|
"utf8"
|
||||||
|
)
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
setJSRunner((js: string, ctx: Record<string, any>) => {
|
setJSRunner((js: string, ctx: Record<string, any>) => {
|
||||||
return tracer.trace("runJS", {}, span => {
|
return tracer.trace("runJS", {}, span => {
|
||||||
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
|
const bbCtx = context.getCurrentContext()!
|
||||||
let track: TrackerFn = f => f()
|
if (!bbCtx.jsIsolate) {
|
||||||
if (perRequestLimit) {
|
bbCtx.jsIsolate = new ivm.Isolate({ memoryLimit: 64 })
|
||||||
const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span =>
|
bbCtx.jsContext = bbCtx.jsIsolate.createContextSync()
|
||||||
context.getCurrentContext()
|
const helpersModule = bbCtx.jsIsolate.compileModuleSync(helpersSource)
|
||||||
)
|
helpersModule.instantiateSync(bbCtx.jsContext, () => {
|
||||||
if (bbCtx) {
|
throw new Error("No imports allowed")
|
||||||
if (!bbCtx.jsExecutionTracker) {
|
|
||||||
span?.addTags({
|
|
||||||
createdExecutionTracker: true,
|
|
||||||
})
|
|
||||||
bbCtx.jsExecutionTracker = tracer.trace(
|
|
||||||
"runJS.createExecutionTimeTracker",
|
|
||||||
{},
|
|
||||||
span => timers.ExecutionTimeTracker.withLimit(perRequestLimit)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
span?.addTags({
|
|
||||||
js: {
|
|
||||||
limitMS: bbCtx.jsExecutionTracker.limitMs,
|
|
||||||
elapsedMS: bbCtx.jsExecutionTracker.elapsedMS,
|
|
||||||
},
|
|
||||||
})
|
|
||||||
// We call checkLimit() here to prevent paying the cost of creating
|
|
||||||
// a new VM context below when we don't need to.
|
|
||||||
tracer.trace("runJS.checkLimitAndBind", {}, span => {
|
|
||||||
bbCtx.jsExecutionTracker!.checkLimit()
|
|
||||||
track = bbCtx.jsExecutionTracker!.track.bind(
|
|
||||||
bbCtx.jsExecutionTracker
|
|
||||||
)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
const isolate = new ivm.Isolate({ memoryLimit: 64 })
|
|
||||||
const vm = isolate.createContextSync()
|
|
||||||
const jail = vm.global
|
|
||||||
jail.setSync("global", jail.derefInto())
|
|
||||||
|
|
||||||
for (let key in ctx) {
|
|
||||||
let value
|
|
||||||
if (["alert", "setInterval", "setTimeout"].includes(key)) {
|
|
||||||
value = undefined
|
|
||||||
} else {
|
|
||||||
value = ctx[key]
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof value === "function") {
|
|
||||||
js = value.toString() + "\n" + js
|
|
||||||
continue
|
|
||||||
} else {
|
|
||||||
value = new ivm.ExternalCopy(value).copyInto({ release: true })
|
|
||||||
}
|
|
||||||
jail.setSync(key, value)
|
|
||||||
}
|
|
||||||
|
|
||||||
const script = isolate.compileScriptSync(js)
|
|
||||||
|
|
||||||
return track(() => {
|
|
||||||
return script.runSync(vm, {
|
|
||||||
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
|
||||||
})
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const perRequestLimit = env.JS_PER_REQUEST_TIME_LIMIT_MS
|
||||||
|
if (perRequestLimit) {
|
||||||
|
const cpuMs = Number(bbCtx.jsIsolate.cpuTime) / 1e6
|
||||||
|
if (cpuMs > perRequestLimit) {
|
||||||
|
throw new Error(
|
||||||
|
`CPU time limit exceeded (${cpuMs}ms > ${perRequestLimit}ms)`
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const script = bbCtx.jsIsolate.compileScriptSync(js)
|
||||||
|
return script.runSync(bbCtx.jsContext, {
|
||||||
|
timeout: env.JS_PER_EXECUTION_TIME_LIMIT_MS,
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue