Skip to content

Commit b25881b

Browse files
committed
Watch instance healthcheck and terminate if not responding
1 parent d864c0d commit b25881b

3 files changed

Lines changed: 77 additions & 6 deletions

File tree

cli.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@ import {Command} from 'commander';
33

44
import {getPackageJson} from './src/utils.js';
55

6-
import {loginCommand} from './src/circuitscan.js';
6+
import {loginCommand, stopInstanceCommand} from './src/circuitscan.js';
77
import circomCommands from './src/circom/index.js';
88
import circomMultiCommands from './src/circomMulti/index.js';
99
import noirCommands from './src/noir/index.js';
@@ -16,6 +16,7 @@ program
1616
.version(getPackageJson().version);
1717

1818
loginCommand(program);
19+
stopInstanceCommand(program);
1920
// Each pipeline adds its commands
2021
circomCommands(program);
2122
circomMultiCommands(program);

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "circuitscan",
3-
"version": "0.1.2",
3+
"version": "0.1.3",
44
"main": "cli.js",
55
"type": "module",
66
"author": "numtel <ben@latenightsketches.com>",

src/circuitscan.js

Lines changed: 74 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,6 @@ import {
66
generateRandomString,
77
delay,
88
instanceSizes,
9-
loadConfig,
109
} from './utils.js';
1110
import {StatusLogger} from './StatusLogger.js';
1211

@@ -54,8 +53,9 @@ export async function invokeRemoteMachine(payload, options) {
5453
// status report during compilation
5554
const status = new StatusLogger(`${options.config.blobUrl}status/${requestId}.json`, 3000, Number(options.instance || 10));
5655

56+
const apiKey = activeApiKey(options);
5757
const event = {
58-
apiKey: activeApiKey(options),
58+
apiKey,
5959
payload: {
6060
...payload,
6161
requestId,
@@ -84,7 +84,7 @@ export async function invokeRemoteMachine(payload, options) {
8484
if(data.status === 'ok' && instanceType) {
8585
console.log('# Instance started. Wait a few minutes for initialization...');
8686
// Other statuses will arrive from the StatusLogger
87-
const {stderr, stdout} = await watchInstance(options.config.blobUrl, requestId, 8000);
87+
const {stderr, stdout} = await watchInstance(options.config.blobUrl, requestId, apiKey, options, 8000);
8888
console.error(stderr);
8989
console.log(stdout);
9090
const response = await fetch(`${options.config.blobUrl}instance-response/${requestId}.json`);
@@ -143,6 +143,17 @@ export function loginCommand(program) {
143143
});
144144
}
145145

146+
export function stopInstanceCommand(program) {
147+
program
148+
.command('terminate <requestId>')
149+
.description('Terminate a running compilation job')
150+
.action(async (requestId) => {
151+
const apiKey = loadUserConfig().apiKey;
152+
const body = terminateInstance(requestId, apiKey);
153+
console.log(body);
154+
});
155+
}
156+
146157
function loadUserConfig() {
147158
try {
148159
return JSON.parse(readFileSync(join(homedir(), '.circuitscan'), 'utf8'));
@@ -162,24 +173,82 @@ function activeApiKey(options) {
162173
return config.apiKey;
163174
}
164175

165-
function watchInstance(blobUrl, requestId, timeout) {
176+
function watchInstance(blobUrl, requestId, apiKey, options, timeout) {
166177
return new Promise((resolve, reject) => {
178+
let ip, healthcheckInterval;
167179
const interval = setInterval(async () => {
168180
try {
181+
if(!ip) {
182+
ip = (await fetchResult(blobUrl, requestId, 'ip')).trim();
183+
console.log(`# Instance running at ${ip}...`);
184+
healthcheckInterval = setInterval(async () => {
185+
try {
186+
await healthcheckFetch(ip, timeout - 1000);
187+
} catch(error) {
188+
if(error instanceof TimeoutError) {
189+
// Instance is not responding
190+
console.log(
191+
'\n\n# Instance health check failed, terminating!\n' +
192+
'# Possible out-of-memory exception. Try a larger instance size.\n\n'
193+
);
194+
195+
try {
196+
const response = await terminateInstance(requestId, apiKey, options);
197+
if(response.status !== 'ok') {
198+
console.error(response);
199+
}
200+
} catch(error) {
201+
// Not a big deal, just display it
202+
console.error(error);
203+
}
204+
}
205+
clearInterval(interval);
206+
clearInterval(healthcheckInterval);
207+
reject(error);
208+
}
209+
}, timeout);
210+
}
169211
const stderr = await fetchResult(blobUrl, requestId, 'stderr');
170212
const stdout = await fetchResult(blobUrl, requestId, 'stderr');
171213
clearInterval(interval);
214+
clearInterval(healthcheckInterval);
172215
resolve({ stderr, stdout });
173216
} catch(error) {
174217
if(!(error instanceof NotFoundError)) {
175218
clearInterval(interval);
219+
clearInterval(healthcheckInterval);
176220
reject(error);
177221
}
178222
}
179223
}, timeout);
180224
});
181225
}
182226

227+
function healthcheckFetch(ip, timeout) {
228+
const timeoutPromise = new Promise((_, reject) =>
229+
setTimeout(() => reject(new TimeoutError()), timeout)
230+
);
231+
const fetchPromise = fetch(`http://${ip}:3000`);
232+
return Promise.race([fetchPromise, timeoutPromise]);
233+
}
234+
235+
async function terminateInstance(requestId, apiKey, options) {
236+
const event = {payload: {requestId}, apiKey};
237+
const response = await fetch(process.env.LOCAL_TERMINATOR || (options && options.config && options.config.terminatorURL), {
238+
method: 'POST',
239+
headers: {
240+
'Content-Type': 'application/json',
241+
},
242+
body: JSON.stringify(event),
243+
});
244+
if (!response.ok && response.status !== 400) {
245+
throw new Error('Network response was not ok');
246+
}
247+
const data = await response.json();
248+
const body = 'body' in data ? JSON.parse(data.body) : data;
249+
return body;
250+
}
251+
183252
async function fetchResult(blobUrl, requestId, pipename) {
184253
const response = await fetch(`${blobUrl}instance/${requestId}/${pipename}.txt`);
185254
if (!response.ok) {
@@ -195,4 +264,5 @@ async function fetchResult(blobUrl, requestId, pipename) {
195264
}
196265

197266
class NotFoundError extends Error {}
267+
class TimeoutError extends Error {}
198268

0 commit comments

Comments
 (0)