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

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

There is a newer version: 12.1.0.alpha0
Show newest version
//
// ========================================================================
// Copyright (c) 1995 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under the
// terms of the Eclipse Public License v. 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0, or the Apache License, Version 2.0
// which is available at https://www.apache.org/licenses/LICENSE-2.0.
//
// SPDX-License-Identifier: EPL-2.0 OR Apache-2.0
// ========================================================================
//

package org.eclipse.jetty.io;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.util.NanoTime;
import org.eclipse.jetty.util.component.Destroyable;
import org.eclipse.jetty.util.thread.Scheduler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

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.

*

The typical scenario to use this class is when you have events * that postpone (by re-scheduling), or cancel then re-schedule, a * timeout for a single entity. * For example: connection idleness, where for each connection there * is a CyclicTimeout and a read/write postpones the timeout; when * the timeout expires, the implementation checks against a timestamp * if the connection is really idle. * Another example: HTTP session expiration, where for each HTTP * session there is a CyclicTimeout and at the beginning of the * request processing the timeout is canceled (via cancel()), but at * the end of the request processing the timeout is re-scheduled.

*

This implementation has a {@link Timeout} holding the time * at which the scheduled task should fire, and a linked list of * {@link Wakeup}, each holding the actual scheduled task.

*

Calling {@link #schedule(long, TimeUnit)} the first time will * create a Timeout with an associated Wakeup and submit a task to * the scheduler. * Calling {@link #schedule(long, TimeUnit)} again with the same or * a larger delay will cancel the previous Timeout, but keep the * previous Wakeup without submitting a new task to the scheduler, * therefore reducing the pressure on the scheduler and avoid it * becomes a bottleneck. * When the Wakeup task fires, it will see that the Timeout is now * in the future and will attach a new Wakeup with the future time * to the Timeout, and submit a scheduler task for the new Wakeup.

* * @see CyclicTimeouts */ public abstract class CyclicTimeout implements Destroyable { private static final Logger LOG = LoggerFactory.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.

*

If a timeout is already set, it will be cancelled and replaced * by the new one.

* * @param delay The period of time before the timeout expires. * @param units The unit of time of the period. * @return true if the timeout was already set. */ public boolean schedule(long delay, TimeUnit units) { long now = NanoTime.now(); long newTimeoutAt = now + units.toNanos(delay); Wakeup newWakeup = null; boolean result; while (true) { Timeout timeout = _timeout.get(); result = timeout._at != MAX_VALUE; // Is the current wakeup good to use? ie before our timeout time? Wakeup wakeup = timeout._wakeup; if (wakeup == null || NanoTime.isBefore(newTimeoutAt, wakeup._at)) // No, we need an earlier wakeup. wakeup = newWakeup = new Wakeup(newTimeoutAt, wakeup); if (_timeout.compareAndSet(timeout, new Timeout(newTimeoutAt, wakeup))) { if (LOG.isDebugEnabled()) { LOG.debug("Installed timeout in {} ms, {} wake up in {} ms", units.toMillis(delay), newWakeup != null ? "new" : "existing", NanoTime.millisElapsed(now, wakeup._at)); } 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 (newWakeup != null) newWakeup.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; while (true) { Timeout timeout = _timeout.get(); result = timeout._at != MAX_VALUE; Wakeup wakeup = timeout._wakeup; Timeout newTimeout = wakeup == null ? NOT_SET : new Timeout(MAX_VALUE, wakeup); if (_timeout.compareAndSet(timeout, newTimeout)) 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:%dms,%s", getClass().getSimpleName(), hashCode(), NanoTime.millisUntil(_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, NanoTime.elapsed(now, _at), TimeUnit.NANOSECONDS)); } private void destroy() { Scheduler.Task task = _task.getAndSet(DESTROYED); if (task != null) task.cancel(); } @Override public void run() { long now = NanoTime.now(); Wakeup newWakeup = null; boolean hasExpired = false; 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; // 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; Timeout newTimeout; if (NanoTime.isBeforeOrSame(timeout._at, now)) { // We have timed out! hasExpired = true; newTimeout = 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 || NanoTime.isBefore(timeout._at, wakeup._at)) // No, we need an earlier wakeup. wakeup = newWakeup = new Wakeup(timeout._at, wakeup); newTimeout = new Timeout(timeout._at, wakeup); } else { // We don't timeout, preserve scheduled chain. newTimeout = 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, newTimeout)) break; } // If we created a new wakeup, we need to actually schedule it. if (newWakeup != null) newWakeup.schedule(now); // If we expired, then do the callback. if (hasExpired) onTimeoutExpired(); } @Override public String toString() { return String.format("%s@%x:%dms->%s", getClass().getSimpleName(), hashCode(), _at == MAX_VALUE ? _at : NanoTime.millisUntil(_at), _next); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy