32
32
import java .io .StringReader ;
33
33
import java .util .ArrayList ;
34
34
import java .util .List ;
35
+ import java .util .concurrent .ScheduledFuture ;
36
+ import java .util .concurrent .ScheduledThreadPoolExecutor ;
37
+ import java .util .concurrent .TimeUnit ;
35
38
import java .util .logging .Level ;
36
39
import java .util .logging .Logger ;
37
40
import org .opensolaris .opengrok .configuration .RuntimeEnvironment ;
@@ -48,6 +51,16 @@ public class Ctags {
48
51
private static final Logger LOGGER = LoggerFactory .getLogger (Ctags .class );
49
52
private static final long SIGNALWAIT_MS = 5000 ;
50
53
54
+ /**
55
+ * Wait up to 60 seconds for ctags to run, which is a very long time for a
56
+ * single run but is really intended for detecting blocked ctags processes
57
+ * as happened with @tuxillo's testing of
58
+ * <a href="https://github.com/oracle/opengrok/pull/1936">
59
+ * Feature/deferred_ops</a>.
60
+ */
61
+ private static final long CTAGSWAIT_S = 60 ;
62
+
63
+ private final ScheduledThreadPoolExecutor schedExecutor ;
51
64
private final CtagsBuffer buffer = new CtagsBuffer ();
52
65
private final Object syncRoot = new Object ();
53
66
private volatile boolean closing ;
@@ -65,6 +78,19 @@ public class Ctags {
65
78
66
79
private boolean junit_testing = false ;
67
80
81
+ /**
82
+ * Initializes an instance using the specified, required executor which
83
+ * will be used to track ctags timeouts to avoid blockages.
84
+ * <p>(The {@code schedExecutor} is not owned by {@link Ctags}.)
85
+ * @param schedExecutor required, defined instance
86
+ */
87
+ public Ctags (ScheduledThreadPoolExecutor schedExecutor ) {
88
+ if (schedExecutor == null ) {
89
+ throw new IllegalArgumentException ("`schedExecutor' is null" );
90
+ }
91
+ this .schedExecutor = schedExecutor ;
92
+ }
93
+
68
94
/**
69
95
* Gets a value indicating if a subprocess of ctags was started and either:
70
96
* 1) it is not alive; or 2) any necessary supporting thread is not alive.
@@ -311,56 +337,33 @@ private void initialize() throws IOException {
311
337
312
338
@ Override
313
339
public void run () {
314
- StringBuilder sb = new StringBuilder ();
315
340
try (BufferedReader error = new BufferedReader (
316
341
new InputStreamReader (ctags .getErrorStream ()))) {
317
342
String s ;
318
343
while ((s = error .readLine ()) != null ) {
319
- sb .append (s );
320
- sb .append ('\n' );
344
+ if (s .length () > 0 ) {
345
+ LOGGER .log (Level .WARNING , "Error from ctags: {0}" ,
346
+ s );
347
+ }
321
348
if (closing ) break ;
322
349
}
323
350
} catch (IOException exp ) {
324
351
LOGGER .log (Level .WARNING , "Got an exception reading ctags error stream: " , exp );
325
352
}
326
- if (sb .length () > 0 ) {
327
- LOGGER .log (Level .WARNING , "Error from ctags: {0}" , sb .toString ());
328
- }
329
353
}
330
354
});
331
355
errThread .setDaemon (true );
332
356
errThread .start ();
333
357
334
- outThread = new Thread (() -> {
335
- while (!closing ) {
336
- synchronized (syncRoot ) {
337
- while (!signalled && !closing ) {
338
- try {
339
- syncRoot .wait (SIGNALWAIT_MS );
340
- } catch (InterruptedException e ) {
341
- LOGGER .log (Level .WARNING ,
342
- "Ctags direct client unexpectedly interrupted" );
343
- }
344
- }
345
- signalled = false ;
346
- }
347
- if (closing ) return ;
348
-
349
- CtagsReader rdr = new CtagsReader ();
350
- readTags (rdr );
351
- Definitions defs = rdr .getDefinitions ();
352
- try {
353
- buffer .put (defs );
354
- } catch (InterruptedException ex ) {
355
- // ignore
356
- }
357
- }
358
- });
359
- outThread .setDaemon (true );
360
- outThread .start ();
358
+ if (outThread == null || !outThread .isAlive ()) {
359
+ outThread = new Thread (() -> outThreadStart ());
360
+ outThread .setDaemon (true );
361
+ outThread .start ();
362
+ }
361
363
}
362
364
363
- public Definitions doCtags (String file ) throws IOException {
365
+ public Definitions doCtags (String file ) throws IOException ,
366
+ InterruptedException {
364
367
boolean ctagsRunning = false ;
365
368
if (ctags != null ) {
366
369
try {
@@ -380,23 +383,36 @@ public Definitions doCtags(String file) throws IOException {
380
383
initialize ();
381
384
}
382
385
383
- Definitions ret = null ;
384
- if ( file . length () > 0 && ! " \n " . equals ( file )) {
385
- synchronized (syncRoot ) {
386
- signalled = true ;
387
- syncRoot .notify ();
388
- }
386
+ if ( file . length () < 1 || " \n " . equals ( file )) return null ;
387
+
388
+ synchronized (syncRoot ) {
389
+ signalled = true ;
390
+ syncRoot .notify ();
391
+ }
389
392
393
+ Thread clientThread = Thread .currentThread ();
394
+ ScheduledFuture futureTimeout = schedExecutor .schedule (() -> {
395
+ clientThread .interrupt ();
396
+ outThread .interrupt ();
397
+ LOGGER .log (Level .WARNING , "Ctags futureTimeout executed." );
398
+ }, CTAGSWAIT_S , TimeUnit .SECONDS );
399
+
400
+ Definitions ret ;
401
+ try {
390
402
ctagsIn .write (file );
403
+ if (Thread .interrupted ()) throw new InterruptedException ("write()" );
391
404
ctagsIn .flush ();
392
- try {
393
- ret = buffer .take ();
394
- } catch (InterruptedException e ) {
395
- LOGGER .log (Level .WARNING ,
396
- "Ctags indirect client unexpectedly interrupted" );
397
- }
405
+ if (Thread .interrupted ()) throw new InterruptedException ("flush()" );
406
+ ret = buffer .take ();
407
+ } finally {
408
+ futureTimeout .cancel (false );
398
409
}
399
410
411
+ /*
412
+ * If the timeout could not be canceled in time, then act as if no
413
+ * results were obtained.
414
+ */
415
+ if (Thread .interrupted ()) throw new InterruptedException ("late" );
400
416
return ret ;
401
417
}
402
418
@@ -442,15 +458,23 @@ public void destroy() {
442
458
};
443
459
444
460
CtagsReader rdr = new CtagsReader ();
445
- readTags (rdr );
461
+ try {
462
+ readTags (rdr );
463
+ } catch (InterruptedException ex ) {
464
+ LOGGER .log (Level .SEVERE , "readTags() test" , ex );
465
+ }
446
466
Definitions ret = rdr .getDefinitions ();
447
467
return ret ;
448
468
}
449
469
450
- private void readTags (CtagsReader reader ) {
470
+ private void readTags (CtagsReader reader ) throws InterruptedException {
451
471
try {
452
472
do {
453
473
String tagLine = ctagsOut .readLine ();
474
+ if (Thread .interrupted ()) {
475
+ throw new InterruptedException ("readLine()" );
476
+ }
477
+
454
478
//log.fine("Tagline:-->" + tagLine+"<----ONELINE");
455
479
if (tagLine == null ) {
456
480
if (!junit_testing ) {
@@ -480,9 +504,52 @@ private void readTags(CtagsReader reader) {
480
504
481
505
reader .readLine (tagLine );
482
506
} while (true );
507
+ } catch (InterruptedException e ) {
508
+ throw e ;
483
509
} catch (Exception e ) {
484
510
LOGGER .log (Level .WARNING , "CTags parsing problem: " , e );
485
511
}
486
512
LOGGER .severe ("CTag reader cycle was interrupted!" );
487
513
}
514
+
515
+ private void outThreadStart () {
516
+ while (!closing ) {
517
+ synchronized (syncRoot ) {
518
+ while (!signalled && !closing ) {
519
+ try {
520
+ syncRoot .wait (SIGNALWAIT_MS );
521
+ } catch (InterruptedException e ) {
522
+ LOGGER .log (Level .WARNING ,
523
+ "Ctags outThread unexpectedly interrupted--{0}" ,
524
+ e .getMessage ());
525
+ /*
526
+ * Terminating this thread will cause this Ctags
527
+ * instance, which appears to be blocked, to be
528
+ * reported as closed by isClosed().
529
+ */
530
+ return ;
531
+ }
532
+ }
533
+ signalled = false ;
534
+ }
535
+ if (closing ) return ;
536
+
537
+ CtagsReader rdr = new CtagsReader ();
538
+ try {
539
+ readTags (rdr );
540
+ Definitions defs = rdr .getDefinitions ();
541
+ buffer .put (defs );
542
+ } catch (InterruptedException ex ) {
543
+ LOGGER .log (Level .WARNING ,
544
+ "Ctags outThread unexpectedly interrupted--{0}" ,
545
+ ex .getMessage ());
546
+ /*
547
+ * Terminating this thread will cause this Ctags instance,
548
+ * which appears to be blocked, to be reported as closed by
549
+ * isClosed().
550
+ */
551
+ return ;
552
+ }
553
+ }
554
+ }
488
555
}
0 commit comments