1/*
2 * Copyright (c) 2013, 2015, Oracle and/or its affiliates. All rights reserved.
3 * 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
30import jdk.test.lib.LockFreeLogger;
31import jdk.testlibrary.Utils;
32
33/**
34 * ThreadStateController allows a thread to request this thread to transition
35 * to a specific thread state.  The {@linkplain #transitionTo request} is
36 * a blocking call that the calling thread will wait until this thread is about
37 * going to the new state.  Only one request of state transition at a time
38 * is supported (the Phaser expects only parties of 2 to arrive and advance
39 * to next phase).
40 */
41public class ThreadStateController extends Thread {
42    // used to achieve waiting states
43    private final Object lock;
44    public ThreadStateController(String name, Object lock) {
45        super(name);
46        this.lock = lock;
47    }
48
49    public void checkThreadState(Thread.State expected) {
50        // maximum number of retries when checking for thread state.
51        final int MAX_RETRY = 500;
52
53        // wait for the thread to transition to the expected state.
54        // There is a small window between the thread checking the state
55        // and the thread actual entering that state.
56        Thread.State state;
57        int retryCount=0;
58        while ((state = getState()) != expected && retryCount < MAX_RETRY) {
59            pause(10);
60            retryCount++;
61        }
62
63        if (state == null) {
64            throw new RuntimeException(getName() + " expected to have " +
65                expected + " but got null.");
66        }
67
68        if (state != expected) {
69            throw new RuntimeException(String.format("%s expected in %s state but got %s " +
70                "(iterations %d interrupted %d)%n",
71                getName(), expected, state, iterations.get(), interrupted.get()));
72        }
73    }
74
75    public static void pause(long ms) {
76        try {
77            Thread.sleep(Utils.adjustTimeout(ms));
78        } catch (InterruptedException e) {
79            throw new RuntimeException(e);
80        }
81    }
82
83    // Phaser to sync between the main thread putting
84    // this thread into various states
85    private final Phaser phaser =  new Phaser(2);
86    private volatile int newState = S_RUNNABLE;
87    private volatile int state = 0;
88    private boolean done = false;
89
90    private static final int S_RUNNABLE = 1;
91    private static final int S_BLOCKED = 2;
92    private static final int S_WAITING = 3;
93    private static final int S_TIMED_WAITING = 4;
94    private static final int S_PARKED = 5;
95    private static final int S_TIMED_PARKED = 6;
96    private static final int S_SLEEPING = 7;
97    private static final int S_TERMINATE = 8;
98
99    // for debugging
100    private final AtomicInteger iterations = new AtomicInteger();
101    private final AtomicInteger interrupted = new AtomicInteger();
102
103    private final LockFreeLogger logger = new LockFreeLogger();
104
105    @Override
106    public void run() {
107        // this thread has started
108        while (!done) {
109            // state transition
110            int nextState = state;
111            if (newState != state) {
112                nextState = newState;
113                iterations.set(0);
114                interrupted.set(0);
115            }
116            iterations.incrementAndGet();
117            switch (nextState) {
118                case S_RUNNABLE: {
119                    stateChange(nextState);
120                    double sum = 0;
121                    for (int i = 0; i < 1000; i++) {
122                       double r = Math.random();
123                       double x = Math.pow(3, r);
124                       sum += x - r;
125                    }
126                    break;
127                }
128                case S_BLOCKED: {
129                    log("%d: %s is going to block (iterations %d)%n",
130                        getId(), getName(), iterations.get());
131                    stateChange(nextState);
132                    // going to block on lock
133                    synchronized (lock) {
134                        log("%d:   %s acquired the lock (iterations %d)%n",
135                            getId(), getName(), iterations.get());
136                        try {
137                            // this thread has escaped the BLOCKED state
138                            // release the lock and a short wait before continue
139                            lock.wait(Utils.adjustTimeout(10));
140                        } catch (InterruptedException e) {
141                            // ignore
142                            interrupted.incrementAndGet();
143                        }
144                    }
145                    break;
146                }
147                case S_WAITING: {
148                    synchronized (lock) {
149                        log("%d: %s is going to waiting (iterations %d interrupted %d)%n",
150                            getId(), getName(), iterations.get(), interrupted.get());
151                        try {
152                            stateChange(nextState);
153                            lock.wait();
154                            log("%d:   %s wakes up from waiting (iterations %d interrupted %d)%n",
155                                getId(), getName(), iterations.get(), interrupted.get());
156                        } catch (InterruptedException e) {
157                            // ignore
158                            interrupted.incrementAndGet();
159                        }
160                    }
161                    break;
162                }
163                case S_TIMED_WAITING: {
164                    synchronized (lock) {
165                        log("%d: %s is going to timed waiting (iterations %d interrupted %d)%n",
166                            getId(), getName(), iterations.get(), interrupted.get());
167                        try {
168                            stateChange(nextState);
169                            lock.wait(Integer.MAX_VALUE);
170                            log("%d:   %s wakes up from timed waiting (iterations %d interrupted %d)%n",
171                                getId(), getName(), iterations.get(), interrupted.get());
172                        } catch (InterruptedException e) {
173                            // ignore
174                            interrupted.incrementAndGet();
175                        }
176                    }
177                    break;
178                }
179                case S_PARKED: {
180                    log("%d: %s is going to park (iterations %d)%n",
181                        getId(), getName(), iterations.get());
182                    stateChange(nextState);
183                    LockSupport.park();
184                    break;
185                }
186                case S_TIMED_PARKED: {
187                    log("%d: %s is going to timed park (iterations %d)%n",
188                        getId(), getName(), iterations.get());
189                    long deadline = System.currentTimeMillis() +
190                                        Utils.adjustTimeout(10000*1000);
191                    stateChange(nextState);
192                    LockSupport.parkUntil(deadline);
193                    break;
194                }
195                case S_SLEEPING: {
196                    log("%d: %s is going to sleep (iterations %d interrupted %d)%n",
197                        getId(), getName(), iterations.get(), interrupted.get());
198                    try {
199                        stateChange(nextState);
200                        Thread.sleep(Utils.adjustTimeout(1000000));
201                    } catch (InterruptedException e) {
202                        // finish sleeping
203                        interrupted.incrementAndGet();
204                    }
205                    break;
206                }
207                case S_TERMINATE: {
208                    done = true;
209                    stateChange(nextState);
210                    break;
211                }
212                default:
213                    break;
214            }
215        }
216    }
217
218    /**
219     * Change the state if it matches newState.
220     */
221    private void stateChange(int nextState) {
222        // no state change
223        if (state == nextState)
224            return;
225
226        // transition to the new state
227        if (newState == nextState) {
228            state = nextState;
229            phaser.arrive();
230            log("%d:   state change: %s %s%n",
231                getId(), toStateName(nextState), phaserToString(phaser));
232            return;
233        }
234
235        // should never reach here
236        throw new RuntimeException("current " + state + " next " + nextState +
237                " new state " + newState);
238    }
239
240    /**
241     * Blocks until this thread transitions to the given state
242     */
243    public void transitionTo(Thread.State tstate) throws InterruptedException {
244        switch (tstate) {
245            case RUNNABLE:
246                nextState(S_RUNNABLE);
247                break;
248            case BLOCKED:
249                nextState(S_BLOCKED);
250                break;
251            case WAITING:
252                nextState(S_WAITING);
253                break;
254            case TIMED_WAITING:
255                nextState(S_TIMED_WAITING);
256                break;
257            case TERMINATED:
258                nextState(S_TERMINATE);
259                break;
260            default:
261                break;
262        }
263    }
264
265    /**
266     * Blocks until this thread transitions to sleeping
267     */
268    public void transitionToSleep() throws InterruptedException {
269        nextState(S_SLEEPING);
270    }
271
272    /**
273     * Blocks until this thread transitions to park or timed park
274     */
275    public void transitionToPark(boolean timed) throws InterruptedException {
276        nextState(timed ? S_TIMED_PARKED : S_PARKED);
277    }
278
279    private void nextState(int s) throws InterruptedException {
280        final long id = Thread.currentThread().getId();
281        log("%d: wait until the thread transitions to %s %s%n",
282            id, toStateName(s), phaserToString(phaser));
283        this.newState = s;
284        int phase = phaser.arrive();
285        log("%d:   awaiting party arrive %s %s%n",
286            id, toStateName(s), phaserToString(phaser));
287        for (;;) {
288            // when this thread has changed its state before it waits or parks
289            // on a lock, a potential race might happen if it misses the notify
290            // or unpark.  Hence await for the phaser to advance with timeout
291            // to cope with this race condition.
292            switch (state) {
293                case S_WAITING:
294                case S_TIMED_WAITING:
295                    synchronized (lock) {
296                        lock.notify();
297                    }
298                    break;
299                case S_PARKED:
300                case S_TIMED_PARKED:
301                    LockSupport.unpark(this);
302                    break;
303                case S_SLEEPING:
304                    this.interrupt();
305                    break;
306                case S_BLOCKED:
307                default:
308                    break;
309            }
310            try {
311                phaser.awaitAdvanceInterruptibly(phase, 100, TimeUnit.MILLISECONDS);
312                log("%d:   arrived at %s %s%n",
313                    id, toStateName(s), phaserToString(phaser));
314                return;
315            } catch (TimeoutException ex) {
316                // this thread hasn't arrived at this phase
317                log("%d: Timeout: %s%n", id, phaser);
318            }
319        }
320    }
321
322    private String phaserToString(Phaser p) {
323        return "[phase = " + p.getPhase() +
324               " parties = " + p.getRegisteredParties() +
325               " arrived = " + p.getArrivedParties() + "]";
326    }
327
328    private String toStateName(int state) {
329        switch (state) {
330            case S_RUNNABLE:
331                return "runnable";
332            case S_WAITING:
333                return "waiting";
334            case S_TIMED_WAITING:
335                return "timed waiting";
336            case S_PARKED:
337                return "parked";
338            case S_TIMED_PARKED:
339                return "timed parked";
340            case S_SLEEPING:
341                return "sleeping";
342            case S_BLOCKED:
343                return "blocked";
344            case S_TERMINATE:
345                return "terminated";
346            default:
347                return "unknown " + state;
348        }
349    }
350
351    private void log(String msg, Object ... params) {
352        logger.log(msg, params);
353    }
354
355    /**
356     * Waits for the controller to complete the test run and returns the
357     * generated log
358     * @return The controller log
359     * @throws InterruptedException
360     */
361    public String getLog() throws InterruptedException {
362        this.join();
363
364        return logger.toString();
365    }
366}
367