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 /** Create a new timer with a default thread name. */
92 public InterruptTimer() {
93 this("JGit-InterruptTimer"); //$NON-NLS-1$
94 }
95
96 /**
97 * Create a new timer to signal on interrupt on the caller.
98 * <p>
99 * The timer thread is created in the calling thread's ThreadGroup.
100 *
101 * @param threadName
102 * name of the timer thread.
103 */
104 public InterruptTimer(final String threadName) {
105 state = new AlarmState();
106 autoKiller = new AutoKiller(state);
107 thread = new AlarmThread(threadName, state);
108 thread.start();
109 }
110
111 /**
112 * Arm the interrupt timer before entering a blocking operation.
113 *
114 * @param timeout
115 * number of milliseconds before the interrupt should trigger.
116 * Must be > 0.
117 */
118 public void begin(final int timeout) {
119 if (timeout <= 0)
120 throw new IllegalArgumentException(MessageFormat.format(
121 JGitText.get().invalidTimeout, Integer.valueOf(timeout)));
122 Thread.interrupted();
123 state.begin(timeout);
124 }
125
126 /** Disable the interrupt timer, as the operation is complete. */
127 public void end() {
128 state.end();
129 }
130
131 /** Shutdown the timer thread, and wait for it to terminate. */
132 public void terminate() {
133 state.terminate();
134 try {
135 thread.join();
136 } catch (InterruptedException e) {
137 //
138 }
139 }
140
141 static final class AlarmThread extends Thread {
142 AlarmThread(final String name, final AlarmState q) {
143 super(q);
144 setName(name);
145 setDaemon(true);
146 }
147 }
148
149 // The trick here is, the AlarmThread does not have a reference to the
150 // AutoKiller instance, only the InterruptTimer itself does. Thus when
151 // the InterruptTimer is GC'd, the AutoKiller is also unreachable and
152 // can be GC'd. When it gets finalized, it tells the AlarmThread to
153 // terminate, triggering the thread to exit gracefully.
154 //
155 private static final class AutoKiller {
156 private final AlarmState state;
157
158 AutoKiller(final AlarmState s) {
159 state = s;
160 }
161
162 @Override
163 protected void finalize() throws Throwable {
164 state.terminate();
165 }
166 }
167
168 static final class AlarmState implements Runnable {
169 private Thread callingThread;
170
171 private long deadline;
172
173 private boolean terminated;
174
175 AlarmState() {
176 callingThread = Thread.currentThread();
177 }
178
179 @Override
180 public synchronized void run() {
181 while (!terminated && callingThread.isAlive()) {
182 try {
183 if (0 < deadline) {
184 final long delay = deadline - now();
185 if (delay <= 0) {
186 deadline = 0;
187 callingThread.interrupt();
188 } else {
189 wait(delay);
190 }
191 } else {
192 wait(1000);
193 }
194 } catch (InterruptedException e) {
195 // Treat an interrupt as notice to examine state.
196 }
197 }
198 }
199
200 synchronized void begin(final int timeout) {
201 if (terminated)
202 throw new IllegalStateException(JGitText.get().timerAlreadyTerminated);
203 callingThread = Thread.currentThread();
204 deadline = now() + timeout;
205 notifyAll();
206 }
207
208 synchronized void end() {
209 if (0 == deadline)
210 Thread.interrupted();
211 else
212 deadline = 0;
213 notifyAll();
214 }
215
216 synchronized void terminate() {
217 if (!terminated) {
218 deadline = 0;
219 terminated = true;
220 notifyAll();
221 }
222 }
223
224 private static long now() {
225 return System.currentTimeMillis();
226 }
227 }
228 }