11import * as _ from 'lodash' ;
2+ import * as path from 'path' ;
23
34import { Interceptor } from '.' ;
45
56import { HtkConfig } from '../config' ;
67import { spawnToResult , waitForExit } from '../process-management' ;
78import { OVERRIDE_JAVA_AGENT } from './terminal/terminal-env-overrides' ;
89import { reportError } from '../error-tracking' ;
9- import { commandExists } from '../util' ;
10+ import { commandExists , delay } from '../util' ;
1011
1112type JvmTarget = { pid : string , name : string , interceptedByProxy : number | undefined } ;
1213
1314// Check that Java is present, and that it's compatible with agent attachment:
14- const isJavaAvailable = commandExists ( 'java' ) . then ( async ( isAvailable ) => {
15- if ( ! isAvailable ) return false ;
16-
17- const result = await spawnToResult (
18- 'java' , [
19- '-Djdk.attach.allowAttachSelf=true' , // Required for self-test
20- '-jar' , OVERRIDE_JAVA_AGENT ,
21- 'self-test'
22- ]
23- ) ;
24-
25- // If Java is present, but it's not working, we report it. Hoping that this will hunt
26- // down some specific incompatibilities that we can better work around/detect.
27- if ( result . exitCode !== 0 ) {
28- console . log ( result . stdout ) ;
29- console . log ( result . stderr ) ;
30- throw new Error ( `JVM attach not available, exited with ${ result . exitCode } ` ) ;
15+ const javaBinPromise : Promise < string | false > = ( async ( ) => {
16+ const javaBinPaths = [
17+ // Check what Java binaries might exist:
18+ ...( ! ! process . env . JAVA_HOME // $JAVA_HOME/bin/java is preferable
19+ ? [ path . join ( process . env . JAVA_HOME ! ! , 'bin' , 'java' ) ]
20+ : [ ]
21+ ) ,
22+ ...( await commandExists ( 'java' ) // but any other Java in $PATH will do
23+ ? [ 'java' ]
24+ : [ ]
25+ )
26+ ] ;
27+
28+ // Run a self test in parallel with each of them:
29+ const javaTestResults = await Promise . all ( javaBinPaths . map ( async ( possibleJavaBin ) => ( {
30+ javaBin : possibleJavaBin ,
31+ output : await testJavaBin ( possibleJavaBin )
32+ } ) ) )
33+
34+ // Use the first Java in the list that succeeds:
35+ const bestJava = javaTestResults . filter ( ( { output } ) =>
36+ output . exitCode === 0
37+ ) [ 0 ] ;
38+
39+ if ( javaTestResults . length && ! bestJava ) {
40+ // If some Java is present, but none are working, we report the failures. Hoping that this will hunt
41+ // down some specific incompatibilities that we can better work around/detect.
42+ javaTestResults . forEach ( ( testResult ) => {
43+ console . log ( `Running ${ testResult . javaBin } :` ) ;
44+ console . log ( testResult . output . stdout ) ;
45+ console . log ( testResult . output . stderr ) ;
46+ } ) ;
47+
48+ throw new Error ( `JVM attach not available, exited with ${ javaTestResults [ 0 ] . output . exitCode } ` ) ;
49+ } else if ( bestJava ) {
50+ return bestJava . javaBin ;
3151 } else {
32- return true ;
52+ // No Java available anywhere - we just give up
53+ return false ;
3354 }
34- } ) . catch ( ( e ) => {
35- // This is expected to happen occasionally, e.g. when using Java 8 (which
36- // doesn't support the VM attachment APIs we need).
55+ } ) ( ) . catch ( ( e ) => {
3756 reportError ( e ) ;
3857 return false ;
3958} ) ;
4059
60+ function testJavaBin ( possibleJavaBin : string ) {
61+ return Promise . race ( [
62+ spawnToResult (
63+ possibleJavaBin , [
64+ '-Djdk.attach.allowAttachSelf=true' , // Required for self-test
65+ '-jar' , OVERRIDE_JAVA_AGENT ,
66+ 'self-test'
67+ ]
68+ ) ,
69+ // Time out permanently after 30 seconds - this only runs once max anyway
70+ delay ( 30000 ) . then ( ( ) => {
71+ throw new Error ( `Java bin test for ${ possibleJavaBin } timed out` ) ;
72+ } )
73+ ] ) ;
74+ }
75+
4176export class JvmInterceptor implements Interceptor {
4277 readonly id = 'attach-jvm' ;
4378 readonly version = '1.0.1' ;
@@ -49,7 +84,7 @@ export class JvmInterceptor implements Interceptor {
4984 constructor ( private config : HtkConfig ) { }
5085
5186 async isActivable ( ) : Promise < boolean > {
52- return await isJavaAvailable ;
87+ return ! ! await javaBinPromise ;
5388 }
5489
5590 isActive ( proxyPort : number | string ) {
@@ -79,8 +114,11 @@ export class JvmInterceptor implements Interceptor {
79114 private targetsPromise : Promise < JvmTarget [ ] > | undefined ;
80115
81116 private async getTargets ( ) : Promise < JvmTarget [ ] > {
117+ const javaBin = await javaBinPromise ;
118+ if ( ! javaBin ) throw new Error ( "Attach activated but no Java available" ) ;
119+
82120 const listTargetsOutput = await spawnToResult (
83- 'java' , [
121+ javaBin , [
84122 '-jar' , OVERRIDE_JAVA_AGENT ,
85123 'list-targets'
86124 ]
0 commit comments