/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License, Version 1.0 only
* (the "License"). You may not use this file except in compliance
* with the License.
*
* You can obtain a copy of the license at legal-notices/CDDLv1_0.txt
* or http://forgerock.org/license/CDDLv1.0.html.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at legal-notices/CDDLv1_0.txt.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information:
* Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*
*
* Copyright 2010 Sun Microsystems, Inc.
* Portions copyright 2013-2014 ForgeRock AS.
*/
package com.forgerock.opendj.ldap;
import static com.forgerock.opendj.util.StaticUtils.DEBUG_LOG;
import static java.util.Collections.newSetFromMap;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.logging.Level;
import com.forgerock.opendj.util.ReferenceCountedObject;
/**
* Checks {@code TimeoutEventListener listeners} for events that have timed out.
*
* All listeners registered with the {@code #addListener()} method are called
* back with {@code TimeoutEventListener#handleTimeout()} to be able to handle
* the timeout.
*/
public final class TimeoutChecker {
/**
* Global reference on the timeout checker.
*/
public static final ReferenceCountedObject TIMEOUT_CHECKER =
new ReferenceCountedObject() {
@Override
protected void destroyInstance(final TimeoutChecker instance) {
instance.shutdown();
}
@Override
protected TimeoutChecker newInstance() {
return new TimeoutChecker();
}
};
/**
* Condition variable used for coordinating the timeout thread.
*/
private final Object stateLock = new Object();
/**
* The listener set must be safe from CMEs. For example, if the listener is
* a connection, expiring requests can cause the connection to be closed.
*/
private final Set listeners =
newSetFromMap(new ConcurrentHashMap());
/**
* Used to signal thread shutdown.
*/
private volatile boolean shutdownRequested = false;
/**
* Contains the minimum delay for listeners which were added while the
* timeout check was not sleeping (i.e. while it was processing listeners).
*/
private volatile long pendingListenerMinDelay = Long.MAX_VALUE;
private TimeoutChecker() {
final Thread checkerThread = new Thread("OpenDJ LDAP SDK Timeout Checker") {
@Override
public void run() {
DEBUG_LOG.fine("Timeout Checker Starting");
while (!shutdownRequested) {
/*
* New listeners may be added during iteration and may not
* be included in the computation of the new delay. This
* could potentially result in the timeout checker waiting
* longer than it should, or even forever (e.g. if the new
* listener is the first).
*/
final long currentTime = System.currentTimeMillis();
long delay = Long.MAX_VALUE;
pendingListenerMinDelay = Long.MAX_VALUE;
for (final TimeoutEventListener listener : listeners) {
if (DEBUG_LOG.isLoggable(Level.FINER)) {
DEBUG_LOG.finer("Checking listener " + listener + " delay = "
+ delay);
}
// May update the connections set.
final long newDelay = listener.handleTimeout(currentTime);
if (newDelay > 0) {
delay = Math.min(newDelay, delay);
}
}
try {
synchronized (stateLock) {
// Include any pending listener delays.
delay = Math.min(pendingListenerMinDelay, delay);
if (shutdownRequested) {
// Stop immediately.
break;
} else if (delay <= 0) {
/*
* If there is at least one connection then the
* delay should be > 0.
*/
stateLock.wait();
} else {
stateLock.wait(delay);
}
}
} catch (final InterruptedException e) {
shutdownRequested = true;
}
}
}
};
checkerThread.setDaemon(true);
checkerThread.start();
}
/**
* Registers a timeout event listener for timeout notification.
*
* @param listener
* The timeout event listener.
*/
public void addListener(final TimeoutEventListener listener) {
/*
* Only add the listener if it has a non-zero timeout. This assumes that
* the timeout is fixed.
*/
final long timeout = listener.getTimeout();
if (timeout > 0) {
listeners.add(listener);
synchronized (stateLock) {
pendingListenerMinDelay = Math.min(pendingListenerMinDelay, timeout);
stateLock.notifyAll();
}
}
}
/**
* Deregisters a timeout event listener for timeout notification.
*
* @param listener
* The timeout event listener.
*/
public void removeListener(final TimeoutEventListener listener) {
listeners.remove(listener);
// No need to signal.
}
private void shutdown() {
synchronized (stateLock) {
shutdownRequested = true;
stateLock.notifyAll();
}
}
}