@@ -86,6 +86,12 @@ public class TypeScriptParser {
86
86
*/
87
87
public static final String TYPESCRIPT_TIMEOUT_VAR = "SEMMLE_TYPESCRIPT_TIMEOUT" ;
88
88
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
+
89
95
/**
90
96
* An environment variable (without the <tt>SEMMLE_</tt> or <tt>LGTM_</tt> prefix), that can be
91
97
* 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) {
179
185
public String verifyNodeInstallation () {
180
186
if (nodeJsVersionString != null ) return nodeJsVersionString ;
181
187
182
- ByteArrayOutputStream out = new ByteArrayOutputStream ();
183
- ByteArrayOutputStream err = new ByteArrayOutputStream ();
184
-
185
188
// Determine where to find the Node.js runtime.
186
189
String explicitNodeJsRuntime = Env .systemEnv ().get (TYPESCRIPT_NODE_RUNTIME_VAR );
187
190
if (explicitNodeJsRuntime != null ) {
@@ -198,12 +201,41 @@ public String verifyNodeInstallation() {
198
201
nodeJsRuntimeExtraArgs = Arrays .asList (extraArgs .split ("\\ s+" ));
199
202
}
200
203
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 ();
201
234
Builder b =
202
235
new Builder (
203
236
getNodeJsRuntimeInvocation ("--version" ), out , err , getParserWrapper ().getParentFile ());
204
237
b .expectFailure (); // We want to do our own logging in case of an error.
205
238
206
- int timeout = Env .systemEnv ().getInt (TYPESCRIPT_TIMEOUT_VAR , 10000 );
207
239
try {
208
240
int r = b .execute (timeout );
209
241
String stdout = new String (out .toByteArray ());
@@ -213,10 +245,6 @@ public String verifyNodeInstallation() {
213
245
"Could not start Node.js. It is required for TypeScript extraction.\n " + stderr );
214
246
}
215
247
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)." );
220
248
} catch (ResourceError e ) {
221
249
// In case 'node' is not found, the process builder converts the IOException
222
250
// into a ResourceError.
0 commit comments