org.eclipse.jetty.io.CyclicTimeout Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of ehcache Show documentation
Show all versions of ehcache Show documentation
Ehcache is an open source, standards-based cache used to boost performance,
offload the database and simplify scalability. Ehcache is robust, proven and full-featured and
this has made it the most widely-used Java-based cache.
//
// ========================================================================
// Copyright (c) 1995-2018 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.atomic.AtomicReference;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Scheduler;
import static java.lang.Long.MAX_VALUE;
/**
* An abstract implementation of a timeout.
* Subclasses should implement {@link #onTimeoutExpired()}.
* This implementation is optimised assuming that the timeout
* will mostly be cancelled and then reused with a similar value.
*/
public abstract class CyclicTimeout implements Destroyable
{
private static final Logger LOG = Log.getLogger(CyclicTimeout.class);
private static final Timeout NOT_SET = new Timeout(MAX_VALUE, null);
private static final Scheduler.Task DESTROYED = () -> false;
/* The underlying scheduler to use */
private final Scheduler _scheduler;
/* Reference to the current Timeout and chain of Wakeup */
private final AtomicReference _timeout = new AtomicReference<>(NOT_SET);
/**
* @param scheduler A scheduler used to schedule wakeups
*/
public CyclicTimeout(Scheduler scheduler)
{
_scheduler = scheduler;
}
public Scheduler getScheduler()
{
return _scheduler;
}
/**
* Schedules a timeout, even if already set, cancelled or expired.
*
* @param delay The period of time before the timeout expires.
* @param units The unit of time of the period.
* @return true if the timer was already set.
*/
public boolean schedule(long delay, TimeUnit units)
{
long now = System.nanoTime();
long new_timeout_at = now + units.toNanos(delay);
boolean result;
Wakeup new_wakeup;
while (true)
{
Timeout timeout = _timeout.get();
new_wakeup = null;
result = timeout._at != MAX_VALUE;
// Is the current wakeup good to use? ie before our timeout time?
Wakeup wakeup = timeout._wakeup;
if (wakeup == null || wakeup._at > new_timeout_at)
// No, we need an earlier wakeup.
wakeup = new_wakeup = new Wakeup(new_timeout_at, wakeup);
if (_timeout.compareAndSet(timeout, new Timeout(new_timeout_at, wakeup)))
{
if (LOG.isDebugEnabled())
LOG.debug("Installed timeout in {} ms, waking up in {} ms",
units.toMillis(delay),
TimeUnit.NANOSECONDS.toMillis(wakeup._at - now));
break;
}
}
// If we created a new wakeup, we need to actually schedule it.
// Any wakeup that is created and discarded by the failed CAS will not be
// in the wakeup chain, will not have a scheduler task set and will be GC'd.
if (new_wakeup != null)
new_wakeup.schedule(now);
return result;
}
/**
* Cancels this CyclicTimeout so that it won't expire.
* After being cancelled, this CyclicTimeout can be scheduled again.
*
* @return true if this CyclicTimeout was scheduled to expire
* @see #destroy()
*/
public boolean cancel()
{
boolean result;
Timeout timeout;
Timeout new_timeout;
while (true)
{
timeout = _timeout.get();
result = timeout._at != MAX_VALUE;
Wakeup wakeup = timeout._wakeup;
new_timeout = wakeup == null ? NOT_SET : new Timeout(MAX_VALUE, wakeup);
if (_timeout.compareAndSet(timeout, new_timeout))
break;
}
return result;
}
/**
* Invoked when the timeout expires.
*/
public abstract void onTimeoutExpired();
/**
* Destroys this CyclicTimeout.
* After being destroyed, this CyclicTimeout is not used anymore.
*/
@Override
public void destroy()
{
Timeout timeout = _timeout.getAndSet(NOT_SET);
Wakeup wakeup = timeout == null ? null : timeout._wakeup;
while (wakeup != null)
{
wakeup.destroy();
wakeup = wakeup._next;
}
}
/**
* A timeout time with a link to a Wakeup chain.
*/
private static class Timeout
{
private final long _at;
private final Wakeup _wakeup;
private Timeout(long timeoutAt, Wakeup wakeup)
{
_at = timeoutAt;
_wakeup = wakeup;
}
@Override
public String toString()
{
return String.format("%s@%x:%d,%s", getClass().getSimpleName(), hashCode(), _at, _wakeup);
}
}
/**
* A Wakeup chain of real scheduler tasks.
*/
private class Wakeup implements Runnable
{
private final AtomicReference _task = new AtomicReference<>();
private final long _at;
private final Wakeup _next;
private Wakeup(long wakeupAt, Wakeup next)
{
_at = wakeupAt;
_next = next;
}
private void schedule(long now)
{
_task.compareAndSet(null, _scheduler.schedule(this, _at - now, TimeUnit.NANOSECONDS));
}
private void destroy()
{
Scheduler.Task task = _task.getAndSet(DESTROYED);
if (task != null)
task.cancel();
}
@Override
public void run()
{
long now;
Wakeup new_wakeup;
boolean has_expired;
while (true)
{
Timeout timeout = _timeout.get();
// We must look for ourselves in the current wakeup list.
// If we find ourselves, then we act and we use our tail for any new
// wakeup list, effectively removing any wakeup before us in the list (and making them no-ops).
// If we don't find ourselves, then a wakeup that should have expired after us has already run
// and removed us from the list, so we become a noop.
Wakeup wakeup = timeout._wakeup;
while (wakeup != null)
{
if (wakeup == this)
break;
// Not us, so look at next wakeup in the list.
wakeup = wakeup._next;
}
if (wakeup == null)
// Not found, we become a noop.
return;
now = System.nanoTime();
new_wakeup = null;
has_expired = false;
Timeout new_timeout;
// We are in the wakeup list! So we have to act and we know our
// tail has not expired (else it would have removed us from the list).
// Remove ourselves (and any prior Wakeup) from the wakeup list.
wakeup = wakeup._next;
if (timeout._at <= now)
{
// We have timed out!
has_expired = true;
new_timeout = wakeup == null ? NOT_SET : new Timeout(MAX_VALUE, wakeup);
}
else if (timeout._at != MAX_VALUE)
{
// We have not timed out, but we are set to!
// Is the current wakeup good to use? ie before our timeout time?
if (wakeup == null || wakeup._at >= timeout._at)
// No, we need an earlier wakeup.
wakeup = new_wakeup = new Wakeup(timeout._at, wakeup);
new_timeout = new Timeout(timeout._at, wakeup);
}
else
{
// We don't timeout, preserve scheduled chain.
new_timeout = wakeup == null ? NOT_SET : new Timeout(MAX_VALUE, wakeup);
}
// Loop until we succeed in changing state or we are a noop!
if (_timeout.compareAndSet(timeout, new_timeout))
break;
}
// If we created a new wakeup, we need to actually schedule it.
if (new_wakeup != null)
new_wakeup.schedule(now);
// If we expired, then do the callback.
if (has_expired)
onTimeoutExpired();
}
@Override
public String toString()
{
return String.format("%s@%x:%d->%s", getClass().getSimpleName(), hashCode(), _at, _next);
}
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy