| /* |
| * Copyright (C) 2009, Google Inc. |
| * and other copyright owners as documented in the project's IP log. |
| * |
| * This program and the accompanying materials are made available |
| * under the terms of the Eclipse Distribution License v1.0 which |
| * accompanies this distribution, is reproduced below, and is |
| * available at http://www.eclipse.org/org/documents/edl-v10.php |
| * |
| * All rights reserved. |
| * |
| * Redistribution and use in source and binary forms, with or |
| * without modification, are permitted provided that the following |
| * conditions are met: |
| * |
| * - Redistributions of source code must retain the above copyright |
| * notice, this list of conditions and the following disclaimer. |
| * |
| * - Redistributions in binary form must reproduce the above |
| * copyright notice, this list of conditions and the following |
| * disclaimer in the documentation and/or other materials provided |
| * with the distribution. |
| * |
| * - Neither the name of the Eclipse Foundation, Inc. nor the |
| * names of its contributors may be used to endorse or promote |
| * products derived from this software without specific prior |
| * written permission. |
| * |
| * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND |
| * CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, |
| * INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES |
| * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE |
| * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR |
| * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, |
| * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT |
| * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; |
| * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER |
| * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, |
| * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) |
| * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF |
| * ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. |
| */ |
| |
| package org.eclipse.jgit.util.io; |
| |
| import java.text.MessageFormat; |
| |
| import org.eclipse.jgit.internal.JGitText; |
| |
| /** |
| * Triggers an interrupt on the calling thread if it doesn't complete a block. |
| * <p> |
| * Classes can use this to trip an alarm interrupting the calling thread if it |
| * doesn't complete a block within the specified timeout. Typical calling |
| * pattern is: |
| * |
| * <pre> |
| * private InterruptTimer myTimer = ...; |
| * void foo() { |
| * try { |
| * myTimer.begin(timeout); |
| * // work |
| * } finally { |
| * myTimer.end(); |
| * } |
| * } |
| * </pre> |
| * <p> |
| * An InterruptTimer is not recursive. To implement recursive timers, |
| * independent InterruptTimer instances are required. A single InterruptTimer |
| * may be shared between objects which won't recursively call each other. |
| * <p> |
| * Each InterruptTimer spawns one background thread to sleep the specified time |
| * and interrupt the thread which called {@link #begin(int)}. It is up to the |
| * caller to ensure that the operations within the work block between the |
| * matched begin and end calls tests the interrupt flag (most IO operations do). |
| * <p> |
| * To terminate the background thread, use {@link #terminate()}. If the |
| * application fails to terminate the thread, it will (eventually) terminate |
| * itself when the InterruptTimer instance is garbage collected. |
| * |
| * @see TimeoutInputStream |
| */ |
| public final class InterruptTimer { |
| private final AlarmState state; |
| |
| private final AlarmThread thread; |
| |
| final AutoKiller autoKiller; |
| |
| /** |
| * Create a new timer with a default thread name. |
| */ |
| public InterruptTimer() { |
| this("JGit-InterruptTimer"); //$NON-NLS-1$ |
| } |
| |
| /** |
| * Create a new timer to signal on interrupt on the caller. |
| * <p> |
| * The timer thread is created in the calling thread's ThreadGroup. |
| * |
| * @param threadName |
| * name of the timer thread. |
| */ |
| public InterruptTimer(String threadName) { |
| state = new AlarmState(); |
| autoKiller = new AutoKiller(state); |
| thread = new AlarmThread(threadName, state); |
| thread.start(); |
| } |
| |
| /** |
| * Arm the interrupt timer before entering a blocking operation. |
| * |
| * @param timeout |
| * number of milliseconds before the interrupt should trigger. |
| * Must be > 0. |
| */ |
| public void begin(int timeout) { |
| if (timeout <= 0) |
| throw new IllegalArgumentException(MessageFormat.format( |
| JGitText.get().invalidTimeout, Integer.valueOf(timeout))); |
| Thread.interrupted(); |
| state.begin(timeout); |
| } |
| |
| /** |
| * Disable the interrupt timer, as the operation is complete. |
| */ |
| public void end() { |
| state.end(); |
| } |
| |
| /** |
| * Shutdown the timer thread, and wait for it to terminate. |
| */ |
| public void terminate() { |
| state.terminate(); |
| try { |
| thread.join(); |
| } catch (InterruptedException e) { |
| // |
| } |
| } |
| |
| static final class AlarmThread extends Thread { |
| AlarmThread(String name, AlarmState q) { |
| super(q); |
| setName(name); |
| setDaemon(true); |
| } |
| } |
| |
| // The trick here is, the AlarmThread does not have a reference to the |
| // AutoKiller instance, only the InterruptTimer itself does. Thus when |
| // the InterruptTimer is GC'd, the AutoKiller is also unreachable and |
| // can be GC'd. When it gets finalized, it tells the AlarmThread to |
| // terminate, triggering the thread to exit gracefully. |
| // |
| private static final class AutoKiller { |
| private final AlarmState state; |
| |
| AutoKiller(AlarmState s) { |
| state = s; |
| } |
| |
| @Override |
| protected void finalize() throws Throwable { |
| state.terminate(); |
| } |
| } |
| |
| static final class AlarmState implements Runnable { |
| private Thread callingThread; |
| |
| private long deadline; |
| |
| private boolean terminated; |
| |
| AlarmState() { |
| callingThread = Thread.currentThread(); |
| } |
| |
| @Override |
| public synchronized void run() { |
| while (!terminated && callingThread.isAlive()) { |
| try { |
| if (0 < deadline) { |
| final long delay = deadline - now(); |
| if (delay <= 0) { |
| deadline = 0; |
| callingThread.interrupt(); |
| } else { |
| wait(delay); |
| } |
| } else { |
| wait(1000); |
| } |
| } catch (InterruptedException e) { |
| // Treat an interrupt as notice to examine state. |
| } |
| } |
| } |
| |
| synchronized void begin(int timeout) { |
| if (terminated) |
| throw new IllegalStateException(JGitText.get().timerAlreadyTerminated); |
| callingThread = Thread.currentThread(); |
| deadline = now() + timeout; |
| notifyAll(); |
| } |
| |
| synchronized void end() { |
| if (0 == deadline) |
| Thread.interrupted(); |
| else |
| deadline = 0; |
| notifyAll(); |
| } |
| |
| synchronized void terminate() { |
| if (!terminated) { |
| deadline = 0; |
| terminated = true; |
| notifyAll(); |
| } |
| } |
| |
| private static long now() { |
| return System.currentTimeMillis(); |
| } |
| } |
| } |