1 /*
2 * Copyright (C) 2009, Google Inc.
3 * and other copyright owners as documented in the project's IP log.
4 *
5 * This program and the accompanying materials are made available
6 * under the terms of the Eclipse Distribution License v1.0 which
7 * accompanies this distribution, is reproduced below, and is
8 * available at http://www.eclipse.org/org/documents/edl-v10.php
9 *
10 * All rights reserved.
11 *
12 * Redistribution and use in source and binary forms, with or
13 * without modification, are permitted provided that the following
14 * conditions are met:
15 *
16 * - Redistributions of source code must retain the above copyright
17 * notice, this list of conditions and the following disclaimer.
18 *
19 * - Redistributions in binary form must reproduce the above
20 * copyright notice, this list of conditions and the following
21 * disclaimer in the documentation and/or other materials provided
22 * with the distribution.
23 *
24 * - Neither the name of the Eclipse Foundation, Inc. nor the
25 * names of its contributors may be used to endorse or promote
26 * products derived from this software without specific prior
27 * written permission.
28 *
29 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND
30 * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES,
31 * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
32 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
33 * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
34 * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
35 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
36 * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
37 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
38 * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
39 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
40 * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
41 * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
42 */
43
44 package org.eclipse.jgit.util.io;
45
46 import java.text.MessageFormat;
47
48 import org.eclipse.jgit.internal.JGitText;
49
50 /**
51 * Triggers an interrupt on the calling thread if it doesn't complete a block.
52 * <p>
53 * Classes can use this to trip an alarm interrupting the calling thread if it
54 * doesn't complete a block within the specified timeout. Typical calling
55 * pattern is:
56 *
57 * <pre>
58 * private InterruptTimer myTimer = ...;
59 * void foo() {
60 * try {
61 * myTimer.begin(timeout);
62 * // work
63 * } finally {
64 * myTimer.end();
65 * }
66 * }
67 * </pre>
68 * <p>
69 * An InterruptTimer is not recursive. To implement recursive timers,
70 * independent InterruptTimer instances are required. A single InterruptTimer
71 * may be shared between objects which won't recursively call each other.
72 * <p>
73 * Each InterruptTimer spawns one background thread to sleep the specified time
74 * and interrupt the thread which called {@link #begin(int)}. It is up to the
75 * caller to ensure that the operations within the work block between the
76 * matched begin and end calls tests the interrupt flag (most IO operations do).
77 * <p>
78 * To terminate the background thread, use {@link #terminate()}. If the
79 * application fails to terminate the thread, it will (eventually) terminate
80 * itself when the InterruptTimer instance is garbage collected.
81 *
82 * @see TimeoutInputStream
83 */
84 public final class InterruptTimer {
85 private final AlarmState state;
86
87 private final AlarmThread thread;
88
89 final AutoKiller autoKiller;
90
91 /**
92 * Create a new timer with a default thread name.
93 */
94 public InterruptTimer() {
95 this("JGit-InterruptTimer"); //$NON-NLS-1$
96 }
97
98 /**
99 * Create a new timer to signal on interrupt on the caller.
100 * <p>
101 * The timer thread is created in the calling thread's ThreadGroup.
102 *
103 * @param threadName
104 * name of the timer thread.
105 */
106 public InterruptTimer(String threadName) {
107 state = new AlarmState();
108 autoKiller = new AutoKiller(state);
109 thread = new AlarmThread(threadName, state);
110 thread.start();
111 }
112
113 /**
114 * Arm the interrupt timer before entering a blocking operation.
115 *
116 * @param timeout
117 * number of milliseconds before the interrupt should trigger.
118 * Must be > 0.
119 */
120 public void begin(int timeout) {
121 if (timeout <= 0)
122 throw new IllegalArgumentException(MessageFormat.format(
123 JGitText.get().invalidTimeout, Integer.valueOf(timeout)));
124 Thread.interrupted();
125 state.begin(timeout);
126 }
127
128 /**
129 * Disable the interrupt timer, as the operation is complete.
130 */
131 public void end() {
132 state.end();
133 }
134
135 /**
136 * Shutdown the timer thread, and wait for it to terminate.
137 */
138 public void terminate() {
139 state.terminate();
140 try {
141 thread.join();
142 } catch (InterruptedException e) {
143 //
144 }
145 }
146
147 static final class AlarmThread extends Thread {
148 AlarmThread(String name, AlarmState q) {
149 super(q);
150 setName(name);
151 setDaemon(true);
152 }
153 }
154
155 // The trick here is, the AlarmThread does not have a reference to the
156 // AutoKiller instance, only the InterruptTimer itself does. Thus when
157 // the InterruptTimer is GC'd, the AutoKiller is also unreachable and
158 // can be GC'd. When it gets finalized, it tells the AlarmThread to
159 // terminate, triggering the thread to exit gracefully.
160 //
161 private static final class AutoKiller {
162 private final AlarmState state;
163
164 AutoKiller(AlarmState s) {
165 state = s;
166 }
167
168 @Override
169 protected void finalize() throws Throwable {
170 state.terminate();
171 }
172 }
173
174 static final class AlarmState implements Runnable {
175 private Thread callingThread;
176
177 private long deadline;
178
179 private boolean terminated;
180
181 AlarmState() {
182 callingThread = Thread.currentThread();
183 }
184
185 @Override
186 public synchronized void run() {
187 while (!terminated && callingThread.isAlive()) {
188 try {
189 if (0 < deadline) {
190 final long delay = deadline - now();
191 if (delay <= 0) {
192 deadline = 0;
193 callingThread.interrupt();
194 } else {
195 wait(delay);
196 }
197 } else {
198 wait(1000);
199 }
200 } catch (InterruptedException e) {
201 // Treat an interrupt as notice to examine state.
202 }
203 }
204 }
205
206 synchronized void begin(int timeout) {
207 if (terminated)
208 throw new IllegalStateException(JGitText.get().timerAlreadyTerminated);
209 callingThread = Thread.currentThread();
210 deadline = now() + timeout;
211 notifyAll();
212 }
213
214 synchronized void end() {
215 if (0 == deadline)
216 Thread.interrupted();
217 else
218 deadline = 0;
219 notifyAll();
220 }
221
222 synchronized void terminate() {
223 if (!terminated) {
224 deadline = 0;
225 terminated = true;
226 notifyAll();
227 }
228 }
229
230 private static long now() {
231 return System.currentTimeMillis();
232 }
233 }
234 }