Skip to content
Snippets Groups Projects
Unverified Commit 87495466 authored by dionisio-bot[bot]'s avatar dionisio-bot[bot] Committed by GitHub
Browse files

fix: app subprocess restart on error and better reporting (#35284)

parent 88992d76
No related branches found
No related tags found
No related merge requests found
---
'@rocket.chat/apps-engine': patch
---
Adds simple app subprocess metrics report
---
'@rocket.chat/apps-engine': patch
---
Attempts to restart an app subprocess if the spawn command fails
---
'@rocket.chat/apps-engine': patch
---
Fixes an issue while collecting the error message from a failed restart attempt of an app subprocess
---
'@rocket.chat/apps-engine': patch
---
Prevents app:getStatus requests from timing out in some cases
......@@ -19,41 +19,41 @@ import handleOnUpdate from './handleOnUpdate.ts';
export default async function handleApp(method: string, params: unknown): Promise<Defined | JsonRpcError> {
const [, appMethod] = method.split(':');
// We don't want the getStatus method to generate logs, so we handle it separately
if (appMethod === 'getStatus') {
return handleGetStatus();
}
try {
// We don't want the getStatus method to generate logs, so we handle it separately
if (appMethod === 'getStatus') {
return await handleGetStatus();
}
// `app` will be undefined if the method here is "app:construct"
const app = AppObjectRegistry.get<App>('app');
// `app` will be undefined if the method here is "app:construct"
const app = AppObjectRegistry.get<App>('app');
app?.getLogger().debug(`'${appMethod}' is being called...`);
app?.getLogger().debug(`'${appMethod}' is being called...`);
if (uikitInteractions.includes(appMethod)) {
return handleUIKitInteraction(appMethod, params).then((result) => {
if (result instanceof JsonRpcError) {
app?.getLogger().debug(`'${appMethod}' was unsuccessful.`, result.message);
} else {
app?.getLogger().debug(`'${appMethod}' was successfully called! The result is:`, result);
}
if (uikitInteractions.includes(appMethod)) {
return handleUIKitInteraction(appMethod, params).then((result) => {
if (result instanceof JsonRpcError) {
app?.getLogger().debug(`'${appMethod}' was unsuccessful.`, result.message);
} else {
app?.getLogger().debug(`'${appMethod}' was successfully called! The result is:`, result);
}
return result;
});
}
return result;
});
}
if (appMethod.startsWith('check') || appMethod.startsWith('execute')) {
return handleListener(appMethod, params).then((result) => {
if (result instanceof JsonRpcError) {
app?.getLogger().debug(`'${appMethod}' was unsuccessful.`, result.message);
} else {
app?.getLogger().debug(`'${appMethod}' was successfully called! The result is:`, result);
}
if (appMethod.startsWith('check') || appMethod.startsWith('execute')) {
return handleListener(appMethod, params).then((result) => {
if (result instanceof JsonRpcError) {
app?.getLogger().debug(`'${appMethod}' was unsuccessful.`, result.message);
} else {
app?.getLogger().debug(`'${appMethod}' was successfully called! The result is:`, result);
}
return result;
});
}
return result;
});
}
try {
let result: Defined | JsonRpcError;
switch (appMethod) {
......
......@@ -59,6 +59,10 @@ export const Queue = new (class Queue {
this.queue.push(encoder.encode(message));
this.processQueue();
}
public getCurrentSize() {
return this.queue.length;
}
});
export const Transport = new (class Transporter {
......
......@@ -330,18 +330,24 @@ export class DenoRuntimeSubprocessController extends EventEmitter {
}
private waitUntilReady(): Promise<void> {
if (this.state === 'ready') {
return;
}
return new Promise((resolve, reject) => {
const timeoutId = setTimeout(() => reject(new Error(`[${this.getAppId()}] Timeout: app process not ready`)), this.options.timeout);
let timeoutId: NodeJS.Timeout;
if (this.state === 'ready') {
const handler = () => {
clearTimeout(timeoutId);
return resolve();
}
resolve();
};
this.once('ready', () => {
clearTimeout(timeoutId);
return resolve();
});
timeoutId = setTimeout(() => {
this.off('ready', handler);
reject(new Error(`[${this.getAppId()}] Timeout: app process not ready`));
}, this.options.timeout);
this.once('ready', handler);
});
}
......
......@@ -92,6 +92,7 @@ export class LivenessManager {
this.pingTimeoutConsecutiveCount = 0;
this.subprocess.once('exit', this.handleExit.bind(this));
this.subprocess.once('error', this.handleError.bind(this));
}
/**
......@@ -165,6 +166,11 @@ export class LivenessManager {
this.messenger.send(COMMAND_PING);
}
private handleError(err: Error) {
this.debug('App has failed to start.`', err);
this.restartProcess(err.message);
}
private handleExit(exitCode: number, signal: string) {
this.pingAbortController.emit('abort');
......@@ -195,8 +201,6 @@ export class LivenessManager {
return;
}
this.pingTimeoutConsecutiveCount = 0;
this.restartCount++;
this.restartLog.push({
reason,
source,
......
import { ChildProcess } from 'child_process';
import type { ChildProcess } from 'child_process';
import type { JsonRpc } from 'jsonrpc-lite';
......@@ -33,7 +33,7 @@ export class ProcessMessenger {
}
private switchStrategy() {
if (this.deno instanceof ChildProcess) {
if (this.deno?.stdin?.writable) {
this._sendStrategy = this.strategySend.bind(this);
// Get a clean encoder
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment