37
37
import java .io .File ;
38
38
import java .io .IOException ;
39
39
import java .io .OutputStream ;
40
+ import java .lang .reflect .Field ;
40
41
import java .net .MalformedURLException ;
41
42
import java .net .URL ;
43
+ import java .time .Duration ;
42
44
import java .util .List ;
43
45
import java .util .concurrent .TimeUnit ;
44
46
import java .util .concurrent .locks .ReentrantLock ;
@@ -56,6 +58,8 @@ public final class AppiumDriverLocalService extends DriverService {
56
58
private static final Pattern LOG_MESSAGE_PATTERN = Pattern .compile ("^(.*)\\ R" );
57
59
private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern .compile ("^(\\ [debug\\ ] )?\\ [(.+?)\\ ]" );
58
60
private static final String APPIUM_SERVICE_SLF4J_LOGGER_PREFIX = "appium.service" ;
61
+ private static final Duration DESTROY_TIMEOUT = Duration .ofSeconds (60 );
62
+
59
63
private final File nodeJSExec ;
60
64
private final ImmutableList <String > nodeJSArgs ;
61
65
private final ImmutableMap <String , String > nodeJSEnvironment ;
@@ -64,7 +68,7 @@ public final class AppiumDriverLocalService extends DriverService {
64
68
private final ReentrantLock lock = new ReentrantLock (true ); //uses "fair" thread ordering policy
65
69
private final ListOutputStream stream = new ListOutputStream ().add (System .out );
66
70
private final URL url ;
67
-
71
+
68
72
private CommandLine process = null ;
69
73
70
74
AppiumDriverLocalService (String ipAddress , File nodeJSExec , int nodeJSPort ,
@@ -187,10 +191,47 @@ public void stop() {
187
191
}
188
192
}
189
193
190
- private void destroyProcess () {
191
- if (process .isRunning ()) {
192
- process .destroy ();
194
+ /**
195
+ * Destroys the service if it is running.
196
+ *
197
+ * @param timeout The maximum time to wait before the process will be force-killed.
198
+ * @return The exit code of the process or zero if the process was not running.
199
+ */
200
+ private int destroyProcess (Duration timeout ) {
201
+ if (!process .isRunning ()) {
202
+ return 0 ;
193
203
}
204
+
205
+ // This all magic is necessary, because Selenium does not publicly expose
206
+ // process killing timeouts. By default a process is killed forcibly if
207
+ // it does not exit after two seconds, which is in most cases not enough for
208
+ // Appium
209
+ try {
210
+ Field processField = process .getClass ().getDeclaredField ("process" );
211
+ processField .setAccessible (true );
212
+ Object osProcess = processField .get (process );
213
+ Field watchdogField = osProcess .getClass ().getDeclaredField ("executeWatchdog" );
214
+ watchdogField .setAccessible (true );
215
+ Object watchdog = watchdogField .get (osProcess );
216
+ Field nativeProcessField = watchdog .getClass ().getDeclaredField ("process" );
217
+ nativeProcessField .setAccessible (true );
218
+ Process nativeProcess = (Process ) nativeProcessField .get (watchdog );
219
+ nativeProcess .destroy ();
220
+ nativeProcess .waitFor (timeout .toMillis (), TimeUnit .MILLISECONDS );
221
+ } catch (Exception e ) {
222
+ LOG .warn ("No explicit timeout could be applied to the process termination" , e );
223
+ }
224
+
225
+ return process .destroy ();
226
+ }
227
+
228
+ /**
229
+ * Destroys the service.
230
+ * This methods waits up to `DESTROY_TIMEOUT` seconds for the Appium service
231
+ * to exit gracefully.
232
+ */
233
+ private void destroyProcess () {
234
+ destroyProcess (DESTROY_TIMEOUT );
194
235
}
195
236
196
237
/**
0 commit comments