All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.eclipse.jetty.io.CyclicTimeout Maven / Gradle / Ivy

The newest version!
//
//  ========================================================================
//  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 - 2024 Weber Informatics LLC | Privacy Policy