--- /dev/null
+//
+// ========================================================================
+// Copyright (c) 1995-2014 Mort Bay Consulting Pty. Ltd.
+// ------------------------------------------------------------------------
+// All rights reserved. This program and the accompanying materials
+// are made available under the terms of the Eclipse Public License v1.0
+// and Apache License v2.0 which accompanies this distribution.
+//
+// The Eclipse Public License is available at
+// http://www.eclipse.org/legal/epl-v10.html
+//
+// The Apache License v2.0 is available at
+// http://www.opensource.org/licenses/apache2.0.php
+//
+// You may elect to redistribute this code under either of these licenses.
+// ========================================================================
+//
+
+package org.eclipse.jetty.io;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+import java.util.concurrent.atomic.AtomicReference;
+
+import org.eclipse.jetty.util.log.Log;
+import org.eclipse.jetty.util.log.Logger;
+import org.eclipse.jetty.util.thread.Scheduler;
+
+/**
+ * An Abstract implementation of an Idle Timeout.
+ * <p/>
+ * This implementation is optimised that timeout operations are not cancelled on
+ * every operation. Rather timeout are allowed to expire and a check is then made
+ * to see when the last operation took place. If the idle timeout has not expired,
+ * the timeout is rescheduled for the earliest possible time a timeout could occur.
+ */
+public abstract class IdleTimeout
+{
+ private static final Logger LOG = Log.getLogger(IdleTimeout.class);
+ private final Scheduler _scheduler;
+ private final AtomicReference<Scheduler.Task> _timeout = new AtomicReference<>();
+ private volatile long _idleTimeout;
+ private volatile long _idleTimestamp = System.currentTimeMillis();
+
+ private final Runnable _idleTask = new Runnable()
+ {
+ @Override
+ public void run()
+ {
+ long idleLeft = checkIdleTimeout();
+ if (idleLeft >= 0)
+ scheduleIdleTimeout(idleLeft > 0 ? idleLeft : getIdleTimeout());
+ }
+ };
+
+ /**
+ * @param scheduler A scheduler used to schedule checks for the idle timeout.
+ */
+ public IdleTimeout(Scheduler scheduler)
+ {
+ _scheduler = scheduler;
+ }
+
+ public long getIdleTimestamp()
+ {
+ return _idleTimestamp;
+ }
+
+ public long getIdleTimeout()
+ {
+ return _idleTimeout;
+ }
+
+ public void setIdleTimeout(long idleTimeout)
+ {
+ long old = _idleTimeout;
+ _idleTimeout = idleTimeout;
+
+ // Do we have an old timeout
+ if (old > 0)
+ {
+ // if the old was less than or equal to the new timeout, then nothing more to do
+ if (old <= idleTimeout)
+ return;
+
+ // old timeout is too long, so cancel it.
+ deactivate();
+ }
+
+ // If we have a new timeout, then check and reschedule
+ if (isOpen())
+ activate();
+ }
+
+ /**
+ * This method should be called when non-idle activity has taken place.
+ */
+ public void notIdle()
+ {
+ _idleTimestamp = System.currentTimeMillis();
+ }
+
+ private void scheduleIdleTimeout(long delay)
+ {
+ Scheduler.Task newTimeout = null;
+ if (isOpen() && delay > 0 && _scheduler != null)
+ newTimeout = _scheduler.schedule(_idleTask, delay, TimeUnit.MILLISECONDS);
+ Scheduler.Task oldTimeout = _timeout.getAndSet(newTimeout);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ public void onOpen()
+ {
+ activate();
+ }
+
+ private void activate()
+ {
+ if (_idleTimeout > 0)
+ _idleTask.run();
+ }
+
+ public void onClose()
+ {
+ deactivate();
+ }
+
+ private void deactivate()
+ {
+ Scheduler.Task oldTimeout = _timeout.getAndSet(null);
+ if (oldTimeout != null)
+ oldTimeout.cancel();
+ }
+
+ protected long checkIdleTimeout()
+ {
+ if (isOpen())
+ {
+ long idleTimestamp = getIdleTimestamp();
+ long idleTimeout = getIdleTimeout();
+ long idleElapsed = System.currentTimeMillis() - idleTimestamp;
+ long idleLeft = idleTimeout - idleElapsed;
+
+ LOG.debug("{} idle timeout check, elapsed: {} ms, remaining: {} ms", this, idleElapsed, idleLeft);
+
+ if (idleTimestamp != 0 && idleTimeout > 0)
+ {
+ if (idleLeft <= 0)
+ {
+ LOG.debug("{} idle timeout expired", this);
+ try
+ {
+ onIdleExpired(new TimeoutException("Idle timeout expired: " + idleElapsed + "/" + idleTimeout + " ms"));
+ }
+ finally
+ {
+ notIdle();
+ }
+ }
+ }
+
+ return idleLeft >= 0 ? idleLeft : 0;
+ }
+ return -1;
+ }
+
+ /**
+ * This abstract method is called when the idle timeout has expired.
+ *
+ * @param timeout a TimeoutException
+ */
+ protected abstract void onIdleExpired(TimeoutException timeout);
+
+ /**
+ * This abstract method should be called to check if idle timeouts
+ * should still be checked.
+ *
+ * @return True if the entity monitored should still be checked for idle timeouts
+ */
+ public abstract boolean isOpen();
+}