From 6f1b6bb64f355dfc1be3508f02a31e7a91200300 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Tue, 3 Dec 2024 22:51:29 -0300 Subject: [PATCH 1/3] make Deno capture unhandled exceptions and rejections and report them to the server https://github.com/RocketChat/Rocket.Chat/pull/33997 --- deno-runtime/error-handlers.ts | 33 +++++++++++++++++++ deno-runtime/main.ts | 3 ++ src/definition/metadata/AppMethod.ts | 2 ++ .../runtime/deno/AppsEngineDenoRuntime.ts | 18 ++++++++++ 4 files changed, 56 insertions(+) create mode 100644 deno-runtime/error-handlers.ts diff --git a/deno-runtime/error-handlers.ts b/deno-runtime/error-handlers.ts new file mode 100644 index 000000000..1e042e0f2 --- /dev/null +++ b/deno-runtime/error-handlers.ts @@ -0,0 +1,33 @@ +import * as Messenger from './lib/messenger.ts'; + +export function unhandledRejectionListener(event: PromiseRejectionEvent) { + event.preventDefault(); + + const { type, reason } = event; + + Messenger.sendNotification({ + method: 'unhandledRejection', + params: [ + { + type, + reason: reason instanceof Error ? reason.message : reason, + timestamp: new Date(), + }, + ], + }); +} + +export function unhandledExceptionListener(event: ErrorEvent) { + event.preventDefault(); + + const { type, message, filename, lineno, colno } = event; + Messenger.sendNotification({ + method: 'uncaughtException', + params: [{ type, message, filename, lineno, colno }], + }); +} + +export default function registerErrorListeners() { + addEventListener('unhandledrejection', unhandledRejectionListener); + addEventListener('error', unhandledExceptionListener); +} diff --git a/deno-runtime/main.ts b/deno-runtime/main.ts index 09be5258e..fa2822908 100644 --- a/deno-runtime/main.ts +++ b/deno-runtime/main.ts @@ -21,6 +21,7 @@ import videoConferenceHandler from './handlers/videoconference-handler.ts'; import apiHandler from './handlers/api-handler.ts'; import handleApp from './handlers/app/handler.ts'; import handleScheduler from './handlers/scheduler-handler.ts'; +import registerErrorListeners from './error-handlers.ts'; type Handlers = { app: typeof handleApp; @@ -126,4 +127,6 @@ async function main() { } } +registerErrorListeners(); + main(); diff --git a/src/definition/metadata/AppMethod.ts b/src/definition/metadata/AppMethod.ts index 007ef7d0c..65e4d5b8d 100644 --- a/src/definition/metadata/AppMethod.ts +++ b/src/definition/metadata/AppMethod.ts @@ -100,4 +100,6 @@ export enum AppMethod { EXECUTE_POST_USER_STATUS_CHANGED = 'executePostUserStatusChanged', // Runtime specific methods RUNTIME_RESTART = 'runtime:restart', + RUNTIME_UNCAUGHT_EXCEPTION = 'runtime:uncaughtException', + RUNTIME_UNHANDLED_REJECTION = 'runtime:unhandledRejection', } diff --git a/src/server/runtime/deno/AppsEngineDenoRuntime.ts b/src/server/runtime/deno/AppsEngineDenoRuntime.ts index 00c07aa8a..15a74d29e 100644 --- a/src/server/runtime/deno/AppsEngineDenoRuntime.ts +++ b/src/server/runtime/deno/AppsEngineDenoRuntime.ts @@ -13,6 +13,7 @@ import type { IParseAppPackageResult } from '../../compiler'; import { AppConsole, type ILoggerStorageEntry } from '../../logging'; import type { AppAccessorManager, AppApiManager } from '../../managers'; import { AppStatus } from '../../../definition/AppStatus'; +import type { AppMethod } from '../../../definition/metadata'; import { bundleLegacyApp } from './bundler'; import { ProcessMessenger } from './ProcessMessenger'; import { LivenessManager } from './LivenessManager'; @@ -535,12 +536,29 @@ export class DenoRuntimeSubprocessController extends EventEmitter { case 'log': console.log('SUBPROCESS LOG', message); break; + case 'unhandledRejection': + case 'uncaughtException': + await this.logUnhandledError(`runtime:${method}`, message); + break; + default: console.warn('Unrecognized method from sub process'); break; } } + private async logUnhandledError( + method: `${AppMethod.RUNTIME_UNCAUGHT_EXCEPTION | AppMethod.RUNTIME_UNHANDLED_REJECTION}`, + message: jsonrpc.IParsedObjectRequest | jsonrpc.IParsedObjectNotification, + ) { + this.debug('Unhandled error of type "%s" caught in subprocess', method); + + const logger = new AppConsole(method); + logger.error(message.payload); + + await this.logStorage.storeEntries(AppConsole.toStorageEntry(this.getAppId(), logger)); + } + private async handleResultMessage(message: jsonrpc.IParsedObjectError | jsonrpc.IParsedObjectSuccess): Promise { const { id } = message.payload; From d1792f0a6c40ce3acd361829140bc28d5300046e Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Wed, 4 Dec 2024 10:14:27 -0300 Subject: [PATCH 2/3] apps-engine timeout config https://github.com/RocketChat/Rocket.Chat/pull/33690 --- src/server/ProxiedApp.ts | 6 +++--- src/server/runtime/deno/AppsEngineDenoRuntime.ts | 14 +++++++++++++- 2 files changed, 16 insertions(+), 4 deletions(-) diff --git a/src/server/ProxiedApp.ts b/src/server/ProxiedApp.ts index adc98e26c..1af91b3ce 100644 --- a/src/server/ProxiedApp.ts +++ b/src/server/ProxiedApp.ts @@ -54,9 +54,9 @@ export class ProxiedApp { let options; // Pre events need to be fast as they block the user - if (method.startsWith('checkPre') || method.startsWith('executePre')) { - options = { timeout: 1000 }; - } + // if (method.startsWith('checkPre') || method.startsWith('executePre')) { + // options = { timeout: 1000 }; + // } try { return await this.appRuntime.sendRequest({ method: `app:${method}`, params: args }, options); diff --git a/src/server/runtime/deno/AppsEngineDenoRuntime.ts b/src/server/runtime/deno/AppsEngineDenoRuntime.ts index 15a74d29e..6f9e55ae3 100644 --- a/src/server/runtime/deno/AppsEngineDenoRuntime.ts +++ b/src/server/runtime/deno/AppsEngineDenoRuntime.ts @@ -53,6 +53,18 @@ const COMMAND_PONG = '_zPONG'; export const JSONRPC_METHOD_NOT_FOUND = -32601; +export function getRuntimeTimeout() { + const defaultTimeout = 30000; + const envValue = isFinite(process.env.APPS_ENGINE_RUNTIME_TIMEOUT as any) ? Number(process.env.APPS_ENGINE_RUNTIME_TIMEOUT) : defaultTimeout; + + if (envValue < 0) { + console.log('Environment variable APPS_ENGINE_RUNTIME_TIMEOUT has a negative value, ignoring...'); + return defaultTimeout; + } + + return envValue; +} + export function isValidOrigin(accessor: string): accessor is typeof ALLOWED_ACCESSOR_METHODS[number] { return ALLOWED_ACCESSOR_METHODS.includes(accessor as any); } @@ -88,7 +100,7 @@ export class DenoRuntimeSubprocessController extends EventEmitter { private readonly debug: debug.Debugger; private readonly options = { - timeout: 10000, + timeout: getRuntimeTimeout(), }; private readonly accessors: AppAccessorManager; From 556200cf0d4653edddb7d6b992c9b883ea6ac2e9 Mon Sep 17 00:00:00 2001 From: Douglas Gubert Date: Wed, 4 Dec 2024 10:18:22 -0300 Subject: [PATCH 3/3] 1.43.3-rc.0 --- package-lock.json | 2 +- package.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package-lock.json b/package-lock.json index 541d72628..0327e5dc6 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/apps-engine", - "version": "1.43.2", + "version": "1.43.3-rc.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 4397a093b..03b80d9d5 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@rocket.chat/apps-engine", - "version": "1.43.2", + "version": "1.43.3-rc.0", "description": "The engine code for the Rocket.Chat Apps which manages, runs, translates, coordinates and all of that.", "main": "index", "typings": "index",