Merge branch 'master' into budi-8067-sql-testing-more-datasource-types

This commit is contained in:
Sam Rose 2024-03-13 11:29:46 +00:00 committed by GitHub
commit 05cd71107b
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 59 additions and 50 deletions

View File

@ -1,5 +1,5 @@
{ {
"version": "2.21.8", "version": "2.21.9",
"npmClient": "yarn", "npmClient": "yarn",
"packages": [ "packages": [
"packages/*", "packages/*",

View File

@ -1,5 +1,4 @@
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 = {
@ -10,6 +9,6 @@ export type ContextMap = {
isScim?: boolean isScim?: boolean
automationId?: string automationId?: string
isMigrating?: boolean isMigrating?: boolean
jsExecutionTracker?: ExecutionTimeTracker
vm?: VM vm?: VM
cleanup?: (() => void | Promise<void>)[]
} }

View File

@ -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`
)
}
}
}

View File

@ -1,6 +1,7 @@
import Router from "@koa/router" import Router from "@koa/router"
import { auth, middleware, env as envCore } from "@budibase/backend-core" import { auth, middleware, env as envCore } from "@budibase/backend-core"
import currentApp from "../middleware/currentapp" import currentApp from "../middleware/currentapp"
import cleanup from "../middleware/cleanup"
import zlib from "zlib" import zlib from "zlib"
import { mainRoutes, staticRoutes, publicRoutes } from "./routes" import { mainRoutes, staticRoutes, publicRoutes } from "./routes"
import { middleware as pro } from "@budibase/pro" import { middleware as pro } from "@budibase/pro"
@ -62,6 +63,8 @@ if (apiEnabled()) {
.use(auth.auditLog) .use(auth.auditLog)
// @ts-ignore // @ts-ignore
.use(migrations) .use(migrations)
// @ts-ignore
.use(cleanup)
// authenticated routes // authenticated routes
for (let route of mainRoutes) { for (let route of mainRoutes) {

View File

@ -8,6 +8,7 @@ import {
import { context, logging } from "@budibase/backend-core" import { context, logging } from "@budibase/backend-core"
import tracer from "dd-trace" import tracer from "dd-trace"
import { IsolatedVM } from "./vm" import { IsolatedVM } from "./vm"
import type { VM } from "@budibase/types"
export function init() { export function init() {
setJSRunner((js: string, ctx: Record<string, any>) => { setJSRunner((js: string, ctx: Record<string, any>) => {
@ -15,18 +16,23 @@ export function init() {
try { try {
const bbCtx = context.getCurrentContext() const bbCtx = context.getCurrentContext()
const vm = bbCtx?.vm const vm =
? bbCtx.vm bbCtx?.vm ||
: new IsolatedVM({ new IsolatedVM({
memoryLimit: env.JS_RUNNER_MEMORY_LIMIT, memoryLimit: env.JS_RUNNER_MEMORY_LIMIT,
invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS, invocationTimeout: env.JS_PER_INVOCATION_TIMEOUT_MS,
isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS, isolateAccumulatedTimeout: env.JS_PER_REQUEST_TIMEOUT_MS,
}).withHelpers() }).withHelpers()
if (bbCtx) { if (bbCtx && !bbCtx.vm) {
// If we have a context, we want to persist it to reuse the isolate
bbCtx.vm = vm bbCtx.vm = vm
bbCtx.cleanup = bbCtx.cleanup || []
bbCtx.cleanup.push(() => vm.close())
} }
// Because we can't pass functions into an Isolate, we remove them from
// the passed context and rely on the withHelpers() method to add them
// back in.
const { helpers, ...rest } = ctx const { helpers, ...rest } = ctx
return vm.withContext(rest, () => vm.execute(js)) return vm.withContext(rest, () => vm.execute(js))
} catch (error: any) { } catch (error: any) {

View File

@ -195,6 +195,11 @@ export class IsolatedVM implements VM {
return result[this.runResultKey] return result[this.runResultKey]
} }
close(): void {
this.vm.release()
this.isolate.dispose()
}
private registerCallbacks(functions: Record<string, any>) { private registerCallbacks(functions: Record<string, any>) {
const libId = crypto.randomUUID().replace(/-/g, "") const libId = crypto.randomUUID().replace(/-/g, "")

View File

@ -0,0 +1,33 @@
import { Ctx } from "@budibase/types"
import { context } from "@budibase/backend-core"
import { tracer } from "dd-trace"
export default async (ctx: Ctx, next: any) => {
const resp = await next()
const current = context.getCurrentContext()
if (!current || !current.cleanup) {
return resp
}
let errors = []
for (let fn of current.cleanup) {
try {
await tracer.trace("cleanup", async span => {
await fn()
})
} catch (e) {
// We catch errors here to ensure we at least attempt to run all cleanup
// functions. We'll throw the first error we encounter after all cleanup
// functions have been run.
errors.push(e)
}
}
delete current.cleanup
if (errors.length > 0) {
throw errors[0]
}
return resp
}

View File

@ -1,4 +1,5 @@
export interface VM { export interface VM {
execute(code: string): any execute(code: string): any
withContext<T>(context: Record<string, any>, executeWithContext: () => T): T withContext<T>(context: Record<string, any>, executeWithContext: () => T): T
close(): void
} }