1 /*
2  * Copyright (c) 2014, 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 /**
25  * This test is useful for finding out whether a Thread can have a
26  * private variable indicate whether or not it is finished, and to illustrate
27  * the ease with which Threads terminate each other.
28  *
29  * @test
30  */
31 
32 public class CancellableThreadTest {
33     public static final int THREADPAIRS = Integer.parseInt(System.getProperty("test.threadpairs", "128"));
34 
main(String args[])35     public static void main(String args[]) {
36         Thread[] threads = new Thread[THREADPAIRS];
37         Canceller[] cancellers = new Canceller[THREADPAIRS];
38 
39         System.out.println("Running with " + THREADPAIRS + " thread pairs");
40 
41         for (int i = 0; i < THREADPAIRS; i++) {
42             cancellers[i] = new Canceller(i);
43             threads[i] = new Thread(cancellers[i]);
44             threads[i].start();
45         }
46 
47         for (int i = 0; i < THREADPAIRS; i++) {
48             try {
49                 threads[i].join();
50             } catch (InterruptedException e) {
51             }
52 
53             if (cancellers[i].failed) {
54                 throw new RuntimeException(" Test failed in " + i + " th pair. See error messages above.");
55             }
56         }
57     }
58 }
59 
60 class Canceller implements Runnable {
61 
62     private final CancellableTimer timer;
63 
64     public final String name;
65     public volatile boolean failed = false;
66     private volatile boolean hasBeenNotified = false;
67 
Canceller(int index)68     public Canceller(int index) {
69         this.name = "Canceller #" + index;
70         timer = new CancellableTimer(index, this);
71     }
72 
setHasBeenNotified()73     public void setHasBeenNotified() {
74         hasBeenNotified = true;
75     }
76 
77     /**
78      * This method contains the "action" of this Canceller Thread.
79      * It starts a CancellableTimer, waits, and then interrupts the
80      * CancellableTimer after the CancellableTimer notifies it.  It then
81      * tries to join the CancellableTimer to itself and reports on whether
82      * it was successful in doing so.
83      */
run()84     public void run() {
85         Thread timerThread = new Thread(timer);
86 
87         try {
88             synchronized(this) {
89                 timerThread.start();
90                 wait();
91             }
92         } catch (InterruptedException e) {
93             System.err.println(name + " was interrupted during wait()");
94             failed = true;
95         }
96 
97         if (!hasBeenNotified) {
98             System.err.println(name + ".hasBeenNotified is not true as expected");
99             failed = true;
100         }
101 
102         synchronized (timer) {
103             timerThread.interrupt();
104         }
105 
106         try {
107             timerThread.join();
108         } catch (InterruptedException ie) {
109             System.err.println(name + " was interrupted while joining " +
110                     timer.name);
111             failed = true;
112         }
113 
114         if (timerThread.isAlive()) {
115             System.err.println(timer.name + " is still alive after " + name +
116                     " attempted to join it.");
117             failed = true;
118         }
119     }
120 }
121 
122 /**
123  * This non-public class is the Thread which the Canceller Thread deliberately
124  * interrupts and then joins to itself after this Thread has slept for a few milliseconds.
125  */
126 
127 class CancellableTimer implements Runnable {
128 
129     public final String name;
130     private final Canceller myCanceller;
131 
CancellableTimer(int index, Canceller aCanceller)132     public CancellableTimer(int index, Canceller aCanceller) {
133         this.name = "CancellableTimer #" + index;
134         this.myCanceller = aCanceller;
135     }
136 
137     /**
138      * This is where this CancellableTimer does its work. It notifies its
139      * Canceller, waits for the Canceller to interrupt it, then catches the
140      * InterruptedException, and sleeps for a few milliseconds before exiting.
141      */
run()142     public void run() {
143         try {
144             synchronized (this) {
145                 synchronized (myCanceller) {
146                     myCanceller.setHasBeenNotified();
147                     myCanceller.notify();
148                 }
149                 wait();
150             }
151         } catch (InterruptedException first) {
152             // isInterrupted should've been cleared here and we should not register a
153             // second interrupt
154             if (Thread.currentThread().isInterrupted()) {
155                 System.err.println(name + " should not register an interrupt here");
156                 myCanceller.failed = true;
157             }
158 
159             try {
160                 Thread.sleep(1);
161             } catch (InterruptedException e) {
162                 System.err.println(name + " was interrupted when sleeping");
163                 myCanceller.failed = true;
164             }
165         }
166     }
167 }
168