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

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

There is a newer version: 12.1.0.alpha0
Show newest version
//
//  ========================================================================
//  Copyright (c) 1995-2022 Mort Bay Consulting Pty Ltd and others.
//  ------------------------------------------------------------------------
//  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.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

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;

/**
 * 

An implementation of a timeout that manages many {@link Expirable expirable} entities whose * timeouts are mostly cancelled or re-scheduled.

*

A typical scenario is for a parent entity to manage the timeouts of many children entities.

*

When a new entity is created, call {@link #schedule(Expirable)} with the new entity so that * this instance can be aware and manage the timeout of the new entity.

*

Eventually, this instance wakes up and iterates over the entities provided by {@link #iterator()}. * During the iteration, each entity:

*
    *
  • may never expire (see {@link Expirable#getExpireNanoTime()}; the entity is ignored
  • *
  • may be expired; {@link #onExpired(Expirable)} is called with that entity as parameter
  • *
  • may expire at a future time; the iteration records the earliest expiration time among * all non-expired entities
  • *
*

When the iteration is complete, this instance is re-scheduled with the earliest expiration time * calculated during the iteration.

* * @param the {@link Expirable} entity type * @see CyclicTimeout */ public abstract class CyclicTimeouts implements Destroyable { private static final Logger LOG = Log.getLogger(CyclicTimeouts.class); private final AtomicLong earliestTimeout = new AtomicLong(Long.MAX_VALUE); private final CyclicTimeout cyclicTimeout; public CyclicTimeouts(Scheduler scheduler) { cyclicTimeout = new Timeouts(scheduler); } /** * @return the entities to iterate over when this instance expires */ protected abstract Iterator iterator(); /** *

Invoked during the iteration when the given entity is expired.

*

This method may be invoked multiple times, and even concurrently, * for the same expirable entity and therefore the expiration of the * entity, if any, should be an idempotent action.

* * @param expirable the entity that is expired * @return whether the entity should be removed from the iterator via {@link Iterator#remove()} */ protected abstract boolean onExpired(T expirable); private void onTimeoutExpired() { if (LOG.isDebugEnabled()) LOG.debug("Timeouts check for {}", this); long now = System.nanoTime(); long earliest = Long.MAX_VALUE; // Reset the earliest timeout so we can expire again. // A concurrent call to schedule(long) may lose an // earliest value, but the corresponding entity will // be seen during the iteration below. earliestTimeout.set(earliest); Iterator iterator = iterator(); if (iterator == null) return; // Scan the entities to abort expired entities // and to find the entity that expires the earliest. while (iterator.hasNext()) { T expirable = iterator.next(); long expiresAt = expirable.getExpireNanoTime(); if (LOG.isDebugEnabled()) LOG.debug("Entity {} expires in {} ms for {}", expirable, TimeUnit.NANOSECONDS.toMillis(expiresAt - now), this); if (expiresAt == -1) continue; if (expiresAt <= now) { boolean remove = onExpired(expirable); if (LOG.isDebugEnabled()) LOG.debug("Entity {} expired, remove={} for {}", expirable, remove, this); if (remove) iterator.remove(); continue; } earliest = Math.min(earliest, expiresAt); } if (earliest < Long.MAX_VALUE) schedule(earliest); } /** *

Manages the timeout of a new entity.

* * @param expirable the new entity to manage the timeout for */ public void schedule(T expirable) { long expiresAt = expirable.getExpireNanoTime(); if (expiresAt < Long.MAX_VALUE) schedule(expiresAt); } private void schedule(long expiresAt) { // Schedule a timeout for the earliest entity that may expire. // When the timeout expires, scan the entities for the next // earliest entity that may expire, and reschedule a new timeout. long prevEarliest = earliestTimeout.getAndUpdate(t -> Math.min(t, expiresAt)); long expires = expiresAt; while (expires < prevEarliest) { // A new entity expires earlier than previous entities, schedule it. long delay = Math.max(0, expires - System.nanoTime()); if (LOG.isDebugEnabled()) LOG.debug("Scheduling timeout in {} ms for {}", TimeUnit.NANOSECONDS.toMillis(delay), this); schedule(cyclicTimeout, delay, TimeUnit.NANOSECONDS); // If we lost a race and overwrote a schedule() with an earlier time, then that earlier time // is remembered by earliestTimeout, in which case we will loop and set it again ourselves. prevEarliest = expires; expires = earliestTimeout.get(); } } @Override public void destroy() { cyclicTimeout.destroy(); } boolean schedule(CyclicTimeout cyclicTimeout, long delay, TimeUnit unit) { return cyclicTimeout.schedule(delay, unit); } /** *

An entity that may expire.

*/ public interface Expirable { /** *

Returns the expiration time in nanoseconds.

*

The value to return must be calculated taking into account {@link System#nanoTime()}, * for example:

* {@code expireNanoTime = System.nanoTime() + timeoutNanos} *

Returning {@link Long#MAX_VALUE} indicates that this entity does not expire.

* * @return the expiration time in nanoseconds, or {@link Long#MAX_VALUE} if this entity does not expire */ public long getExpireNanoTime(); } private class Timeouts extends CyclicTimeout { private Timeouts(Scheduler scheduler) { super(scheduler); } @Override public void onTimeoutExpired() { CyclicTimeouts.this.onTimeoutExpired(); } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy