Revert jsRunner changes to vm

This commit is contained in:
Adria Navarro 2024-02-09 16:36:43 +01:00
parent e39bd1869e
commit 7ce9756d8c
4 changed files with 93 additions and 32 deletions

View File

@ -1,4 +1,5 @@
import { IdentityContext, VM } from "@budibase/types" import { IdentityContext, VM } from "@budibase/types"
import { ExecutionTimeTracker } from "../timers"
// 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 = {
@ -9,5 +10,6 @@ export type ContextMap = {
isScim?: boolean isScim?: boolean
automationId?: string automationId?: string
isMigrating?: boolean isMigrating?: boolean
jsExecutionTracker?: ExecutionTimeTracker
vm?: VM vm?: VM
} }

View File

@ -20,3 +20,43 @@ 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`
)
}
}
}

View File

@ -72,9 +72,9 @@ const environment = {
HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT, HTTP_MB_LIMIT: process.env.HTTP_MB_LIMIT,
FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main", FORKED_PROCESS_NAME: process.env.FORKED_PROCESS_NAME || "main",
JS_PER_INVOCATION_TIMEOUT_MS: JS_PER_INVOCATION_TIMEOUT_MS:
parseIntSafe(process.env.JS_PER_INVOCATION_TIMEOUT_MS) || 1000, parseIntSafe(process.env.JS_PER_EXECUTION_TIME_LIMIT_MS) || 1000,
JS_PER_REQUEST_TIMEOUT_MS: parseIntSafe( JS_PER_REQUEST_TIMEOUT_MS: parseIntSafe(
process.env.JS_PER_REQUEST_TIMEOUT_MS process.env.JS_PER_REQUEST_TIME_LIMIT_MS
), ),
// old // old
CLIENT_ID: process.env.CLIENT_ID, CLIENT_ID: process.env.CLIENT_ID,

View File

@ -1,42 +1,61 @@
import vm from "vm"
import env from "../environment" import env from "../environment"
import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates" import { setJSRunner } from "@budibase/string-templates"
import { context, timers } from "@budibase/backend-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import { IsolatedVM } from "./vm" type TrackerFn = <T>(f: () => T) => T
import { context } from "@budibase/backend-core"
export function init() { export function init() {
setJSRunner((js: string, ctx: Record<string, any>) => { setJSRunner((js: string, ctx: vm.Context) => {
return tracer.trace("runJS", {}, span => { return tracer.trace("runJS", {}, span => {
try { const perRequestLimit = env.JS_PER_REQUEST_TIMEOUT_MS
const bbCtx = context.getCurrentContext()! let track: TrackerFn = f => f()
if (perRequestLimit) {
let { vm } = bbCtx const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span =>
if (!vm) { context.getCurrentContext()
// Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource )
const { helpers, ...ctxToPass } = ctx if (bbCtx) {
if (!bbCtx.jsExecutionTracker) {
vm = new IsolatedVM({ span?.addTags({
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, createdExecutionTracker: true,
invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, })
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, 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
)
}) })
.withContext(ctxToPass)
.withHelpers()
bbCtx.vm = vm
} }
const result = vm.execute(js)
return result
} catch (error: any) {
if (error.message === "Script execution timed out.") {
throw new JsErrorTimeout()
}
throw error
} }
ctx = {
...ctx,
alert: undefined,
setInterval: undefined,
setTimeout: undefined,
}
vm.createContext(ctx)
return track(() =>
vm.runInNewContext(js, ctx, {
timeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
})
)
}) })
}) })
} }