37
37
import java .io .InputStreamReader ;
38
38
import java .io .PrintStream ;
39
39
import java .io .PrintWriter ;
40
+ import java .lang .reflect .Method ;
40
41
import java .net .InetSocketAddress ;
41
42
import java .net .ServerSocket ;
42
43
import java .net .Socket ;
@@ -90,12 +91,30 @@ public static class Fault extends Exception {
90
91
static final boolean showAgent = Flags .get ("showAgent" );
91
92
static final boolean traceAgent = Flags .get ("traceAgent" );
92
93
94
+ // the following code here allows us to run jtreg on older
95
+ // JDKs where the pid() method is unavailable on the
96
+ // Process class. we use PID only for debug purposes and
97
+ // the inability to get the PID of a launched AgentServer
98
+ // is OK.
99
+ private static final long UNKNOWN_PID = -1 ;
100
+ private static final Method PID_METHOD ;
101
+ static {
102
+ Method pidMethod = null ;
103
+ try {
104
+ pidMethod = Process .class .getDeclaredMethod ("pid" ); // only available in Java 9+
105
+ } catch (Exception e ) {
106
+ pidMethod = null ;
107
+ }
108
+ PID_METHOD = pidMethod ;
109
+ }
110
+
93
111
/**
94
112
* Start a JDK with given JVM options.
95
113
*/
96
114
private Agent (File dir , JDK jdk , List <String > vmOpts , Map <String , String > envVars ,
97
115
File policyFile , float timeoutFactor , Logger logger ,
98
116
String testThreadFactory , String testThreadFactoryPath ) throws Fault {
117
+ Process agentServerProcess = null ;
99
118
try {
100
119
id = ++count ;
101
120
this .jdk = jdk ;
@@ -127,8 +146,9 @@ private Agent(File dir, JDK jdk, List<String> vmOpts, Map<String, String> envVar
127
146
// is platform-specific, and Solaris has it on by default.
128
147
ss .setReuseAddress (false );
129
148
ss .bind (new InetSocketAddress (/*port:*/ 0 ), /*backlog:*/ 1 );
149
+ final int port = ss .getLocalPort ();
130
150
cmd .add (AgentServer .PORT );
131
- cmd .add (String .valueOf (ss . getLocalPort () ));
151
+ cmd .add (String .valueOf (port ));
132
152
133
153
if (timeoutFactor != 1.0f ) {
134
154
cmd .add (AgentServer .TIMEOUTFACTOR );
@@ -144,22 +164,27 @@ private Agent(File dir, JDK jdk, List<String> vmOpts, Map<String, String> envVar
144
164
cmd .add (CUSTOM_TEST_THREAD_FACTORY_PATH );
145
165
cmd .add (testThreadFactoryPath );
146
166
}
147
- log ("Started " + cmd );
167
+ log ("Launching " + cmd );
148
168
149
169
ProcessBuilder pb = new ProcessBuilder (cmd );
150
170
pb .directory (dir );
151
171
Map <String , String > env = pb .environment ();
152
172
env .clear ();
153
173
env .putAll (envVars );
154
- process = pb .start ();
174
+ agentServerProcess = process = pb .start ();
175
+ final long pid = getPid (process );
155
176
copyAgentProcessStream ("stdout" , process .getInputStream ());
156
177
copyAgentProcessStream ("stderr" , process .getErrorStream ());
157
178
158
179
try {
159
180
final int ACCEPT_TIMEOUT = (int ) (60 * 1000 * timeoutFactor );
160
- // default 60 seconds, for server to start and "phone home"
181
+ // default 60 seconds, for server to start and "phone home"
161
182
ss .setSoTimeout (ACCEPT_TIMEOUT );
183
+ log ("Waiting up to " + ACCEPT_TIMEOUT + " milli seconds for a" +
184
+ " socket connection on port " + port +
185
+ (pid != UNKNOWN_PID ? " from process " + pid : "" ));
162
186
Socket s = ss .accept ();
187
+ log ("Received connection on port " + port + " from " + s );
163
188
s .setSoTimeout ((int )(KeepAlive .READ_TIMEOUT * timeoutFactor ));
164
189
in = new DataInputStream (s .getInputStream ());
165
190
out = new DataOutputStream (s .getOutputStream ());
@@ -171,6 +196,16 @@ private Agent(File dir, JDK jdk, List<String> vmOpts, Map<String, String> envVar
171
196
// send keep-alive messages to server while not executing actions
172
197
keepAlive .setEnabled (true );
173
198
} catch (IOException e ) {
199
+ log ("Agent creation failed due to " + e );
200
+ if (agentServerProcess != null ) {
201
+ // kill the launched process
202
+ log ("killing AgentServer process" );
203
+ try {
204
+ ProcessUtils .destroyForcibly (agentServerProcess );
205
+ } catch (Exception ignored ) {
206
+ // ignore
207
+ }
208
+ }
174
209
throw new Fault (e );
175
210
}
176
211
}
@@ -578,6 +613,17 @@ private void log(String message, PrintStream out) {
578
613
out .println ("[" + AgentServer .logDateFormat .format (new Date ()) + "] Agent[" + getId () + "]: " + message );
579
614
}
580
615
616
+ private static long getPid (final Process process ) {
617
+ if (PID_METHOD == null ) {
618
+ return UNKNOWN_PID ;
619
+ }
620
+ try {
621
+ return (long ) PID_METHOD .invoke (process );
622
+ } catch (Exception e ) {
623
+ return UNKNOWN_PID ;
624
+ }
625
+ }
626
+
581
627
final JDK jdk ;
582
628
final List <String > vmOpts ;
583
629
final File execDir ;
@@ -730,6 +776,20 @@ public void setMaxPoolSize(int size) {
730
776
logger .log (null , "POOL: max pool size: " + maxPoolSize );
731
777
}
732
778
779
+ /**
780
+ * Sets the maximum attempts to create or obtain an agent VM
781
+ * @param numAttempts number of attempts
782
+ * @throws IllegalArgumentException if {@code numAttempts} is less than {@code 1}
783
+ */
784
+ public void setNumAgentSelectionAttempts (final int numAttempts ) {
785
+ if (numAttempts < 1 ) {
786
+ throw new IllegalArgumentException ("invalid value for agent selection attempts: "
787
+ + numAttempts );
788
+ }
789
+ this .numAgentSelectionAttempts = numAttempts ;
790
+ logger .log (null , "POOL: agent selection attempts: " + numAttempts );
791
+ }
792
+
733
793
/**
734
794
* Obtains an agent with the desired properties.
735
795
* If a suitable agent already exists in the pool, it will be removed from the pool and
@@ -745,7 +805,48 @@ public void setMaxPoolSize(int size) {
745
805
* @return the agent
746
806
* @throws Fault if there is a problem obtaining a suitable agent
747
807
*/
748
- synchronized Agent getAgent (File dir ,
808
+ Agent getAgent (File dir ,
809
+ JDK jdk ,
810
+ List <String > vmOpts ,
811
+ Map <String , String > envVars ,
812
+ String testThreadFactory ,
813
+ String testThreadFactoryPath )
814
+ throws Fault {
815
+ final int numAttempts = this .numAgentSelectionAttempts ;
816
+ assert numAttempts > 0 : "unexpected agent selection attempts: " + numAttempts ;
817
+ Agent .Fault toThrow = null ;
818
+ for (int i = 1 ; i <= numAttempts ; i ++) {
819
+ try {
820
+ if (i != 1 ) {
821
+ logger .log (null , "POOL: re-attempting agent creation, attempt number " + i );
822
+ }
823
+ return doGetAgent (dir , jdk , vmOpts , envVars , testThreadFactory ,
824
+ testThreadFactoryPath );
825
+ } catch (Agent .Fault f ) {
826
+ logger .log (null , "POOL: agent creation failed due to " + f .getCause ());
827
+ // keep track of the fault and reattempt to get an agent if within limit
828
+ if (toThrow == null ) {
829
+ toThrow = f ;
830
+ } else {
831
+ // add the previous exception as a suppressed exception
832
+ // of the current one
833
+ if (toThrow .getCause () != null ) {
834
+ f .addSuppressed (toThrow .getCause ());
835
+ }
836
+ toThrow = f ;
837
+ }
838
+ if (i == numAttempts || !(f .getCause () instanceof IOException )) {
839
+ // we either made enough attempts or we failed due to a non IOException.
840
+ // In either case we don't attempt to create an agent again and instead
841
+ // throw the captured failure(s)
842
+ throw toThrow ;
843
+ }
844
+ }
845
+ }
846
+ throw new AssertionError ("should not reach here" );
847
+ }
848
+
849
+ synchronized Agent doGetAgent (File dir ,
749
850
JDK jdk ,
750
851
List <String > vmOpts ,
751
852
Map <String , String > envVars ,
@@ -927,6 +1028,7 @@ private static String getKey(File dir, JDK jdk, List<String> vmOpts) {
927
1028
private float timeoutFactor = 1.0f ;
928
1029
private int maxPoolSize ;
929
1030
private Duration idleTimeout ;
1031
+ private int numAgentSelectionAttempts ;
930
1032
}
931
1033
932
1034
static class Stats {
0 commit comments