Use isolated-vm
This commit is contained in:
parent
2fe6dbe8a5
commit
6f6100e7a2
|
@ -1,68 +1,41 @@
|
||||||
import vm from "vm"
|
|
||||||
import env from "../environment"
|
import env from "../environment"
|
||||||
import { setJSRunner, setOnErrorLog } from "@budibase/string-templates"
|
import { setJSRunner, JsErrorTimeout } from "@budibase/string-templates"
|
||||||
import { context, logging, timers } from "@budibase/backend-core"
|
|
||||||
import tracer from "dd-trace"
|
import tracer from "dd-trace"
|
||||||
import { serializeError } from "serialize-error"
|
|
||||||
|
|
||||||
type TrackerFn = <T>(f: () => T) => T
|
import { IsolatedVM } from "./vm"
|
||||||
|
import { context } from "@budibase/backend-core"
|
||||||
|
|
||||||
export function init() {
|
export function init() {
|
||||||
setJSRunner((js: string, ctx: vm.Context) => {
|
setJSRunner((js: string, ctx: Record<string, any>) => {
|
||||||
return tracer.trace("runJS", {}, span => {
|
return tracer.trace("runJS", {}, span => {
|
||||||
const perRequestLimit = env.JS_PER_REQUEST_TIMEOUT_MS
|
try {
|
||||||
let track: TrackerFn = f => f()
|
const bbCtx = context.getCurrentContext()!
|
||||||
if (perRequestLimit) {
|
|
||||||
const bbCtx = tracer.trace("runJS.getCurrentContext", {}, span =>
|
let { vm } = bbCtx
|
||||||
context.getCurrentContext()
|
if (!vm) {
|
||||||
)
|
// Can't copy the native helpers into the isolate. We just ignore them as they are handled properly from the helpersSource
|
||||||
if (bbCtx) {
|
const { helpers, ...ctxToPass } = ctx
|
||||||
if (!bbCtx.jsExecutionTracker) {
|
|
||||||
span?.addTags({
|
vm = new IsolatedVM({
|
||||||
createdExecutionTracker: true,
|
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
|
||||||
})
|
invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
|
||||||
bbCtx.jsExecutionTracker = tracer.trace(
|
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
|
||||||
"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
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
ctx = {
|
const result = vm.execute(js)
|
||||||
...ctx,
|
|
||||||
alert: undefined,
|
|
||||||
setInterval: undefined,
|
|
||||||
setTimeout: undefined,
|
|
||||||
}
|
|
||||||
|
|
||||||
vm.createContext(ctx)
|
return result
|
||||||
return track(() =>
|
} catch (error: any) {
|
||||||
vm.runInNewContext(js, ctx, {
|
if (error.message === "Script execution timed out.") {
|
||||||
timeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
|
throw new JsErrorTimeout()
|
||||||
})
|
}
|
||||||
)
|
throw error
|
||||||
|
}
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
|
|
||||||
if (env.LOG_JS_ERRORS) {
|
|
||||||
setOnErrorLog((error: Error) => {
|
|
||||||
logging.logWarn(JSON.stringify(serializeError(error)))
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -127,10 +127,16 @@ class QueryRunner {
|
||||||
|
|
||||||
// transform as required
|
// transform as required
|
||||||
if (transformer) {
|
if (transformer) {
|
||||||
const runner = new ScriptRunner(transformer, {
|
const runner = new ScriptRunner(
|
||||||
data: rows,
|
transformer,
|
||||||
params: enrichedParameters,
|
{
|
||||||
})
|
data: rows,
|
||||||
|
params: enrichedParameters,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
parseBson: datasource.source === SourceName.MONGODB,
|
||||||
|
}
|
||||||
|
)
|
||||||
rows = runner.execute()
|
rows = runner.execute()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,28 +1,40 @@
|
||||||
import fetch from "node-fetch"
|
import tracer, { Span } from "dd-trace"
|
||||||
import { VM, VMScript } from "vm2"
|
import env from "../environment"
|
||||||
|
import { IsolatedVM } from "../jsRunner/vm"
|
||||||
|
|
||||||
const JS_TIMEOUT_MS = 1000
|
const JS_TIMEOUT_MS = 1000
|
||||||
|
|
||||||
class ScriptRunner {
|
class ScriptRunner {
|
||||||
vm: VM
|
private code: string
|
||||||
results: { out: string }
|
private vm: IsolatedVM
|
||||||
script: VMScript
|
|
||||||
|
|
||||||
constructor(script: string, context: any) {
|
private tracerSpan: Span
|
||||||
const code = `let fn = () => {\n${script}\n}; results.out = fn();`
|
|
||||||
this.vm = new VM({
|
constructor(script: string, context: any, { parseBson = false } = {}) {
|
||||||
timeout: JS_TIMEOUT_MS,
|
this.tracerSpan = tracer.startSpan("scriptRunner", { tags: { parseBson } })
|
||||||
})
|
|
||||||
this.results = { out: "" }
|
this.code = `(() => {${script}})();`
|
||||||
this.vm.setGlobals(context)
|
this.vm = new IsolatedVM({
|
||||||
this.vm.setGlobal("fetch", fetch)
|
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
|
||||||
this.vm.setGlobal("results", this.results)
|
invocationTimeout: JS_TIMEOUT_MS,
|
||||||
this.script = new VMScript(code)
|
}).withContext(context)
|
||||||
|
|
||||||
|
if (parseBson && context.data) {
|
||||||
|
this.vm = this.vm.withParsingBson(context.data)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
execute() {
|
execute() {
|
||||||
this.vm.run(this.script)
|
const result = tracer.trace(
|
||||||
return this.results.out
|
"scriptRunner.execute",
|
||||||
|
{ childOf: this.tracerSpan },
|
||||||
|
() => {
|
||||||
|
const result = this.vm.execute(this.code)
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
)
|
||||||
|
this.tracerSpan.finish()
|
||||||
|
return result
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue