1
1
import * as _ from 'lodash' ;
2
+ import * as path from 'path' ;
2
3
3
4
import { Interceptor } from '.' ;
4
5
5
6
import { HtkConfig } from '../config' ;
6
7
import { spawnToResult , waitForExit } from '../process-management' ;
7
8
import { OVERRIDE_JAVA_AGENT } from './terminal/terminal-env-overrides' ;
8
9
import { reportError } from '../error-tracking' ;
9
- import { commandExists } from '../util' ;
10
+ import { commandExists , delay } from '../util' ;
10
11
11
12
type JvmTarget = { pid : string , name : string , interceptedByProxy : number | undefined } ;
12
13
13
14
// 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 ;
31
51
} else {
32
- return true ;
52
+ // No Java available anywhere - we just give up
53
+ return false ;
33
54
}
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 ) => {
37
56
reportError ( e ) ;
38
57
return false ;
39
58
} ) ;
40
59
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
+
41
76
export class JvmInterceptor implements Interceptor {
42
77
readonly id = 'attach-jvm' ;
43
78
readonly version = '1.0.1' ;
@@ -49,7 +84,7 @@ export class JvmInterceptor implements Interceptor {
49
84
constructor ( private config : HtkConfig ) { }
50
85
51
86
async isActivable ( ) : Promise < boolean > {
52
- return await isJavaAvailable ;
87
+ return ! ! await javaBinPromise ;
53
88
}
54
89
55
90
isActive ( proxyPort : number | string ) {
@@ -79,8 +114,11 @@ export class JvmInterceptor implements Interceptor {
79
114
private targetsPromise : Promise < JvmTarget [ ] > | undefined ;
80
115
81
116
private async getTargets ( ) : Promise < JvmTarget [ ] > {
117
+ const javaBin = await javaBinPromise ;
118
+ if ( ! javaBin ) throw new Error ( "Attach activated but no Java available" ) ;
119
+
82
120
const listTargetsOutput = await spawnToResult (
83
- 'java' , [
121
+ javaBin , [
84
122
'-jar' , OVERRIDE_JAVA_AGENT ,
85
123
'list-targets'
86
124
]
0 commit comments