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 
  24 import java.util.concurrent.Phaser;
  25 import java.util.concurrent.TimeUnit;
  26 import java.util.concurrent.TimeoutException;
  27 import java.util.concurrent.atomic.AtomicInteger;
  28 import java.util.concurrent.locks.LockSupport;
  29 
  30 import jdk.test.lib.LockFreeLogger;
  31 import 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  */
  41 public 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 }