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 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.Iterator;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicLong;

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;

/**
 * 

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 = LoggerFactory.getLogger(CyclicTimeouts.class); private final AtomicLong earliestNanoTime = 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.

*

When {@code false} is returned, the implementation should adjust * the {@link Expirable} expiration, so that a call to * {@link Expirable#getExpireNanoTime()} after this method has returned * yields a new expiration nanoTime.

* * @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 = NanoTime.now(); long earliest = Long.MAX_VALUE; // Move the earliest timeout far in the future, 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. earliestNanoTime.set(now + Long.MAX_VALUE); 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 (expiresAt == Long.MAX_VALUE) { if (LOG.isDebugEnabled()) LOG.debug("Entity {} does not expire for {}", expirable, this); continue; } if (LOG.isDebugEnabled()) LOG.debug("Entity {} expires in {} ms for {}", expirable, NanoTime.millisElapsed(now, expiresAt), this); if (NanoTime.isBeforeOrSame(expiresAt, now)) { boolean remove = onExpired(expirable); if (LOG.isDebugEnabled()) LOG.debug("Entity {} expired, remove={} for {}", expirable, remove, this); if (remove) { iterator.remove(); continue; } long newExpiresAt = expirable.getExpireNanoTime(); if (newExpiresAt == expiresAt) continue; expiresAt = newExpiresAt; } earliest = Math.min(earliest, NanoTime.elapsed(now, expiresAt)); } if (earliest != Long.MAX_VALUE) schedule(now + 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 = earliestNanoTime.getAndUpdate(t -> NanoTime.isBefore(t, expiresAt) ? t : expiresAt); long expires = expiresAt; while (NanoTime.isBefore(expires, prevEarliest)) { // A new entity expires earlier than previous entities, schedule it. long delay = Math.max(0, NanoTime.until(expires)); 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 = earliestNanoTime.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 the current nanoTime, * for example:

* {@code expireNanoTime = NanoTime.now() + 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