1/*
2 * Copyright (C) 2011 The Guava Authors
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.google.common.util.concurrent;
18
19import static java.util.concurrent.TimeUnit.MILLISECONDS;
20import static java.util.concurrent.TimeUnit.NANOSECONDS;
21import static junit.framework.Assert.fail;
22
23import com.google.common.testing.TearDown;
24import com.google.common.testing.TearDownAccepter;
25
26import java.util.concurrent.TimeUnit;
27import java.util.logging.Logger;
28
29/**
30 * Utilities for performing thread interruption in tests
31 *
32 * @author Kevin Bourrillion
33 * @author Chris Povirk
34 */
35final class InterruptionUtil {
36  private static final Logger logger =
37      Logger.getLogger(InterruptionUtil.class.getName());
38
39  /**
40   * Runnable which will interrupt the target thread repeatedly when run.
41   */
42  private static final class Interruptenator implements Runnable {
43    private final long everyMillis;
44    private final Thread interruptee;
45    private volatile boolean shouldStop = false;
46
47    Interruptenator(Thread interruptee, long everyMillis) {
48      this.everyMillis = everyMillis;
49      this.interruptee = interruptee;
50    }
51
52    @Override
53    public void run() {
54      while (true) {
55        try {
56          Thread.sleep(everyMillis);
57        } catch (InterruptedException e) {
58          // ok. just stop sleeping.
59        }
60        if (shouldStop) {
61          break;
62        }
63        interruptee.interrupt();
64      }
65    }
66
67    void stopInterrupting() {
68      shouldStop = true;
69    }
70  }
71
72  /**
73   * Interrupts the current thread after sleeping for the specified delay.
74   */
75  static void requestInterruptIn(final long time, final TimeUnit unit) {
76    final Thread interruptee = Thread.currentThread();
77    new Thread(new Runnable() {
78      @Override
79      public void run() {
80        try {
81          unit.sleep(time);
82        } catch (InterruptedException wontHappen) {
83          throw new AssertionError(wontHappen);
84        }
85        interruptee.interrupt();
86      }
87    }).start();
88  }
89
90  static void repeatedlyInterruptTestThread(
91      long interruptPeriodMillis, TearDownAccepter tearDownAccepter) {
92    final Interruptenator interruptingTask =
93        new Interruptenator(Thread.currentThread(), interruptPeriodMillis);
94    final Thread interruptingThread = new Thread(interruptingTask);
95    interruptingThread.start();
96    tearDownAccepter.addTearDown(new TearDown() {
97      @Override public void tearDown() throws Exception {
98        interruptingTask.stopInterrupting();
99        interruptingThread.interrupt();
100        joinUninterruptibly(interruptingThread, 2500, MILLISECONDS);
101        Thread.interrupted();
102        if (interruptingThread.isAlive()) {
103          // This will be hidden by test-output redirection:
104          logger.severe(
105              "InterruptenatorTask did not exit; future tests may be affected");
106          /*
107           * This won't do any good under JUnit 3, but I'll leave it around in
108           * case we ever switch to JUnit 4:
109           */
110          fail();
111        }
112      }
113    });
114  }
115
116  // TODO(cpovirk): promote to Uninterruptibles, and add untimed version
117  private static void joinUninterruptibly(
118      Thread thread, long timeout, TimeUnit unit) {
119    boolean interrupted = false;
120    try {
121      long remainingNanos = unit.toNanos(timeout);
122      long end = System.nanoTime() + remainingNanos;
123
124      while (true) {
125        try {
126          // TimeUnit.timedJoin() treats negative timeouts just like zero.
127          NANOSECONDS.timedJoin(thread, remainingNanos);
128          return;
129        } catch (InterruptedException e) {
130          interrupted = true;
131          remainingNanos = end - System.nanoTime();
132        }
133      }
134    } finally {
135      if (interrupted) {
136        Thread.currentThread().interrupt();
137      }
138    }
139  }
140}
141