@@ -52,20 +52,21 @@ public class Ctags {
52
52
private static final long SIGNALWAIT_MS = 5000 ;
53
53
54
54
/**
55
- * Wait up to 60 seconds for ctags to run, which is a very long time for a
55
+ * Wait up to 90 seconds for ctags to run, which is a very long time for a
56
56
* single run but is really intended for detecting blocked ctags processes
57
57
* as happened with @tuxillo's testing of
58
58
* <a href="https://github.com/oracle/opengrok/pull/1936">
59
59
* Feature/deferred_ops</a>.
60
60
*/
61
- private static final long CTAGSWAIT_S = 60 ;
61
+ private static final long CTAGSWAIT_S = 90 ;
62
62
63
63
private final ScheduledThreadPoolExecutor schedExecutor ;
64
64
private final CtagsBuffer buffer = new CtagsBuffer ();
65
65
private final Object syncRoot = new Object ();
66
66
private volatile boolean closing ;
67
67
private volatile boolean signalled ;
68
- private Process ctags ;
68
+ private volatile Process ctags ;
69
+ private volatile IOException startIOException ;
69
70
private Thread errThread ;
70
71
private Thread outThread ;
71
72
private OutputStreamWriter ctagsIn ;
@@ -119,13 +120,13 @@ public void close() throws IOException {
119
120
if (ctags != null ) {
120
121
closing = true ;
121
122
LOGGER .log (Level .FINE , "Destroying ctags command" );
122
- ctags .destroy ();
123
+ ctags .destroyForcibly ();
123
124
}
124
125
}
125
126
126
- private void initialize () throws IOException {
127
+ private void initialize () throws IOException , InterruptedException {
127
128
RuntimeEnvironment env = RuntimeEnvironment .getInstance ();
128
- if (processBuilder == null ) {
129
+ if (true ) {
129
130
List <String > command = new ArrayList <>();
130
131
131
132
command .add (binary );
@@ -329,7 +330,62 @@ private void initialize() throws IOException {
329
330
processBuilder = new ProcessBuilder (command );
330
331
}
331
332
332
- ctags = processBuilder .start ();
333
+ Thread clientThread = Thread .currentThread ();
334
+
335
+ startIOException = null ;
336
+ // Start ctags on a separate, daemon thread in case start() blocks.
337
+ Thread startThread = new Thread (() -> {
338
+ try {
339
+ Process newCtags = processBuilder .start ();
340
+ LOGGER .log (Level .FINE , "Executed ctags command re t{0}" ,
341
+ clientThread .getId ());
342
+ synchronized (syncRoot ) {
343
+ ctags = newCtags ;
344
+ syncRoot .notify ();
345
+ }
346
+ } catch (IOException e ) {
347
+ startIOException = e ;
348
+ }
349
+ });
350
+ startThread .setDaemon (true );
351
+ startThread .start ();
352
+
353
+ // Set a timeout so the client thread does not wait indefinitely.
354
+ ScheduledFuture startTimeout = schedExecutor .schedule (() -> {
355
+ LOGGER .log (Level .FINE , "Ctags startTimeout executing re t{0}." ,
356
+ clientThread .getId ());
357
+ clientThread .interrupt ();
358
+ /**
359
+ * No point in interrupting startThread, since nothing it executes
360
+ * is interruptible.
361
+ */
362
+ LOGGER .log (Level .FINE , "Ctags startTimeout executed." );
363
+ }, CTAGSWAIT_S , TimeUnit .SECONDS );
364
+
365
+ // Wait until startThread sets the ctags field, until timeout, or error.
366
+ synchronized (syncRoot ) {
367
+ while (ctags == null ) {
368
+ if (startIOException != null ) {
369
+ startTimeout .cancel (false );
370
+ throw startIOException ;
371
+ }
372
+ try {
373
+ syncRoot .wait (SIGNALWAIT_MS );
374
+ } catch (InterruptedException e ) {
375
+ LOGGER .log (Level .WARNING , "Ctags did not start--{0}" ,
376
+ e .getMessage ());
377
+ startTimeout .cancel (false );
378
+ throw e ;
379
+ }
380
+ }
381
+ }
382
+
383
+ startTimeout .cancel (false );
384
+ /*
385
+ * If the timeout could not be truly canceled in time, then ignore it.
386
+ */
387
+ Thread .interrupted ();
388
+
333
389
ctagsIn = new OutputStreamWriter (ctags .getOutputStream ());
334
390
ctagsOut = new BufferedReader (new InputStreamReader (ctags .getInputStream ()));
335
391
@@ -364,38 +420,37 @@ public void run() {
364
420
365
421
public Definitions doCtags (String file ) throws IOException ,
366
422
InterruptedException {
367
- boolean ctagsRunning = false ;
423
+ if (file .length () < 1 || "\n " .equals (file )) return null ;
424
+
368
425
if (ctags != null ) {
369
426
try {
370
427
int exitValue = ctags .exitValue ();
371
428
// If it is possible to retrieve exit value without exception
372
- // this means the ctags process is dead so we must restart it.
373
- ctagsRunning = false ;
429
+ // this means the ctags process is dead.
374
430
LOGGER .log (Level .WARNING , "Ctags process exited with exit value {0}" ,
375
431
exitValue );
432
+ // Throw the following to indicate non-I/O error for retry.
433
+ throw new InterruptedException ("ctags died" );
376
434
} catch (IllegalThreadStateException exp ) {
377
- ctagsRunning = true ;
378
435
// The ctags process is still running.
379
436
}
380
- }
381
-
382
- if (!ctagsRunning ) {
437
+ } else {
383
438
initialize ();
384
439
}
385
440
386
- if (file .length () < 1 || "\n " .equals (file )) return null ;
387
-
388
441
synchronized (syncRoot ) {
389
442
signalled = true ;
390
443
syncRoot .notify ();
391
444
}
392
445
393
446
Thread clientThread = Thread .currentThread ();
394
447
ScheduledFuture futureTimeout = schedExecutor .schedule (() -> {
395
- LOGGER .log (Level .WARNING , "Ctags futureTimeout executing." );
448
+ LOGGER .log (Level .FINE , "Ctags futureTimeout executing re t{0}." ,
449
+ clientThread .getId ());
396
450
clientThread .interrupt ();
397
451
outThread .interrupt ();
398
452
ctags .destroyForcibly ();
453
+ LOGGER .log (Level .FINE , "Ctags futureTimeout executed." );
399
454
}, CTAGSWAIT_S , TimeUnit .SECONDS );
400
455
401
456
Definitions ret ;
@@ -545,9 +600,6 @@ private void outThreadStart() {
545
600
CtagsReader rdr = new CtagsReader ();
546
601
try {
547
602
readTags (rdr );
548
- if (Thread .interrupted ()) {
549
- throw new InterruptedException ("readTags()" );
550
- }
551
603
Definitions defs = rdr .getDefinitions ();
552
604
buffer .put (defs );
553
605
} catch (InterruptedException ex ) {
0 commit comments