Skip to content

Commit d6270ae

Browse files
Merge pull request #291 from TikhomirovSergey/#283-fix
#283 fix: Improvement of AppiumDriverLocalService for the multithreading.
2 parents 34aa329 + 872a303 commit d6270ae

File tree

3 files changed

+317
-33
lines changed

3 files changed

+317
-33
lines changed

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

Lines changed: 46 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ public final class AppiumDriverLocalService extends DriverService {
4444
private final String ipAddress;
4545
private final long startupTimeout;
4646
private final TimeUnit timeUnit;
47-
private final ReentrantLock lock = new ReentrantLock();
47+
private final ReentrantLock lock = new ReentrantLock(true); //uses "fair" thread ordering policy
4848
private final ListOutputStream stream = new ListOutputStream().add(System.out);
4949

5050

@@ -82,19 +82,21 @@ public URL getUrl() {
8282
@Override
8383
public boolean isRunning() {
8484
lock.lock();
85-
if (process == null) {
86-
return false;
87-
}
85+
try {
86+
if (process == null) {
87+
return false;
88+
}
8889

89-
if (!process.isRunning()) {
90-
return false;
91-
}
90+
if (!process.isRunning()) {
91+
return false;
92+
}
9293

93-
try {
94-
ping(500, TimeUnit.MILLISECONDS);
95-
return true;
96-
} catch (UrlChecker.TimeoutException e) {
97-
return false;
94+
try {
95+
ping(500, TimeUnit.MILLISECONDS);
96+
return true;
97+
} catch (UrlChecker.TimeoutException e) {
98+
return false;
99+
}
98100
} finally {
99101
lock.unlock();
100102
}
@@ -118,27 +120,30 @@ private void ping(long time, TimeUnit timeUnit) throws UrlChecker.TimeoutExcepti
118120
*/
119121
public void start() throws AppiumServerHasNotBeenStartedLocallyException {
120122
lock.lock();
121-
if (isRunning())
122-
return;
123-
124123
try {
125-
process = new CommandLine(this.nodeJSExec.getCanonicalPath(), nodeJSArgs.toArray(new String[] {}));
126-
process.setEnvironmentVariables(nodeJSEnvironment);
127-
process.copyOutputTo(stream);
128-
process.executeAsync();
129-
ping(startupTimeout, timeUnit);
130-
} catch (Throwable e) {
131-
destroyProcess();
132-
String msgTxt = "The local appium server has not been started. " +
133-
"The given Node.js executable: " + this.nodeJSExec.getAbsolutePath() + " Arguments: " + nodeJSArgs.toString() + " " + "\n";
134-
if (process != null) {
135-
String processStream = process.getStdOut();
136-
if (!StringUtils.isBlank(processStream))
137-
msgTxt = msgTxt + "Process output: " + processStream + "\n";
124+
if (isRunning()) {
125+
return;
138126
}
139127

140-
throw new AppiumServerHasNotBeenStartedLocallyException(msgTxt,
141-
e);
128+
try {
129+
process = new CommandLine(this.nodeJSExec.getCanonicalPath(), nodeJSArgs.toArray(new String[]{}));
130+
process.setEnvironmentVariables(nodeJSEnvironment);
131+
process.copyOutputTo(stream);
132+
process.executeAsync();
133+
ping(startupTimeout, timeUnit);
134+
} catch (Throwable e) {
135+
destroyProcess();
136+
String msgTxt = "The local appium server has not been started. " +
137+
"The given Node.js executable: " + this.nodeJSExec.getAbsolutePath() + " Arguments: " + nodeJSArgs.toString() + " " + "\n";
138+
if (process != null) {
139+
String processStream = process.getStdOut();
140+
if (!StringUtils.isBlank(processStream))
141+
msgTxt = msgTxt + "Process output: " + processStream + "\n";
142+
}
143+
144+
throw new AppiumServerHasNotBeenStartedLocallyException(msgTxt,
145+
e);
146+
}
142147
} finally {
143148
lock.unlock();
144149
}
@@ -153,14 +158,22 @@ public void start() throws AppiumServerHasNotBeenStartedLocallyException {
153158
@Override
154159
public void stop() {
155160
lock.lock();
156-
destroyProcess();
157-
lock.unlock();
161+
try {
162+
if (process != null) {
163+
destroyProcess();
164+
}
165+
process = null;
166+
}
167+
finally {
168+
lock.unlock();
169+
}
158170
}
159171

160172

161173
private void destroyProcess(){
162-
if (process != null)
174+
if (process.isRunning()) {
163175
process.destroy();
176+
}
164177
}
165178

166179
/**

src/test/java/io/appium/java_client/localserver/ServerBuilderTest.java

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -179,4 +179,35 @@ public void checkAbilityToChangeOutputStreamAfterTheServiceIsStarted() throws Ex
179179
file.delete();
180180
}
181181
}
182+
183+
@Test
184+
public void checkAbilityToShutDownService() {
185+
AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();
186+
service.start();
187+
service.stop();
188+
assertTrue(!service.isRunning());
189+
}
190+
191+
@Test
192+
public void checkAbilityToStartAndShutDownFewServices() throws Exception{
193+
AppiumDriverLocalService service1 = new AppiumServiceBuilder().usingAnyFreePort().build();
194+
AppiumDriverLocalService service2 = new AppiumServiceBuilder().usingAnyFreePort().build();
195+
AppiumDriverLocalService service3 = new AppiumServiceBuilder().usingAnyFreePort().build();
196+
AppiumDriverLocalService service4 = new AppiumServiceBuilder().usingAnyFreePort().build();
197+
service1.start();
198+
service2.start();
199+
service3.start();
200+
service4.start();
201+
service1.stop();
202+
Thread.sleep(1000);
203+
service2.stop();
204+
Thread.sleep(1000);
205+
service3.stop();
206+
Thread.sleep(1000);
207+
service4.stop();
208+
assertTrue(!service1.isRunning());
209+
assertTrue(!service2.isRunning());
210+
assertTrue(!service3.isRunning());
211+
assertTrue(!service4.isRunning());
212+
}
182213
}
Lines changed: 240 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,240 @@
1+
package io.appium.java_client.localserver;
2+
3+
4+
import io.appium.java_client.service.local.AppiumDriverLocalService;
5+
import org.junit.Test;
6+
7+
import static org.junit.Assert.assertTrue;
8+
9+
public class ThreadSafetyTest {
10+
11+
private static abstract class Action implements Cloneable {
12+
abstract Object perform();
13+
14+
public Action clone() {
15+
try {
16+
return (Action) super.clone();
17+
}
18+
catch (Throwable t) {
19+
throw new RuntimeException(t);
20+
}
21+
}
22+
}
23+
24+
private static class TestThread implements Runnable {
25+
private final Action action;
26+
private Object result;
27+
private Throwable t;
28+
29+
TestThread(Action action) {
30+
this.action = action;
31+
}
32+
33+
@Override
34+
public void run() {
35+
try {
36+
result = action.perform();
37+
}
38+
catch (Throwable t) {
39+
this.t = t;
40+
}
41+
42+
}
43+
}
44+
45+
final AppiumDriverLocalService service = AppiumDriverLocalService.buildDefaultService();
46+
final Action run = new Action() {
47+
@Override
48+
Object perform() {
49+
service.start();
50+
return "OK";
51+
}
52+
};
53+
final Action run2 = run.clone();
54+
55+
final Action isRunning = new Action() {
56+
@Override
57+
Object perform() {
58+
return service.isRunning();
59+
}
60+
};
61+
final Action isRunning2 = isRunning.clone();
62+
63+
final Action stop = new Action() {
64+
@Override
65+
Object perform() {
66+
service.stop();
67+
return "OK";
68+
}
69+
};
70+
71+
final Action stop2 = stop.clone();
72+
73+
@Test
74+
public void whenFewTreadsDoTheSameWork() throws Throwable {
75+
76+
TestThread runTestThread = new TestThread(run);
77+
TestThread runTestThread2 = new TestThread(run2);
78+
79+
TestThread isRunningTestThread = new TestThread(isRunning);
80+
TestThread isRunningTestThread2 = new TestThread(isRunning2);
81+
82+
TestThread stopTestThread = new TestThread(stop);
83+
TestThread stopTestThread2 = new TestThread(stop2);
84+
85+
Thread runThread = new Thread(runTestThread);
86+
Thread runThread2 = new Thread(runTestThread2);
87+
88+
Thread isRunningThread = new Thread(isRunningTestThread);
89+
Thread isRunningThread2 = new Thread(isRunningTestThread2);
90+
91+
Thread stopThread = new Thread(stopTestThread);
92+
Thread stopThread2 = new Thread(stopTestThread2);
93+
94+
try {
95+
runThread.start();
96+
runThread2.start();
97+
98+
while (runThread.isAlive() || runThread2.isAlive()) {
99+
//do nothing
100+
}
101+
102+
if (runTestThread.t != null) {
103+
throw runTestThread.t;
104+
}
105+
106+
if (runTestThread2.t != null) {
107+
throw runTestThread2.t;
108+
}
109+
110+
assertTrue(runTestThread.result.equals("OK"));
111+
assertTrue(runTestThread2.result.equals("OK"));
112+
assertTrue(service.isRunning());
113+
114+
isRunningThread.start();
115+
isRunningThread2.start();
116+
117+
while (isRunningThread.isAlive() || isRunningThread2.isAlive()) {
118+
//do nothing
119+
}
120+
121+
if (isRunningTestThread.t != null) {
122+
throw isRunningTestThread.t;
123+
}
124+
125+
if (isRunningTestThread2.t != null) {
126+
throw isRunningTestThread2.t;
127+
}
128+
129+
assertTrue(isRunningTestThread.result.equals(true));
130+
assertTrue(isRunningTestThread2.result.equals(true));
131+
132+
stopThread.start();
133+
stopThread2.start();
134+
135+
while (stopThread.isAlive() || stopThread2.isAlive()) {
136+
//do nothing
137+
}
138+
139+
if (stopTestThread.t != null) {
140+
throw stopTestThread.t;
141+
}
142+
143+
if (stopTestThread2.t != null) {
144+
throw stopTestThread2.t;
145+
}
146+
147+
assertTrue(stopTestThread.result.equals("OK"));
148+
assertTrue(stopTestThread2.result.equals("OK"));
149+
assertTrue(!service.isRunning());
150+
}
151+
finally {
152+
if (service.isRunning()) {
153+
service.stop();
154+
}
155+
}
156+
157+
}
158+
159+
@Test
160+
public void whenFewTreadsDoDifferentWork() throws Throwable {
161+
TestThread runTestThread = new TestThread(run);
162+
TestThread runTestThread2 = new TestThread(run2);
163+
164+
TestThread isRunningTestThread = new TestThread(isRunning);
165+
TestThread isRunningTestThread2 = new TestThread(isRunning2);
166+
167+
TestThread stopTestThread = new TestThread(stop);
168+
TestThread stopTestThread2 = new TestThread(stop2);
169+
170+
Thread runThread = new Thread(runTestThread);
171+
Thread runThread2 = new Thread(runTestThread2);
172+
173+
Thread isRunningThread = new Thread(isRunningTestThread);
174+
Thread isRunningThread2 = new Thread(isRunningTestThread2);
175+
176+
Thread stopThread = new Thread(stopTestThread);
177+
Thread stopThread2 = new Thread(stopTestThread2);
178+
179+
try {
180+
runThread.start(); //(1)
181+
Thread.sleep(10);
182+
isRunningThread.start();//(2)
183+
Thread.sleep(10);
184+
stopThread.start(); //(3)
185+
186+
while (runThread.isAlive() || isRunningThread.isAlive() || stopThread.isAlive()) {
187+
//do nothing
188+
}
189+
190+
if (runTestThread.t != null) {
191+
throw runTestThread.t;
192+
}
193+
194+
if (isRunningTestThread.t != null) {
195+
throw isRunningTestThread.t;
196+
}
197+
198+
if (stopTestThread.t != null) {
199+
throw stopTestThread.t;
200+
}
201+
202+
assertTrue(runTestThread.result.equals("OK")); //the service had been started firstly (see (1))
203+
assertTrue(isRunningTestThread.result.equals(true)); //it was running (see (2))
204+
assertTrue(stopTestThread.result.equals("OK")); //and then the test tried to shut down it (see (3))
205+
assertTrue(!service.isRunning());
206+
207+
isRunningThread2.start(); // (1)
208+
Thread.sleep(10);
209+
stopThread2.start(); // (2)
210+
Thread.sleep(10);
211+
runThread2.start(); //(3)
212+
213+
while (runThread2.isAlive() || isRunningThread2.isAlive() || stopThread2.isAlive()) {
214+
//do nothing
215+
}
216+
217+
if (runTestThread2.t != null) {
218+
throw runTestThread.t;
219+
}
220+
221+
if (isRunningTestThread2.t != null) {
222+
throw isRunningTestThread.t;
223+
}
224+
225+
if (stopTestThread2.t != null) {
226+
throw stopTestThread.t;
227+
}
228+
229+
assertTrue(isRunningTestThread2.result.equals(false)); //the service wasn't being running (see (1))
230+
assertTrue(stopTestThread2.result.equals("OK")); //the service had not been started firstly (see (2)), it is ok
231+
assertTrue(runTestThread2.result.equals("OK")); //and then it was started (see (3))
232+
assertTrue(service.isRunning());
233+
}
234+
finally {
235+
if (service.isRunning()) {
236+
service.stop();
237+
}
238+
}
239+
}
240+
}

0 commit comments

Comments
 (0)