66 generateRandomString ,
77 delay ,
88 instanceSizes ,
9- loadConfig ,
109} from './utils.js' ;
1110import { 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+
146157function 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+
183252async 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
197266class NotFoundError extends Error { }
267+ class TimeoutError extends Error { }
198268
0 commit comments