// // ======================================================================== // Copyright (c) 1995-2017 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.util.thread; import java.io.Closeable; import java.util.concurrent.Callable; import java.util.concurrent.Executor; import java.util.concurrent.RejectedExecutionException; import org.eclipse.jetty.util.log.Log; import org.eclipse.jetty.util.log.Logger; /** * <p>A task (typically either a {@link Runnable} or {@link Callable} * that declares how it will behave when invoked:</p> * <ul> * <li>blocking, the invocation will certainly block (e.g. performs blocking I/O)</li> * <li>non-blocking, the invocation will certainly <strong>not</strong> block</li> * <li>either, the invocation <em>may</em> block</li> * </ul> * * <p> * Static methods and are provided that allow the current thread to be tagged * with a {@link ThreadLocal} to indicate if it has a blocking invocation type. * </p> * */ public interface Invocable { enum InvocationType { BLOCKING, NON_BLOCKING, EITHER } static ThreadLocal<Boolean> __nonBlocking = new ThreadLocal<Boolean>() { @Override protected Boolean initialValue() { return Boolean.FALSE; } }; /** * Test if the current thread has been tagged as non blocking * @return True if the task the current thread is running has * indicated that it will not block. */ public static boolean isNonBlockingInvocation() { return __nonBlocking.get(); } /** * Invoke a task with the calling thread, tagged to indicate * that it will not block. * @param task The task to invoke. */ public static void invokeNonBlocking(Runnable task) { // a Choice exists, so we must indicate NonBlocking Boolean was_non_blocking = __nonBlocking.get(); try { __nonBlocking.set(Boolean.TRUE); task.run(); } finally { __nonBlocking.set(was_non_blocking); } } /** * Invoke a task with the calling thread. * If the task is an {@link Invocable} of {@link InvocationType#EITHER} * then it is invoked with {@link #invokeNonBlocking(Runnable)}, to * indicate the type of invocation that has been assumed. * @param task The task to invoke. */ public static void invokePreferNonBlocking(Runnable task) { switch (getInvocationType(task)) { case BLOCKING: case NON_BLOCKING: task.run(); break; case EITHER: // a Choice exists, so we must indicate NonBlocking invokeNonBlocking(task); break; } } /** * Invoke a task with the calling thread. * If the task is an {@link Invocable} of {@link InvocationType#EITHER} * and the preferredInvocationType is not {@link InvocationType#BLOCKING} * then it is invoked with {@link #invokeNonBlocking(Runnable)}. * @param task The task to invoke. * @param preferredInvocationType The invocation type to use if the task * does not indicate a preference. */ public static void invokePreferred(Runnable task, InvocationType preferredInvocationType) { switch (getInvocationType(task)) { case BLOCKING: case NON_BLOCKING: task.run(); break; case EITHER: if (getInvocationType(task) == InvocationType.EITHER && preferredInvocationType == InvocationType.NON_BLOCKING) invokeNonBlocking(task); else task.run(); break; } } /** * wrap a task with the to indicate invocation type. * If the task is an {@link Invocable} of {@link InvocationType#EITHER} * and the preferredInvocationType is not {@link InvocationType#BLOCKING} * then it is wrapped with an invocation of {@link #invokeNonBlocking(Runnable)}. * otherwise the task itself is returned. * @param task The task to invoke. * @param preferredInvocationType The invocation type to use if the task * does not indicate a preference. * @return A Runnable that invokes the task in the declared or preferred type. */ public static Runnable asPreferred(Runnable task, InvocationType preferredInvocationType) { switch (getInvocationType(task)) { case BLOCKING: case NON_BLOCKING: break; case EITHER: if (preferredInvocationType == InvocationType.NON_BLOCKING) return () -> invokeNonBlocking(task); break; } return task; } /** * Get the invocation type of an Object. * @param o The object to check the invocation type of. * @return If the object is a {@link Invocable}, it is coerced and the {@link #getInvocationType()} * used, otherwise {@link InvocationType#BLOCKING} is returned. */ public static InvocationType getInvocationType(Object o) { if (o instanceof Invocable) return ((Invocable)o).getInvocationType(); return InvocationType.BLOCKING; } /** * @return The InvocationType of this object */ default InvocationType getInvocationType() { return InvocationType.BLOCKING; } /** * An Executor wrapper that knows about Invocable * */ public static class InvocableExecutor implements Executor { private static final Logger LOG = Log.getLogger(InvocableExecutor.class); private final Executor _executor; private final InvocationType _preferredInvocationForExecute; private final InvocationType _preferredInvocationForInvoke; public InvocableExecutor(Executor executor,InvocationType preferred) { this(executor,preferred,preferred); } public InvocableExecutor(Executor executor,InvocationType preferredInvocationForExecute,InvocationType preferredInvocationForIvoke) { _executor=executor; _preferredInvocationForExecute=preferredInvocationForExecute; _preferredInvocationForInvoke=preferredInvocationForIvoke; } public Invocable.InvocationType getPreferredInvocationType() { return _preferredInvocationForInvoke; } public void invoke(Runnable task) { if (LOG.isDebugEnabled()) LOG.debug("{} invoke {}", this, task); Invocable.invokePreferred(task,_preferredInvocationForInvoke); if (LOG.isDebugEnabled()) LOG.debug("{} invoked {}", this, task); } public void execute(Runnable task) { tryExecute(task,_preferredInvocationForExecute); } public void execute(Runnable task, InvocationType preferred) { tryExecute(task,preferred); } public boolean tryExecute(Runnable task) { return tryExecute(task,_preferredInvocationForExecute); } public boolean tryExecute(Runnable task, InvocationType preferred) { try { _executor.execute(Invocable.asPreferred(task,preferred)); return true; } catch(RejectedExecutionException e) { // If we cannot execute, then close the task LOG.debug(e); LOG.warn("Rejected execution of {}",task); try { if (task instanceof Closeable) ((Closeable)task).close(); } catch (Exception x) { e.addSuppressed(x); LOG.warn(e); } } return false; } } }