blob: e2c6341b4710c639b5281c99238a1c1e435fc6a0 [file] [log] [blame]
mchungd70ecd42013-11-05 17:33:26 -08001/*
jbachorik17c7bdb2014-10-23 11:42:20 +02002 * Copyright (c) 2013, 2014 Oracle and/or its affiliates. All rights reserved.
mchungd70ecd42013-11-05 17:33:26 -08003 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23
24import java.util.concurrent.Phaser;
25import java.util.concurrent.TimeUnit;
26import java.util.concurrent.TimeoutException;
27import java.util.concurrent.atomic.AtomicInteger;
28import java.util.concurrent.locks.LockSupport;
29
jbachorik17c7bdb2014-10-23 11:42:20 +020030import jdk.testlibrary.LockFreeLogManager;
31
mchungd70ecd42013-11-05 17:33:26 -080032/**
33 * ThreadStateController allows a thread to request this thread to transition
34 * to a specific thread state. The {@linkplain #transitionTo request} is
35 * a blocking call that the calling thread will wait until this thread is about
36 * going to the new state. Only one request of state transition at a time
37 * is supported (the Phaser expects only parties of 2 to arrive and advance
38 * to next phase).
39 */
40public class ThreadStateController extends Thread {
41 // used to achieve waiting states
42 private final Object lock;
43 public ThreadStateController(String name, Object lock) {
44 super(name);
45 this.lock = lock;
46 }
47
48 public void checkThreadState(Thread.State expected) {
49 // maximum number of retries when checking for thread state.
50 final int MAX_RETRY = 500;
51
52 // wait for the thread to transition to the expected state.
53 // There is a small window between the thread checking the state
54 // and the thread actual entering that state.
55 Thread.State state;
56 int retryCount=0;
57 while ((state = getState()) != expected && retryCount < MAX_RETRY) {
58 pause(10);
59 retryCount++;
60 }
61
62 if (state == null) {
63 throw new RuntimeException(getName() + " expected to have " +
64 expected + " but got null.");
65 }
66
67 if (state != expected) {
68 throw new RuntimeException(String.format("%s expected in %s state but got %s " +
69 "(iterations %d interrupted %d)%n",
70 getName(), expected, state, iterations.get(), interrupted.get()));
71 }
72 }
73
74 public static void pause(long ms) {
75 try {
76 Thread.sleep(ms);
77 } catch (InterruptedException e) {
78 throw new RuntimeException(e);
79 }
80 }
81
82 // Phaser to sync between the main thread putting
83 // this thread into various states
84 private final Phaser phaser = new Phaser(2);
85 private volatile int newState = S_RUNNABLE;
86 private volatile int state = 0;
87 private boolean done = false;
88
89 private static final int S_RUNNABLE = 1;
90 private static final int S_BLOCKED = 2;
91 private static final int S_WAITING = 3;
92 private static final int S_TIMED_WAITING = 4;
93 private static final int S_PARKED = 5;
94 private static final int S_TIMED_PARKED = 6;
95 private static final int S_SLEEPING = 7;
96 private static final int S_TERMINATE = 8;
97
98 // for debugging
jbachorik17c7bdb2014-10-23 11:42:20 +020099 private final AtomicInteger iterations = new AtomicInteger();
100 private final AtomicInteger interrupted = new AtomicInteger();
101
102 private final LockFreeLogManager logManager = new LockFreeLogManager();
103
104 @Override
mchungd70ecd42013-11-05 17:33:26 -0800105 public void run() {
106 // this thread has started
107 while (!done) {
108 // state transition
109 int nextState = state;
110 if (newState != state) {
111 nextState = newState;
112 iterations.set(0);
113 interrupted.set(0);
114 }
115 iterations.incrementAndGet();
116 switch (nextState) {
117 case S_RUNNABLE: {
118 stateChange(nextState);
119 double sum = 0;
120 for (int i = 0; i < 1000; i++) {
121 double r = Math.random();
122 double x = Math.pow(3, r);
123 sum += x - r;
124 }
125 break;
126 }
127 case S_BLOCKED: {
jbachorik17c7bdb2014-10-23 11:42:20 +0200128 log("%d: %s is going to block (iterations %d)%n",
129 getId(), getName(), iterations.get());
mchungd70ecd42013-11-05 17:33:26 -0800130 stateChange(nextState);
131 // going to block on lock
132 synchronized (lock) {
jbachorik17c7bdb2014-10-23 11:42:20 +0200133 log("%d: %s acquired the lock (iterations %d)%n",
134 getId(), getName(), iterations.get());
mchungd70ecd42013-11-05 17:33:26 -0800135 try {
136 // this thread has escaped the BLOCKED state
137 // release the lock and a short wait before continue
138 lock.wait(10);
139 } catch (InterruptedException e) {
140 // ignore
141 interrupted.incrementAndGet();
142 }
143 }
144 break;
145 }
146 case S_WAITING: {
147 synchronized (lock) {
jbachorik17c7bdb2014-10-23 11:42:20 +0200148 log("%d: %s is going to waiting (iterations %d interrupted %d)%n",
149 getId(), getName(), iterations.get(), interrupted.get());
mchungd70ecd42013-11-05 17:33:26 -0800150 try {
151 stateChange(nextState);
152 lock.wait();
jbachorik17c7bdb2014-10-23 11:42:20 +0200153 log("%d: %s wakes up from waiting (iterations %d interrupted %d)%n",
154 getId(), getName(), iterations.get(), interrupted.get());
mchungd70ecd42013-11-05 17:33:26 -0800155 } catch (InterruptedException e) {
156 // ignore
157 interrupted.incrementAndGet();
158 }
159 }
160 break;
161 }
162 case S_TIMED_WAITING: {
163 synchronized (lock) {
jbachorik17c7bdb2014-10-23 11:42:20 +0200164 log("%d: %s is going to timed waiting (iterations %d interrupted %d)%n",
165 getId(), getName(), iterations.get(), interrupted.get());
mchungd70ecd42013-11-05 17:33:26 -0800166 try {
167 stateChange(nextState);
168 lock.wait(10000);
jbachorik17c7bdb2014-10-23 11:42:20 +0200169 log("%d: %s wakes up from timed waiting (iterations %d interrupted %d)%n",
170 getId(), getName(), iterations.get(), interrupted.get());
mchungd70ecd42013-11-05 17:33:26 -0800171 } catch (InterruptedException e) {
172 // ignore
173 interrupted.incrementAndGet();
174 }
175 }
176 break;
177 }
178 case S_PARKED: {
jbachorik17c7bdb2014-10-23 11:42:20 +0200179 log("%d: %s is going to park (iterations %d)%n",
180 getId(), getName(), iterations.get());
mchungd70ecd42013-11-05 17:33:26 -0800181 stateChange(nextState);
182 LockSupport.park();
183 break;
184 }
185 case S_TIMED_PARKED: {
jbachorik17c7bdb2014-10-23 11:42:20 +0200186 log("%d: %s is going to timed park (iterations %d)%n",
187 getId(), getName(), iterations.get());
mchungd70ecd42013-11-05 17:33:26 -0800188 long deadline = System.currentTimeMillis() + 10000*1000;
189 stateChange(nextState);
190 LockSupport.parkUntil(deadline);
191 break;
192 }
193 case S_SLEEPING: {
jbachorik17c7bdb2014-10-23 11:42:20 +0200194 log("%d: %s is going to sleep (iterations %d interrupted %d)%n",
195 getId(), getName(), iterations.get(), interrupted.get());
mchungd70ecd42013-11-05 17:33:26 -0800196 try {
197 stateChange(nextState);
198 Thread.sleep(1000000);
199 } catch (InterruptedException e) {
200 // finish sleeping
201 interrupted.incrementAndGet();
202 }
203 break;
204 }
205 case S_TERMINATE: {
206 done = true;
207 stateChange(nextState);
208 break;
209 }
210 default:
211 break;
212 }
213 }
214 }
215
216 /**
217 * Change the state if it matches newState.
218 */
219 private void stateChange(int nextState) {
220 // no state change
221 if (state == nextState)
222 return;
223
224 // transition to the new state
225 if (newState == nextState) {
226 state = nextState;
227 phaser.arrive();
jbachorik17c7bdb2014-10-23 11:42:20 +0200228 log("%d: state change: %s %s%n",
229 getId(), toStateName(nextState), phaserToString(phaser));
mchungd70ecd42013-11-05 17:33:26 -0800230 return;
231 }
232
233 // should never reach here
234 throw new RuntimeException("current " + state + " next " + nextState +
235 " new state " + newState);
236 }
237
238 /**
239 * Blocks until this thread transitions to the given state
240 */
241 public void transitionTo(Thread.State tstate) throws InterruptedException {
242 switch (tstate) {
243 case RUNNABLE:
244 nextState(S_RUNNABLE);
245 break;
246 case BLOCKED:
247 nextState(S_BLOCKED);
248 break;
249 case WAITING:
250 nextState(S_WAITING);
251 break;
252 case TIMED_WAITING:
253 nextState(S_TIMED_WAITING);
254 break;
255 case TERMINATED:
256 nextState(S_TERMINATE);
257 break;
258 default:
259 break;
260 }
261 }
262
263 /**
264 * Blocks until this thread transitions to sleeping
265 */
266 public void transitionToSleep() throws InterruptedException {
267 nextState(S_SLEEPING);
268 }
269
270 /**
271 * Blocks until this thread transitions to park or timed park
272 */
273 public void transitionToPark(boolean timed) throws InterruptedException {
274 nextState(timed ? S_TIMED_PARKED : S_PARKED);
275 }
276
277 private void nextState(int s) throws InterruptedException {
278 final long id = Thread.currentThread().getId();
jbachorik17c7bdb2014-10-23 11:42:20 +0200279 log("%d: wait until the thread transitions to %s %s%n",
280 id, toStateName(s), phaserToString(phaser));
mchungd70ecd42013-11-05 17:33:26 -0800281 this.newState = s;
282 int phase = phaser.arrive();
jbachorik17c7bdb2014-10-23 11:42:20 +0200283 log("%d: awaiting party arrive %s %s%n",
284 id, toStateName(s), phaserToString(phaser));
mchungd70ecd42013-11-05 17:33:26 -0800285 for (;;) {
286 // when this thread has changed its state before it waits or parks
287 // on a lock, a potential race might happen if it misses the notify
288 // or unpark. Hence await for the phaser to advance with timeout
289 // to cope with this race condition.
290 switch (state) {
291 case S_WAITING:
292 case S_TIMED_WAITING:
293 synchronized (lock) {
294 lock.notify();
295 }
296 break;
297 case S_PARKED:
298 case S_TIMED_PARKED:
299 LockSupport.unpark(this);
300 break;
301 case S_SLEEPING:
302 this.interrupt();
303 break;
304 case S_BLOCKED:
305 default:
306 break;
307 }
308 try {
309 phaser.awaitAdvanceInterruptibly(phase, 100, TimeUnit.MILLISECONDS);
jbachorik17c7bdb2014-10-23 11:42:20 +0200310 log("%d: arrived at %s %s%n",
311 id, toStateName(s), phaserToString(phaser));
mchungd70ecd42013-11-05 17:33:26 -0800312 return;
313 } catch (TimeoutException ex) {
314 // this thread hasn't arrived at this phase
jbachorik17c7bdb2014-10-23 11:42:20 +0200315 log("%d: Timeout: %s%n", id, phaser);
mchungd70ecd42013-11-05 17:33:26 -0800316 }
317 }
318 }
jbachorik17c7bdb2014-10-23 11:42:20 +0200319
mchungd70ecd42013-11-05 17:33:26 -0800320 private String phaserToString(Phaser p) {
321 return "[phase = " + p.getPhase() +
322 " parties = " + p.getRegisteredParties() +
323 " arrived = " + p.getArrivedParties() + "]";
324 }
jbachorik17c7bdb2014-10-23 11:42:20 +0200325
mchungd70ecd42013-11-05 17:33:26 -0800326 private String toStateName(int state) {
327 switch (state) {
328 case S_RUNNABLE:
329 return "runnable";
330 case S_WAITING:
331 return "waiting";
332 case S_TIMED_WAITING:
333 return "timed waiting";
334 case S_PARKED:
335 return "parked";
336 case S_TIMED_PARKED:
337 return "timed parked";
338 case S_SLEEPING:
339 return "sleeping";
340 case S_BLOCKED:
341 return "blocked";
342 case S_TERMINATE:
343 return "terminated";
344 default:
345 return "unknown " + state;
346 }
347 }
jbachorik17c7bdb2014-10-23 11:42:20 +0200348
349 private void log(String msg, Object ... params) {
350 logManager.log(msg, params);
351 }
352
353 /**
354 * Waits for the controller to complete the test run and returns the
355 * generated log
356 * @return The controller log
357 * @throws InterruptedException
358 */
359 public String getLog() throws InterruptedException {
360 this.join();
361
362 return logManager.toString();
363 }
mchungd70ecd42013-11-05 17:33:26 -0800364}