@@ -86,6 +86,12 @@ public class TypeScriptParser {
8686 */
8787 public static final String TYPESCRIPT_TIMEOUT_VAR = "SEMMLE_TYPESCRIPT_TIMEOUT" ;
8888
89+ /**
90+ * An environment variable that can be set to specify a number of retries when verifying
91+ * the TypeScript installation. Default is 3.
92+ */
93+ public static final String TYPESCRIPT_RETRIES_VAR = "SEMMLE_TYPESCRIPT_RETRIES" ;
94+
8995 /**
9096 * An environment variable (without the <tt>SEMMLE_</tt> or <tt>LGTM_</tt> prefix), that can be
9197 * set to indicate the maximum heap space usable by the Node.js process, in addition to its
@@ -179,9 +185,6 @@ public void verifyInstallation(boolean verbose) {
179185 public String verifyNodeInstallation () {
180186 if (nodeJsVersionString != null ) return nodeJsVersionString ;
181187
182- ByteArrayOutputStream out = new ByteArrayOutputStream ();
183- ByteArrayOutputStream err = new ByteArrayOutputStream ();
184-
185188 // Determine where to find the Node.js runtime.
186189 String explicitNodeJsRuntime = Env .systemEnv ().get (TYPESCRIPT_NODE_RUNTIME_VAR );
187190 if (explicitNodeJsRuntime != null ) {
@@ -198,12 +201,41 @@ public String verifyNodeInstallation() {
198201 nodeJsRuntimeExtraArgs = Arrays .asList (extraArgs .split ("\\ s+" ));
199202 }
200203
204+ // Run 'node --version' with a timeout, and retry a few times if it times out.
205+ // If the Java process is suspended we may get a spurious timeout, and we want to
206+ // support long suspensions in cloud environments. Instead of setting a huge timeout,
207+ // retrying guarantees we can survive arbitrary suspensions as long as they don't happen
208+ // too many times in rapid succession.
209+ int timeout = Env .systemEnv ().getInt (TYPESCRIPT_TIMEOUT_VAR , 10000 );
210+ int numRetries = Env .systemEnv ().getInt (TYPESCRIPT_RETRIES_VAR , 3 );
211+ for (int i = 0 ; i < numRetries - 1 ; ++i ) {
212+ try {
213+ return startNodeAndGetVersion (timeout );
214+ } catch (InterruptedError e ) {
215+ Exceptions .ignore (e , "We will retry the call that caused this exception." );
216+ System .err .println ("Starting Node.js seems to take a long time. Retrying." );
217+ }
218+ }
219+ try {
220+ return startNodeAndGetVersion (timeout );
221+ } catch (InterruptedError e ) {
222+ Exceptions .ignore (e , "Exception details are not important." );
223+ throw new CatastrophicError (
224+ "Could not start Node.js (timed out after " + (timeout / 1000 ) + "s and " + numRetries + " attempts" );
225+ }
226+ }
227+
228+ /**
229+ * Checks that Node.js is installed and can be run and returns its version string.
230+ */
231+ private String startNodeAndGetVersion (int timeout ) throws InterruptedError {
232+ ByteArrayOutputStream out = new ByteArrayOutputStream ();
233+ ByteArrayOutputStream err = new ByteArrayOutputStream ();
201234 Builder b =
202235 new Builder (
203236 getNodeJsRuntimeInvocation ("--version" ), out , err , getParserWrapper ().getParentFile ());
204237 b .expectFailure (); // We want to do our own logging in case of an error.
205238
206- int timeout = Env .systemEnv ().getInt (TYPESCRIPT_TIMEOUT_VAR , 10000 );
207239 try {
208240 int r = b .execute (timeout );
209241 String stdout = new String (out .toByteArray ());
@@ -213,10 +245,6 @@ public String verifyNodeInstallation() {
213245 "Could not start Node.js. It is required for TypeScript extraction.\n " + stderr );
214246 }
215247 return nodeJsVersionString = stdout ;
216- } catch (InterruptedError e ) {
217- Exceptions .ignore (e , "Exception details are not important." );
218- throw new CatastrophicError (
219- "Could not start Node.js (timed out after " + (timeout / 1000 ) + "s)." );
220248 } catch (ResourceError e ) {
221249 // In case 'node' is not found, the process builder converts the IOException
222250 // into a ResourceError.
0 commit comments