openvibers/openvibe.codes Commits
Move Codes' shutdown onto openvibe-sdk/service (#4)
Changes
@@@ -6,30 +6,40 @@* client secret are set.*/const { createApp } = require('./app');++const { gracefulStop } = require('openvibe-sdk/service');--(async () => {--const { app, ctx } = await createApp();--const { config } = ctx;++/**++ * The process stop (openvibe-sdk/service, docs/service.md's 5 s family): the HTTP drain runs++ * (defaults 4000/5000), then the outbox settles and the store closes; past the deadline the process++ * exits 0, as the hand-rolled 5 s timer did. Exported so a test can inject `exit` and `signals: false`.++ */++function createLifecycle({ server, ctx, exit, signals }) {++ return gracefulStop({++ name: 'Codes', server, deadlineExitCode: 0, exit, signals,++ close: [() => ctx.outbox.stop(), () => ctx.store.close()],++ });++}--const server = app.listen(config.port, config.host, () => {-- console.log(`[Codes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.store.db.store})`);-- console.log(`[Codes] docs from openvibe-contracts v${ctx.docs.contractsVersion} and openvibe-sdk v${ctx.docs.sdkVersion}; events relay ${ctx.outbox.enabled ? `on → ${config.events.url}` : 'off (events wait in event_outbox)'}`);--});--server.keepAliveTimeout = 65_000;--ctx.outbox.start();--// Keep the JWKS cache fresh in the background (openvibe-sdk/auth): one client per URL, the last--// good keys through outages, exponential backoff, unknown-kid floods throttled, an unref'd timer.--ctx.keys.client.start();++async function start() {++ const { app, ctx } = await createApp();++ const { config } = ctx;--function shutdown(signal) {-- console.log(`[Codes] ${signal}: closing`);-- server.close(async () => {-- try { await ctx.outbox.stop(); } catch { /* best effort */ }-- try { await ctx.store.close(); } catch { /* already closed */ }-- process.exit(0);++ const server = app.listen(config.port, config.host, () => {++ console.log(`[Codes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.store.db.store})`);++ console.log(`[Codes] docs from openvibe-contracts v${ctx.docs.contractsVersion} and openvibe-sdk v${ctx.docs.sdkVersion}; events relay ${ctx.outbox.enabled ? `on → ${config.events.url}` : 'off (events wait in event_outbox)'}`);});-- setTimeout(() => process.exit(0), 5000).unref();++ server.keepAliveTimeout = 65_000;++ ctx.outbox.start();++ // Keep the JWKS cache fresh in the background (openvibe-sdk/auth): one client per URL, the last++ // good keys through outages, exponential backoff, unknown-kid floods throttled, an unref'd timer.++ ctx.keys.client.start();++++ createLifecycle({ server, ctx });++ return { server, ctx };++}++++if (require.main === module) {++ start().catch((err) => { console.error('[Codes] failed to start:', err); process.exit(1); });}--process.on('SIGTERM', () => shutdown('SIGTERM'));--process.on('SIGINT', () => shutdown('SIGINT'));--})().catch((err) => { console.error('[Codes] failed to start:', err); process.exit(1); });++++module.exports = { start, createLifecycle };nnew file mode 100644@@@ -0,0 +1,54 @@++'use strict';++/**++ * openvibe-sdk/service (plan T16/J2): the entry point's graceful stop runs its close steps in order++ * (outbox.stop, then store.close) and resolves exit 0; a second signal while stopping is a no-op.++ */++const assert = require('assert');++const http = require('http');++const { check, done } = require('./helpers/boot');++const { createLifecycle } = require('../server/index');++++const listen = (server) => new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));++++(async () => {++ await check('the entry point stop runs outbox.stop then store.close, then exits 0', async () => {++ const steps = [];++ const ctx = {++ outbox: { stop: async () => { steps.push('outbox.stop'); } },++ store: { close: async () => { steps.push('store.close'); } },++ };++ const server = http.createServer((req, res) => res.end('ok'));++ await listen(server);++++ const exits = [];++ const lifecycle = createLifecycle({ server, ctx, exit: (code) => exits.push(code), signals: false });++ const code = await lifecycle.stop('SIGTERM');++++ assert.strictEqual(code, 0);++ assert.deepStrictEqual(exits, [0]);++ assert.deepStrictEqual(steps, ['outbox.stop', 'store.close']);++ assert.strictEqual(server.listening, false);++ });++++ await check('a second signal while stopping is a no-op', async () => {++ let stops = 0; let closes = 0; let exits = 0;++ const ctx = {++ outbox: { stop: async () => { stops += 1; } },++ store: { close: async () => { closes += 1; } },++ };++ const server = http.createServer((req, res) => res.end('ok'));++ await listen(server);++++ const lifecycle = createLifecycle({ server, ctx, exit: () => { exits += 1; }, signals: false });++ const first = lifecycle.stop('SIGTERM');++ const second = lifecycle.stop('SIGINT');++++ assert.strictEqual(first, second);++ assert.deepStrictEqual(await Promise.all([first, second]), [0, 0]);++ assert.strictEqual(stops, 1);++ assert.strictEqual(closes, 1);++ assert.strictEqual(exits, 1);++ });++++ done();++})().catch((err) => { console.error(err); process.exit(1); });
@@ -6,30 +6,40 @@
@@ -6,30 +6,40 @@
* client secret are set.
* client secret are set.
*/
*/
const { createApp } = require('./app');
const { createApp } = require('./app');
+const { gracefulStop } = require('openvibe-sdk/service');
-(async () => {
-const { app, ctx } = await createApp();
-const { config } = ctx;
+/**
+ * The process stop (openvibe-sdk/service, docs/service.md's 5 s family): the HTTP drain runs
+ * (defaults 4000/5000), then the outbox settles and the store closes; past the deadline the process
+ * exits 0, as the hand-rolled 5 s timer did. Exported so a test can inject `exit` and `signals: false`.
+ */
+function createLifecycle({ server, ctx, exit, signals }) {
+ return gracefulStop({
+ name: 'Codes', server, deadlineExitCode: 0, exit, signals,
+ close: [() => ctx.outbox.stop(), () => ctx.store.close()],
+ });
+}
-const server = app.listen(config.port, config.host, () => {
- console.log(`[Codes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.store.db.store})`);
- console.log(`[Codes] docs from openvibe-contracts v${ctx.docs.contractsVersion} and openvibe-sdk v${ctx.docs.sdkVersion}; events relay ${ctx.outbox.enabled ? `on → ${config.events.url}` : 'off (events wait in event_outbox)'}`);
-});
-server.keepAliveTimeout = 65_000;
-ctx.outbox.start();
-// Keep the JWKS cache fresh in the background (openvibe-sdk/auth): one client per URL, the last
-// good keys through outages, exponential backoff, unknown-kid floods throttled, an unref'd timer.
-ctx.keys.client.start();
+async function start() {
+ const { app, ctx } = await createApp();
+ const { config } = ctx;
-function shutdown(signal) {
- console.log(`[Codes] ${signal}: closing`);
- server.close(async () => {
- try { await ctx.outbox.stop(); } catch { /* best effort */ }
- try { await ctx.store.close(); } catch { /* already closed */ }
- process.exit(0);
+ const server = app.listen(config.port, config.host, () => {
+ console.log(`[Codes] ${config.nodeEnv} on http://${config.host}:${config.port} → ${config.baseUrl} (db ${ctx.store.db.store})`);
+ console.log(`[Codes] docs from openvibe-contracts v${ctx.docs.contractsVersion} and openvibe-sdk v${ctx.docs.sdkVersion}; events relay ${ctx.outbox.enabled ? `on → ${config.events.url}` : 'off (events wait in event_outbox)'}`);
});
});
- setTimeout(() => process.exit(0), 5000).unref();
+ server.keepAliveTimeout = 65_000;
+ ctx.outbox.start();
+ // Keep the JWKS cache fresh in the background (openvibe-sdk/auth): one client per URL, the last
+ // good keys through outages, exponential backoff, unknown-kid floods throttled, an unref'd timer.
+ ctx.keys.client.start();
+
+ createLifecycle({ server, ctx });
+ return { server, ctx };
+}
+
+if (require.main === module) {
+ start().catch((err) => { console.error('[Codes] failed to start:', err); process.exit(1); });
}
}
-process.on('SIGTERM', () => shutdown('SIGTERM'));
-process.on('SIGINT', () => shutdown('SIGINT'));
-})().catch((err) => { console.error('[Codes] failed to start:', err); process.exit(1); });
+
+module.exports = { start, createLifecycle };
new file mode 100644
new file mode 100644
@@ -0,0 +1,54 @@
@@ -0,0 +1,54 @@
+'use strict';
+/**
+ * openvibe-sdk/service (plan T16/J2): the entry point's graceful stop runs its close steps in order
+ * (outbox.stop, then store.close) and resolves exit 0; a second signal while stopping is a no-op.
+ */
+const assert = require('assert');
+const http = require('http');
+const { check, done } = require('./helpers/boot');
+const { createLifecycle } = require('../server/index');
+
+const listen = (server) => new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
+
+(async () => {
+ await check('the entry point stop runs outbox.stop then store.close, then exits 0', async () => {
+ const steps = [];
+ const ctx = {
+ outbox: { stop: async () => { steps.push('outbox.stop'); } },
+ store: { close: async () => { steps.push('store.close'); } },
+ };
+ const server = http.createServer((req, res) => res.end('ok'));
+ await listen(server);
+
+ const exits = [];
+ const lifecycle = createLifecycle({ server, ctx, exit: (code) => exits.push(code), signals: false });
+ const code = await lifecycle.stop('SIGTERM');
+
+ assert.strictEqual(code, 0);
+ assert.deepStrictEqual(exits, [0]);
+ assert.deepStrictEqual(steps, ['outbox.stop', 'store.close']);
+ assert.strictEqual(server.listening, false);
+ });
+
+ await check('a second signal while stopping is a no-op', async () => {
+ let stops = 0; let closes = 0; let exits = 0;
+ const ctx = {
+ outbox: { stop: async () => { stops += 1; } },
+ store: { close: async () => { closes += 1; } },
+ };
+ const server = http.createServer((req, res) => res.end('ok'));
+ await listen(server);
+
+ const lifecycle = createLifecycle({ server, ctx, exit: () => { exits += 1; }, signals: false });
+ const first = lifecycle.stop('SIGTERM');
+ const second = lifecycle.stop('SIGINT');
+
+ assert.strictEqual(first, second);
+ assert.deepStrictEqual(await Promise.all([first, second]), [0, 0]);
+ assert.strictEqual(stops, 1);
+ assert.strictEqual(closes, 1);
+ assert.strictEqual(exits, 1);
+ });
+
+ done();
+})().catch((err) => { console.error(err); process.exit(1); });