Skip to content
This repository was archived by the owner on Nov 5, 2025. It is now read-only.

Commit 1f2b310

Browse files
authored
Merge fixes from core monorepo (#810)
2 parents e4bd643 + 7042b29 commit 1f2b310

File tree

7 files changed

+110
-33
lines changed

7 files changed

+110
-33
lines changed

.github/workflows/build_and_test.yml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,7 @@ jobs:
6565
run: |
6666
tar czf /tmp/workspace.tar.gz .
6767
68-
- uses: actions/upload-artifact@v2
68+
- uses: actions/upload-artifact@v4
6969
with:
7070
name: workspace
7171
path: /tmp/workspace.tar.gz
@@ -80,7 +80,7 @@ jobs:
8080
with:
8181
node-version: '14.19.3'
8282

83-
- uses: actions/download-artifact@v2
83+
- uses: actions/download-artifact@v4
8484
with:
8585
name: workspace
8686
path: /tmp
@@ -102,7 +102,7 @@ jobs:
102102
with:
103103
node-version: '14.19.3'
104104

105-
- uses: actions/download-artifact@v2
105+
- uses: actions/download-artifact@v4
106106
with:
107107
name: workspace
108108
path: /tmp
@@ -124,7 +124,7 @@ jobs:
124124
with:
125125
node-version: '14.19.3'
126126

127-
- uses: actions/download-artifact@v2
127+
- uses: actions/download-artifact@v4
128128
with:
129129
name: workspace
130130
path: /tmp
@@ -140,10 +140,11 @@ jobs:
140140
run: |
141141
tar czf /tmp/workspace.tar.gz .
142142
143-
- uses: actions/upload-artifact@v2
143+
- uses: actions/upload-artifact@v4
144144
with:
145145
name: workspace
146146
path: /tmp/workspace.tar.gz
147+
overwrite: true
147148

148149
publish:
149150
runs-on: ubuntu-latest
@@ -156,7 +157,7 @@ jobs:
156157
with:
157158
node-version: '14.19.3'
158159

159-
- uses: actions/download-artifact@v2
160+
- uses: actions/download-artifact@v4
160161
with:
161162
name: workspace
162163
path: /tmp

src/definition/metadata/AppMethod.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,4 +98,6 @@ export enum AppMethod {
9898
EXECUTE_POST_USER_LOGGED_IN = 'executePostUserLoggedIn',
9999
EXECUTE_POST_USER_LOGGED_OUT = 'executePostUserLoggedOut',
100100
EXECUTE_POST_USER_STATUS_CHANGED = 'executePostUserStatusChanged',
101+
// Runtime specific methods
102+
RUNTIME_RESTART = 'runtime:restart',
101103
}

src/server/compiler/AppCompiler.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,7 @@ export class AppCompiler {
2121
throw new Error(`Invalid App package for "${storage.info.name}". Could not find the classFile (${storage.info.classFile}) file.`);
2222
}
2323

24-
const runtime = await manager.getRuntime().startRuntimeForApp(packageResult);
24+
const runtime = await manager.getRuntime().startRuntimeForApp(packageResult, storage);
2525

2626
const app = new ProxiedApp(manager, storage, runtime);
2727

src/server/managers/AppRuntimeManager.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import type { AppManager } from '../AppManager';
22
import type { IParseAppPackageResult } from '../compiler';
33
import { DenoRuntimeSubprocessController } from '../runtime/deno/AppsEngineDenoRuntime';
4+
import type { IAppStorageItem } from '../storage';
45

56
export type AppRuntimeParams = {
67
appId: string;
@@ -21,14 +22,18 @@ export class AppRuntimeManager {
2122

2223
constructor(private readonly manager: AppManager) {}
2324

24-
public async startRuntimeForApp(appPackage: IParseAppPackageResult, options = { force: false }): Promise<DenoRuntimeSubprocessController> {
25+
public async startRuntimeForApp(
26+
appPackage: IParseAppPackageResult,
27+
storageItem: IAppStorageItem,
28+
options = { force: false },
29+
): Promise<DenoRuntimeSubprocessController> {
2530
const { id: appId } = appPackage.info;
2631

2732
if (appId in this.subprocesses && !options.force) {
2833
throw new Error('App already has an associated runtime');
2934
}
3035

31-
this.subprocesses[appId] = new DenoRuntimeSubprocessController(this.manager, appPackage);
36+
this.subprocesses[appId] = new DenoRuntimeSubprocessController(this.manager, appPackage, storageItem);
3237

3338
await this.subprocesses[appId].setupApp();
3439

src/server/runtime/deno/AppsEngineDenoRuntime.ts

Lines changed: 58 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -7,11 +7,11 @@ import debugFactory from 'debug';
77

88
import { decoder } from './codec';
99
import type { AppManager } from '../../AppManager';
10-
import type { AppLogStorage } from '../../storage';
10+
import type { AppLogStorage, IAppStorageItem } from '../../storage';
1111
import type { AppBridges } from '../../bridges';
1212
import type { IParseAppPackageResult } from '../../compiler';
13+
import { AppConsole, type ILoggerStorageEntry } from '../../logging';
1314
import type { AppAccessorManager, AppApiManager } from '../../managers';
14-
import type { ILoggerStorageEntry } from '../../logging';
1515
import { AppStatus } from '../../../definition/AppStatus';
1616
import { bundleLegacyApp } from './bundler';
1717
import { ProcessMessenger } from './ProcessMessenger';
@@ -103,7 +103,7 @@ export class DenoRuntimeSubprocessController extends EventEmitter {
103103
private readonly livenessManager: LivenessManager;
104104

105105
// We need to keep the appSource around in case the Deno process needs to be restarted
106-
constructor(manager: AppManager, private readonly appPackage: IParseAppPackageResult) {
106+
constructor(manager: AppManager, private readonly appPackage: IParseAppPackageResult, private readonly storageItem: IAppStorageItem) {
107107
super();
108108

109109
this.debug = baseDebug.extend(appPackage.info.id);
@@ -164,23 +164,38 @@ export class DenoRuntimeSubprocessController extends EventEmitter {
164164
}
165165
}
166166

167-
public async killProcess(): Promise<void> {
167+
/**
168+
* Attempts to kill the process currently controlled by this.deno
169+
*
170+
* @returns boolean - if a process has been killed or not
171+
*/
172+
public async killProcess(): Promise<boolean> {
173+
if (!this.deno) {
174+
this.debug('No child process reference');
175+
return false;
176+
}
177+
178+
let { killed } = this.deno;
179+
168180
// This field is not populated if the process is killed by the OS
169-
if (this.deno.killed) {
181+
if (killed) {
170182
this.debug('App process was already killed');
171-
return;
183+
return killed;
172184
}
173185

174186
// What else should we do?
175187
if (this.deno.kill('SIGKILL')) {
176188
// Let's wait until we get confirmation the process exited
177189
await new Promise<void>((r) => this.deno.on('exit', r));
190+
killed = true;
178191
} else {
179192
this.debug('Tried killing the process but failed. Was it already dead?');
193+
killed = false;
180194
}
181195

182196
delete this.deno;
183197
this.messenger.clearReceiver();
198+
return killed;
184199
}
185200

186201
// Debug purposes, could be deleted later
@@ -200,7 +215,7 @@ export class DenoRuntimeSubprocessController extends EventEmitter {
200215

201216
public async getStatus(): Promise<AppStatus> {
202217
// If the process has been terminated, we can't get the status
203-
if (this.deno.exitCode !== null) {
218+
if (!this.deno || this.deno.exitCode !== null) {
204219
return AppStatus.UNKNOWN;
205220
}
206221

@@ -231,19 +246,49 @@ export class DenoRuntimeSubprocessController extends EventEmitter {
231246

232247
public async restartApp() {
233248
this.debug('Restarting app subprocess');
249+
const logger = new AppConsole('runtime:restart');
250+
251+
logger.info('Starting restart procedure for app subprocess...', this.livenessManager.getRuntimeData());
234252

235253
this.state = 'restarting';
236254

237-
await this.killProcess();
255+
try {
256+
const pid = this.deno?.pid;
238257

239-
await this.setupApp();
258+
const hasKilled = await this.killProcess();
240259

241-
// setupApp() changes the state to 'ready' - we'll need to workaround that for now
242-
this.state = 'restarting';
260+
if (hasKilled) {
261+
logger.debug('Process successfully terminated', { pid });
262+
} else {
263+
logger.warn('Could not terminate process. Maybe it was already dead?', { pid });
264+
}
243265

244-
await this.sendRequest({ method: 'app:initialize' });
266+
await this.setupApp();
267+
logger.info('New subprocess successfully spawned', { pid: this.deno.pid });
245268

246-
this.state = 'ready';
269+
// setupApp() changes the state to 'ready' - we'll need to workaround that for now
270+
this.state = 'restarting';
271+
272+
// When an app has just been installed, the status in the storageItem passed to this controller will be "initialized"
273+
// So, whenever we get that value here, let's just make it 'auto_enabled'
274+
let { status } = this.storageItem;
275+
276+
if (status === AppStatus.INITIALIZED) {
277+
logger.info('Stored status was "initialized". Changing to "auto_enabled"');
278+
status = AppStatus.AUTO_ENABLED;
279+
}
280+
281+
await this.sendRequest({ method: 'app:initialize' });
282+
await this.sendRequest({ method: 'app:setStatus', params: [status] });
283+
284+
this.state = 'ready';
285+
286+
logger.info('Successfully restarted app subprocess');
287+
} catch (e) {
288+
logger.error("Failed to restart app's subprocess", { error: e });
289+
} finally {
290+
await this.logStorage.storeEntries(AppConsole.toStorageEntry(this.getAppId(), logger));
291+
}
247292
}
248293

249294
public getAppId(): string {

src/server/runtime/deno/LivenessManager.ts

Lines changed: 19 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ const defaultOptions: LivenessManager['options'] = {
1010
pingRequestTimeout: 10000,
1111
pingFrequencyInMS: 10000,
1212
consecutiveTimeoutLimit: 4,
13-
maxRestarts: 3,
13+
maxRestarts: Infinity,
1414
};
1515

1616
/**
@@ -65,6 +65,16 @@ export class LivenessManager {
6565
this.options = Object.assign({}, defaultOptions, options);
6666
}
6767

68+
public getRuntimeData() {
69+
const { restartCount, pingTimeoutConsecutiveCount, restartLog } = this;
70+
71+
return {
72+
restartCount,
73+
pingTimeoutConsecutiveCount,
74+
restartLog,
75+
};
76+
}
77+
6878
public attach(deno: ChildProcess) {
6979
this.subprocess = deno;
7080

@@ -122,7 +132,7 @@ export class LivenessManager {
122132

123133
if (reason === 'timeout' && this.pingTimeoutConsecutiveCount >= this.options.consecutiveTimeoutLimit) {
124134
this.debug('Subprocess failed to respond to pings %d consecutive times. Attempting restart...', this.options.consecutiveTimeoutLimit);
125-
this.restartProcess();
135+
this.restartProcess('Too many pings timed out');
126136
return false;
127137
}
128138

@@ -154,17 +164,21 @@ export class LivenessManager {
154164
return;
155165
}
156166

167+
let reason: string;
168+
157169
// Otherwise we try to restart the subprocess, if possible
158170
if (signal) {
159171
this.debug('App has been killed (%s). Attempting restart #%d...', signal, this.restartCount + 1);
172+
reason = `App has been killed with signal ${signal}`;
160173
} else {
161174
this.debug('App has exited with code %d. Attempting restart #%d...', exitCode, this.restartCount + 1);
175+
reason = `App has exited with code ${exitCode}`;
162176
}
163177

164-
this.restartProcess();
178+
this.restartProcess(reason);
165179
}
166180

167-
private restartProcess() {
181+
private restartProcess(reason: string) {
168182
if (this.restartCount >= this.options.maxRestarts) {
169183
this.debug('Limit of restarts reached (%d). Aborting restart...', this.options.maxRestarts);
170184
this.controller.stopApp();
@@ -174,6 +188,7 @@ export class LivenessManager {
174188
this.pingTimeoutConsecutiveCount = 0;
175189
this.restartCount++;
176190
this.restartLog.push({
191+
reason,
177192
restartedAt: new Date(),
178193
source: 'liveness-manager',
179194
pid: this.subprocess.pid,

tests/server/runtime/DenoRuntimeSubprocessController.spec.ts

Lines changed: 16 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,24 +1,28 @@
1-
import * as path from 'path';
21
import * as fs from 'fs/promises';
2+
import * as path from 'path';
33

44
import { TestFixture, Setup, Expect, AsyncTest, SpyOn, Any, AsyncSetupFixture, Teardown } from 'alsatian';
55
import type { SuccessObject } from 'jsonrpc-lite';
66

7-
import { AppAccessorManager, AppApiManager } from '../../../src/server/managers';
8-
import { TestInfastructureSetup } from '../../test-data/utilities';
9-
import { DenoRuntimeSubprocessController } from '../../../src/server/runtime/deno/AppsEngineDenoRuntime';
10-
import type { AppManager } from '../../../src/server/AppManager';
7+
import { AppStatus } from '../../../src/definition/AppStatus';
118
import { UserStatusConnection, UserType } from '../../../src/definition/users';
9+
import type { AppManager } from '../../../src/server/AppManager';
1210
import type { IParseAppPackageResult } from '../../../src/server/compiler';
11+
import { AppAccessorManager, AppApiManager } from '../../../src/server/managers';
12+
import { DenoRuntimeSubprocessController } from '../../../src/server/runtime/deno/AppsEngineDenoRuntime';
13+
import type { IAppStorageItem } from '../../../src/server/storage';
14+
import { TestInfastructureSetup } from '../../test-data/utilities';
1315

14-
@TestFixture('DenoRuntimeSubprocessController')
16+
@TestFixture()
1517
export class DenuRuntimeSubprocessControllerTestFixture {
1618
private manager: AppManager;
1719

1820
private controller: DenoRuntimeSubprocessController;
1921

2022
private appPackage: IParseAppPackageResult;
2123

24+
private appStorageItem: IAppStorageItem;
25+
2226
@AsyncSetupFixture
2327
public async fixture() {
2428
const infrastructure = new TestInfastructureSetup();
@@ -35,11 +39,16 @@ export class DenuRuntimeSubprocessControllerTestFixture {
3539
const appPackage = await fs.readFile(path.join(__dirname, '../../test-data/apps/hello-world-test_0.0.1.zip'));
3640

3741
this.appPackage = await this.manager.getParser().unpackageApp(appPackage);
42+
43+
this.appStorageItem = {
44+
id: 'hello-world-test',
45+
status: AppStatus.MANUALLY_ENABLED,
46+
} as IAppStorageItem;
3847
}
3948

4049
@Setup
4150
public setup() {
42-
this.controller = new DenoRuntimeSubprocessController(this.manager, this.appPackage);
51+
this.controller = new DenoRuntimeSubprocessController(this.manager, this.appPackage, this.appStorageItem);
4352
this.controller.setupApp();
4453
}
4554

0 commit comments

Comments
 (0)