com.microsoft.tfs.util.thread.ThreadInterruptTimer.java Source code

Java tutorial

Introduction

Here is the source code for com.microsoft.tfs.util.thread.ThreadInterruptTimer.java

Source

// Copyright (c) Microsoft. All rights reserved.
// Licensed under the MIT license. See License.txt in the repository root.

package com.microsoft.tfs.util.thread;

import java.text.MessageFormat;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;

import com.microsoft.tfs.util.Check;

/**
 * {@link ThreadInterruptTimer} is a {@link Thread} that can interrupt another
 * thread after a period of time has elapsed without a specific kind of
 * communication to the instance.
 * <p>
 * To use it, one (original) thread creates an instance of
 * {@link ThreadInterruptTimer} passing it a timeout and a reference it its own
 * thread, then starts it. The original thread periodically calls
 * {@link ThreadInterruptTimer#reset()} to reset the internal timer (to postpone
 * interruption). If the original thread does not call {@link #reset()} for a
 * time exceeding the timeout given on construction, the
 * {@link ThreadInterruptTimer} instance invokes {@link Thread#interrupt()} once
 * on the original thread and completes.
 * <p>
 * The timeout check is done using a low-resolution sleep loop, accurate to
 * better than {@link #TIMEOUT_CHECK_PERIOD_SECONDS} seconds.
 */
public class ThreadInterruptTimer extends Thread {
    private static final Log log = LogFactory.getLog(ThreadInterruptTimer.class);

    /**
     * {@link ThreadInterruptTimer} sleeps this many seconds after computing and
     * checking its internal idle value. Interruption can be delayed as much as
     * this value (in seconds) from the actual desired timeout.
     */
    public final static int TIMEOUT_CHECK_PERIOD_SECONDS = 2;

    /**
     * The last time we were poked to keep awake (in milliseconds since epoch).
     */
    private long lastReset;

    /**
     * How many seconds we can be idle (time since we were reset) before
     * triggering shutdown.
     */
    private int interruptTimeoutSeconds;

    /**
     * The thread we interrupt when the idle period is exceeded.
     */
    final Thread threadToInterrupt;

    /**
     * Create a {@link ThreadInterruptTimer} with the given idle timeout in
     * seconds that interrupts the given thread when the timeout is reached.
     *
     * @param interruptTimeoutSeconds
     *        time in seconds after which a {@link ThreadInterruptTimer} that
     *        has not been {@link #reset()} since its creation or the last
     *        {@link #reset()} will interrupt the given thread. See the class's
     *        Javadoc for timer accuracy notes. Must be positive.
     * @param threadToInterrupt
     *        the thread to interrupt (via {@link Thread#interrupt()} after the
     *        given timeout. Not null.
     */
    public ThreadInterruptTimer(final int interruptTimeoutSeconds, final Thread threadToInterrupt) {
        super("Thread Interrupt Timer"); //$NON-NLS-1$

        Check.isTrue(interruptTimeoutSeconds > 0, "interruptTimoutSeconds must be positive"); //$NON-NLS-1$
        Check.notNull(threadToInterrupt, "threadToInterrupt"); //$NON-NLS-1$

        synchronized (this) {
            this.interruptTimeoutSeconds = interruptTimeoutSeconds;
            this.threadToInterrupt = threadToInterrupt;
        }
    }

    /**
     * Resets the timer's internal idle counter, postponing thread interruption
     * until the timeout is reached again.
     */
    public void reset() {
        final long now = System.currentTimeMillis();

        final String messageFormat = "Resetting idle at {0}"; //$NON-NLS-1$
        final String message = MessageFormat.format(messageFormat, now);
        log.trace(message);

        synchronized (this) {
            lastReset = now;
        }
    }

    /*
     * (non-Javadoc)
     *
     * @see java.lang.Thread#run()
     */
    @Override
    public void run() {
        reset();

        while (true) {
            long lastResetCopy;
            long interruptTimeoutSecondsCopy;

            synchronized (this) {
                lastResetCopy = lastReset;
                interruptTimeoutSecondsCopy = interruptTimeoutSeconds;
            }

            final long idleMilliseconds = System.currentTimeMillis() - lastResetCopy;

            if (idleMilliseconds > interruptTimeoutSecondsCopy * 1000) {
                break;
            }

            final String messageFormat = "Idle for an acceptable {0} milliseconds out of {1}"; //$NON-NLS-1$
            final String message = MessageFormat.format(messageFormat, idleMilliseconds,
                    interruptTimeoutSecondsCopy * 1000);
            log.trace(message);

            try {
                Thread.sleep(TIMEOUT_CHECK_PERIOD_SECONDS * 1000);
            } catch (final InterruptedException e) {
                break;
            }
        }

        /*
         * To avoid deadlock, we don't hold a lock on this object while
         * interrupting. The implementation of interrupt on the other thread may
         * call back into this class (though there's probably no reason to).
         */
        Thread interruptThisThread;
        synchronized (this) {
            interruptThisThread = threadToInterrupt;
        }

        final String messageFormat = "Timeout period of {0} seconds exceeded, interrupting {1}"; //$NON-NLS-1$
        final String message = MessageFormat.format(messageFormat, interruptTimeoutSeconds,
                interruptThisThread.toString());
        log.debug(message);

        interruptThisThread.interrupt();
    }
}