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

org.eclipse.jetty.util.thread.SerializedInvoker Maven / Gradle / Ivy

The 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.util.thread;

import java.io.IOException;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicReference;

import org.eclipse.jetty.util.component.Dumpable;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * Ensures serial invocation of submitted tasks.
 * 

* The {@link InvocationType} of the {@link Runnable} returned from this class is * always {@link InvocationType#BLOCKING} since a blocking task may be added to the queue * at any time. *

* This class was inspired by the public domain class * NonBlockingMutexExecutor *

*/ public class SerializedInvoker { private static final Logger LOG = LoggerFactory.getLogger(SerializedInvoker.class); private final AtomicReference _tail = new AtomicReference<>(); private final String _name; private final Executor _executor; private volatile Thread _invokerThread; /** * Creates a new instance whose name is {@code anonymous}. */ public SerializedInvoker() { this("anonymous"); } /** * Creates a new instance whose name is derived from the given class. * @param nameFrom the class to use as a name. */ public SerializedInvoker(Class nameFrom) { this(nameFrom.getSimpleName()); } /** * Creates a new instance with the given name. * @param name the name. */ public SerializedInvoker(String name) { this(name, null); } /** * Creates a new instance with the given name and {@link Executor}. * * @param name the name * @param executor the {@link Executor} */ public SerializedInvoker(String name, Executor executor) { _name = name; _executor = executor; } /** * @return whether the current thread is currently executing a task using this invoker */ boolean isCurrentThreadInvoking() { return _invokerThread == Thread.currentThread(); } /** * @throws IllegalStateException when the current thread is not currently executing a task using this invoker */ public void assertCurrentThreadInvoking() throws IllegalStateException { if (!isCurrentThreadInvoking()) throw new IllegalStateException(); } /** * Arrange for a task to be invoked, mutually excluded from other tasks. * @param task The task to invoke * @return A Runnable that must be called to invoke the passed task and possibly other tasks. Null if the * task will be invoked by another caller. */ public Runnable offer(Runnable task) { if (task == null) { if (LOG.isDebugEnabled()) LOG.debug("Offering task null, skipping it in {}", this); return null; } // The NamedRunnable logger is checked to make it possible to enable the nice task names in a debugger // while not flooding the logs nor stderr with logging statements just by adding // org.eclipse.jetty.util.thread.SerializedInvoker$NamedRunnable.LEVEL=DEBUG to jetty-logging.properties. if (NamedRunnable.LOG.isDebugEnabled()) { if (!(task instanceof NamedRunnable)) { // Wrap the given task with another one that's going to delegate run() to the wrapped task while the // wrapper's toString() returns a description of the place in code where SerializedInvoker.run() was called. task = new NamedRunnable(task); } } Link link = new Link(task); if (LOG.isDebugEnabled()) LOG.debug("Offering link {} of {}", link, this); Link penultimate = _tail.getAndSet(link); if (penultimate == null) return link; penultimate._next.lazySet(link); return null; } /** * Arrange for tasks to be invoked, mutually excluded from other tasks. * @param tasks The tasks to invoke * @return A Runnable that must be called to invoke the passed tasks and possibly other tasks. Null if the * tasks will be invoked by another caller. */ public Runnable offer(Runnable... tasks) { if (LOG.isDebugEnabled()) LOG.debug("Offering {} tasks in {}", tasks.length, this); Runnable runnable = null; for (Runnable task : tasks) { if (runnable == null) runnable = offer(task); else offer(task); } return runnable; } /** * Arrange for a task to be run, mutually excluded from other tasks. * This is equivalent to directly running any {@link Runnable} returned from {@link #offer(Runnable)} * @param task The task to invoke */ public void run(Runnable task) { Runnable todo = offer(task); if (todo != null) todo.run(); else if (LOG.isDebugEnabled()) LOG.debug("Queued link in {}", this); } /** * Arrange for tasks to be executed, mutually excluded from other tasks. * This is equivalent to directly running any {@link Runnable} returned from {@link #offer(Runnable...)} * @param tasks The tasks to invoke */ public void run(Runnable... tasks) { Runnable todo = offer(tasks); if (todo != null) todo.run(); else if (LOG.isDebugEnabled()) LOG.debug("Queued links in {}", this); } @Override public String toString() { return String.format("%s@%x{name=%s,tail=%s,invoker=%s}", getClass().getSimpleName(), hashCode(), _name, _tail, _invokerThread); } protected void onError(Runnable task, Throwable t) { LOG.warn("Serialized invocation error", t); } private class Link extends Invocable.ReadyTask implements Dumpable { private final AtomicReference _next = new AtomicReference<>(); public Link(Runnable task) { super(Invocable.getInvocationType(task), task); } @Override public void dump(Appendable out, String indent) throws IOException { Runnable task = getTask(); if (task instanceof NamedRunnable nr) { StringWriter sw = new StringWriter(); nr.stack.printStackTrace(new PrintWriter(sw)); Dumpable.dumpObjects(out, indent, nr.toString(), sw.toString()); } else { Dumpable.dumpObjects(out, indent, task); } Link link = _next.get(); if (link != null) link.dump(out, indent); } Link next() { // Are we the current the last Link? if (_tail.compareAndSet(this, null)) return null; // not the last task, so its next link will eventually be set while (true) { Link next = _next.get(); if (next != null) return next; Thread.onSpinWait(); } } @Override public void run() { Link link = this; InvocationType firstInvocationType = link.getInvocationType(); while (link != null) { if (LOG.isDebugEnabled()) LOG.debug("Running link {} of {}", link, SerializedInvoker.this); Runnable task = link.getTask(); InvocationType currentInvocationType = link.getInvocationType(); if (currentInvocationType == InvocationType.BLOCKING && firstInvocationType != InvocationType.BLOCKING) { // Cannot run a BLOCKING task after a NON_BLOCKING one, // dispatch the current task and exit the iteration. if (_executor != null) { _executor.execute(link); return; } } _invokerThread = Thread.currentThread(); try { task.run(); } catch (Throwable t) { if (LOG.isDebugEnabled()) LOG.debug("Failed while running link {} of {}", link, SerializedInvoker.this, t); onError(task, t); } finally { // _invokerThread must be nulled before calling link.next() as // once the latter has executed, another thread can enter Link.run(). _invokerThread = null; } link = link.next(); if (link == null && LOG.isDebugEnabled()) LOG.debug("Next link is null, execution is over in {}", SerializedInvoker.this); } } @Override public String toString() { return String.format("%s@%x{%s[%s] -> %s}", getClass().getSimpleName(), hashCode(), getTask(), getInvocationType(), _next); } } private class NamedRunnable extends Invocable.ReadyTask { private static final Logger LOG = LoggerFactory.getLogger(NamedRunnable.class); private final String name; private final Throwable stack; private NamedRunnable(Runnable delegate) { super(Invocable.getInvocationType(delegate), delegate); this.stack = new Throwable(); this.name = deriveTaskName(delegate, stack); } private String deriveTaskName(Runnable task, Throwable stack) { StackTraceElement[] stackTrace = stack.getStackTrace(); for (StackTraceElement stackTraceElement : stackTrace) { String className = stackTraceElement.getClassName(); if (!className.equals(SerializedInvoker.class.getName()) && !className.equals(SerializedInvoker.this.getClass().getName()) && !className.equals(getClass().getName())) return "Queued by " + Thread.currentThread().getName() + " at " + stackTraceElement; } return task.toString(); } @Override public String toString() { return name; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy