Skip to content

Commit a9bc83d

Browse files
committed
feat(solo): run sim-chain in a separate process
1 parent cd65fff commit a9bc83d

File tree

5 files changed

+185
-11
lines changed

5 files changed

+185
-11
lines changed
+67
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/* global process */
2+
// @ts-check
3+
import anylogger from 'anylogger';
4+
5+
const console = anylogger('shutdown');
6+
7+
export const makeFreshShutdown = () => {
8+
const shutdownThunks = new Set();
9+
10+
let shuttingDown = false;
11+
/** @type {NodeJS.SignalsListener & NodeJS.BeforeExitListener} */
12+
const shutdown = code => {
13+
const sig = typeof code === 'string' && code.startsWith('SIG');
14+
if (sig) {
15+
process.exitCode = 99;
16+
}
17+
if (shuttingDown) {
18+
process.exit();
19+
}
20+
shuttingDown = true;
21+
console.error('Shutting down cleanly...');
22+
const shutdowners = [...shutdownThunks.keys()];
23+
shutdownThunks.clear();
24+
Promise.allSettled([...shutdowners].map(t => Promise.resolve().then(t)))
25+
.then(statuses => {
26+
for (const status of statuses) {
27+
if (status.status === 'rejected') {
28+
console.warn(status.reason);
29+
}
30+
}
31+
console.log('Process terminated!');
32+
})
33+
.catch(error => console.warn('Error shutting down', error))
34+
.finally(() => {
35+
process.exit();
36+
});
37+
};
38+
39+
// gracefully shut down the thunks on process exit
40+
process.on('SIGTERM', shutdown);
41+
process.on('SIGINT', shutdown);
42+
process.on('SIGQUIT', shutdown);
43+
process.on('beforeExit', shutdown);
44+
process.on('uncaughtException', e => {
45+
console.error(e);
46+
shutdown(-1);
47+
});
48+
49+
return {
50+
registerShutdown: thunk => {
51+
shutdownThunks.add(thunk);
52+
return () => {
53+
shutdownThunks.delete(thunk);
54+
};
55+
},
56+
};
57+
};
58+
59+
let cachedShutdown = null;
60+
export const makeCachedShutdown = () => {
61+
if (!cachedShutdown) {
62+
cachedShutdown = makeFreshShutdown();
63+
}
64+
return cachedShutdown;
65+
};
66+
67+
export { makeCachedShutdown as makeShutdown };

packages/solo/src/main.js

-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,6 @@ const log = anylogger('ag-solo');
2020
// As we add more egress types, put the default types in a comma-separated
2121
// string below.
2222
const DEFAULT_EGRESSES = 'cosmos';
23-
process.on('SIGINT', () => process.exit(99));
2423

2524
const AG_SOLO_BASEDIR =
2625
process.env.AG_SOLO_BASEDIR && path.resolve(process.env.AG_SOLO_BASEDIR);

packages/solo/src/pipe-entrypoint.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
/* global process */
2+
// @ts-check
3+
import '@endo/init/pre-bundle-source.js';
4+
import 'node-lmdb';
5+
import '@endo/init';
6+
7+
import { parse, stringify } from '@endo/marshal';
8+
import { makePromiseKit } from '@agoric/promise-kit';
9+
10+
import '@agoric/cosmic-swingset/src/anylogger-agoric.js';
11+
import { connectToFakeChain } from '@agoric/cosmic-swingset/src/sim-chain.js';
12+
13+
// console.error('getting pipe entrypoing started');
14+
const [method, ...margs] = process.argv.slice(2);
15+
16+
const { send: psend } = process;
17+
assert(psend);
18+
const send = (...args) => psend.apply(process, args);
19+
20+
process.on('SIGINT', () => {
21+
// console.log('exiting pipe child');
22+
process.exit(99);
23+
});
24+
25+
const main = async () => {
26+
let baton = makePromiseKit();
27+
let deliverator;
28+
switch (method) {
29+
case 'connectToFakeChain': {
30+
const [basedir, GCI, delay] = margs;
31+
deliverator = await connectToFakeChain(
32+
basedir,
33+
GCI,
34+
Number(delay),
35+
async (...args) => {
36+
// console.log('sending', args);
37+
send(stringify(harden(args)));
38+
baton = makePromiseKit();
39+
return baton.promise;
40+
},
41+
);
42+
break;
43+
}
44+
default:
45+
}
46+
47+
process.on('message', async msg => {
48+
if (msg === 'go') {
49+
baton.resolve(undefined);
50+
return;
51+
}
52+
const as = parse(`${msg}`);
53+
deliverator(...as).then(() => send('go'));
54+
});
55+
56+
send('go');
57+
};
58+
59+
main().catch(e => {
60+
console.error(e);
61+
process.exitCode = 1;
62+
});

packages/solo/src/pipe.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
// @ts-check
2+
import { fork } from 'child_process';
3+
import path from 'path';
4+
5+
import { makePromiseKit } from '@agoric/promise-kit';
6+
import { parse, stringify } from '@endo/marshal';
7+
8+
import { makeShutdown } from '@agoric/cosmic-swingset/src/shutdown.js';
9+
10+
const filename = new URL(import.meta.url).pathname;
11+
const dirname = path.dirname(filename);
12+
13+
export const connectToPipe = async ({ method, args, deliverInboundToMbx }) => {
14+
// console.log('connectToPipe', method, args);
15+
16+
const cp = fork(path.join(dirname, 'pipe-entrypoint.js'), [method, ...args], {
17+
stdio: ['inherit', 'inherit', 'inherit', 'ipc'],
18+
});
19+
// console.log('connectToPipe', 'done fork');
20+
21+
let baton = makePromiseKit();
22+
cp.on('message', msg => {
23+
// console.log('connectToPipe', 'received', msg);
24+
if (msg === 'go') {
25+
baton.resolve(undefined);
26+
return;
27+
}
28+
// console.log('pipe.js', msg);
29+
const as = parse(`${msg}`);
30+
deliverInboundToMbx(...as).then(() => cp.send('go'));
31+
});
32+
33+
const { registerShutdown } = makeShutdown();
34+
registerShutdown(() => {
35+
// console.log('connectToPipe', 'shutdown');
36+
cp.kill('SIGINT');
37+
});
38+
39+
await baton.promise;
40+
return async (...as) => {
41+
// console.log('sending from pipe.js', as);
42+
baton = makePromiseKit();
43+
cp.send(stringify(harden(as)));
44+
return baton.promise;
45+
};
46+
};

packages/solo/src/start.js

+10-10
Original file line numberDiff line numberDiff line change
@@ -28,10 +28,11 @@ import {
2828
buildTimer,
2929
} from '@agoric/swingset-vat';
3030
import { openSwingStore } from '@agoric/swing-store';
31-
import { connectToFakeChain } from '@agoric/cosmic-swingset/src/sim-chain.js';
3231
import { makeWithQueue } from '@agoric/vats/src/queue.js';
32+
import { makeShutdown } from '@agoric/cosmic-swingset/src/shutdown.js';
3333

3434
import { deliver, addDeliveryTarget } from './outbound.js';
35+
import { connectToPipe } from './pipe.js';
3536
import { makeHTTPListener } from './web.js';
3637

3738
import { connectToChain } from './chain-cosmos-sdk.js';
@@ -307,6 +308,7 @@ const deployWallet = async ({ agWallet, deploys, hostport }) => {
307308

308309
// We turn off NODE_OPTIONS in case the user is debugging.
309310
const { NODE_OPTIONS: _ignore, ...noOptionsEnv } = process.env;
311+
let unregister;
310312
const cp = fork(
311313
agoricCli,
312314
[
@@ -321,12 +323,11 @@ const deployWallet = async ({ agWallet, deploys, hostport }) => {
321323
if (err) {
322324
console.error(err);
323325
}
324-
// eslint-disable-next-line no-use-before-define
325-
process.off('exit', killDeployment);
326+
unregister();
326327
},
327328
);
328-
const killDeployment = () => cp.kill('SIGINT');
329-
process.on('exit', killDeployment);
329+
const { registerShutdown } = makeShutdown();
330+
unregister = registerShutdown(() => cp.kill('SIGINT'));
330331
};
331332

332333
const start = async (basedir, argv) => {
@@ -421,12 +422,11 @@ const start = async (basedir, argv) => {
421422
break;
422423
case 'fake-chain': {
423424
log(`adding follower/sender for fake chain ${c.GCI}`);
424-
const deliverator = await connectToFakeChain(
425-
basedir,
426-
c.GCI,
427-
c.fakeDelay,
425+
const deliverator = await connectToPipe({
426+
method: 'connectToFakeChain',
427+
args: [basedir, c.GCI, c.fakeDelay],
428428
deliverInboundToMbx,
429-
);
429+
});
430430
addDeliveryTarget(c.GCI, deliverator);
431431
break;
432432
}

0 commit comments

Comments
 (0)