Skip to content

Commit d1d0f78

Browse files
fix: Increase the timeout for graceful service termination (#1354)
1 parent cf33d48 commit d1d0f78

File tree

1 file changed

+45
-4
lines changed

1 file changed

+45
-4
lines changed

src/main/java/io/appium/java_client/service/local/AppiumDriverLocalService.java

Lines changed: 45 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@
3737
import java.io.File;
3838
import java.io.IOException;
3939
import java.io.OutputStream;
40+
import java.lang.reflect.Field;
4041
import java.net.MalformedURLException;
4142
import java.net.URL;
43+
import java.time.Duration;
4244
import java.util.List;
4345
import java.util.concurrent.TimeUnit;
4446
import java.util.concurrent.locks.ReentrantLock;
@@ -56,6 +58,8 @@ public final class AppiumDriverLocalService extends DriverService {
5658
private static final Pattern LOG_MESSAGE_PATTERN = Pattern.compile("^(.*)\\R");
5759
private static final Pattern LOGGER_CONTEXT_PATTERN = Pattern.compile("^(\\[debug\\] )?\\[(.+?)\\]");
5860
private static final String APPIUM_SERVICE_SLF4J_LOGGER_PREFIX = "appium.service";
61+
private static final Duration DESTROY_TIMEOUT = Duration.ofSeconds(60);
62+
5963
private final File nodeJSExec;
6064
private final ImmutableList<String> nodeJSArgs;
6165
private final ImmutableMap<String, String> nodeJSEnvironment;
@@ -64,7 +68,7 @@ public final class AppiumDriverLocalService extends DriverService {
6468
private final ReentrantLock lock = new ReentrantLock(true); //uses "fair" thread ordering policy
6569
private final ListOutputStream stream = new ListOutputStream().add(System.out);
6670
private final URL url;
67-
71+
6872
private CommandLine process = null;
6973

7074
AppiumDriverLocalService(String ipAddress, File nodeJSExec, int nodeJSPort,
@@ -187,10 +191,47 @@ public void stop() {
187191
}
188192
}
189193

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;
193203
}
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);
194235
}
195236

196237
/**

0 commit comments

Comments
 (0)