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

org.eclipse.jetty.util.thread.strategy.EatWhatYouKill Maven / Gradle / Ivy

There is a newer version: 12.0.13
Show newest version
//
// ========================================================================
// Copyright (c) 1995-2021 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.strategy;

import java.io.Closeable;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.atomic.LongAdder;

import org.eclipse.jetty.util.annotation.ManagedAttribute;
import org.eclipse.jetty.util.annotation.ManagedObject;
import org.eclipse.jetty.util.annotation.ManagedOperation;
import org.eclipse.jetty.util.component.ContainerLifeCycle;
import org.eclipse.jetty.util.thread.AutoLock;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.TryExecutor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * 

A strategy where the thread that produces will run the resulting task if it * is possible to do so without thread starvation.

* *

This strategy preemptively dispatches a thread as a pending producer, so that * when a thread produces a task it can immediately run the task and let the pending * producer thread take over production. When operating in this way, the sub-strategy * is called Execute Produce Consume (EPC).

*

However, if the task produced uses the {@link Invocable} API to indicate that * it will not block, then the strategy will run it directly, regardless of the * presence of a pending producer thread and then resume production after the * task has completed. When operating in this pattern, the sub-strategy is called * ProduceConsume (PC).

*

If there is no pending producer thread available and if the task has not * indicated it is non-blocking, then this strategy will dispatch the execution of * the task and immediately continue production. When operating in this pattern, the * sub-strategy is called ProduceExecuteConsume (PEC).

*/ @ManagedObject("eat what you kill execution strategy") public class EatWhatYouKill extends ContainerLifeCycle implements ExecutionStrategy, Runnable { private static final Logger LOG = LoggerFactory.getLogger(EatWhatYouKill.class); private enum State { IDLE, PRODUCING, REPRODUCING } /* The modes this strategy can work in */ private enum Mode { PRODUCE_CONSUME, PRODUCE_INVOKE_CONSUME, // This is PRODUCE_CONSUME an EITHER task with NON_BLOCKING invocation PRODUCE_EXECUTE_CONSUME, EXECUTE_PRODUCE_CONSUME // Eat What You Kill! } private final AutoLock _lock = new AutoLock(); private final LongAdder _pcMode = new LongAdder(); private final LongAdder _picMode = new LongAdder(); private final LongAdder _pecMode = new LongAdder(); private final LongAdder _epcMode = new LongAdder(); private final Producer _producer; private final Executor _executor; private final TryExecutor _tryExecutor; private State _state = State.IDLE; private boolean _pending; public EatWhatYouKill(Producer producer, Executor executor) { _producer = producer; _executor = executor; _tryExecutor = TryExecutor.asTryExecutor(executor); addBean(_producer); addBean(_tryExecutor); if (LOG.isDebugEnabled()) LOG.debug("{} created", this); } @Override public void dispatch() { boolean execute = false; try (AutoLock l = _lock.lock()) { switch (_state) { case IDLE: if (!_pending) { _pending = true; execute = true; } break; case PRODUCING: _state = State.REPRODUCING; break; default: break; } } if (LOG.isDebugEnabled()) LOG.debug("{} dispatch {}", this, execute); if (execute) _executor.execute(this); } @Override public void run() { tryProduce(true); } @Override public void produce() { tryProduce(false); } private void tryProduce(boolean wasPending) { if (LOG.isDebugEnabled()) LOG.debug("{} tryProduce {}", this, wasPending); try (AutoLock l = _lock.lock()) { if (wasPending) _pending = false; switch (_state) { case IDLE: // Enter PRODUCING _state = State.PRODUCING; break; case PRODUCING: // Keep other Thread producing _state = State.REPRODUCING; return; default: return; } } boolean nonBlocking = Invocable.isNonBlockingInvocation(); while (isRunning()) { try { if (doProduce(nonBlocking)) continue; return; } catch (Throwable th) { LOG.warn("Unable to produce", th); } } } private boolean doProduce(boolean nonBlocking) { Runnable task = produceTask(); if (task == null) { try (AutoLock l = _lock.lock()) { // Could another task just have been queued with a produce call? switch (_state) { case PRODUCING: _state = State.IDLE; return false; case REPRODUCING: _state = State.PRODUCING; return true; default: throw new IllegalStateException(toStringLocked()); } } } Mode mode; if (nonBlocking) { // The calling thread cannot block, so we only have a choice between PC and PEC modes, // based on the invocation type of the task switch (Invocable.getInvocationType(task)) { case NON_BLOCKING: mode = Mode.PRODUCE_CONSUME; break; case EITHER: mode = Mode.PRODUCE_INVOKE_CONSUME; break; default: mode = Mode.PRODUCE_EXECUTE_CONSUME; break; } } else { // The calling thread can block, so we can choose between PC, PEC and EPC modes, // based on the invocation type of the task and if a reserved thread is available switch (Invocable.getInvocationType(task)) { case NON_BLOCKING: mode = Mode.PRODUCE_CONSUME; break; case BLOCKING: // The task is blocking, so PC is not an option. Thus we choose // between EPC and PEC based on the availability of a reserved thread. try (AutoLock l = _lock.lock()) { if (_pending) { _state = State.IDLE; mode = Mode.EXECUTE_PRODUCE_CONSUME; } else if (_tryExecutor.tryExecute(this)) { _pending = true; _state = State.IDLE; mode = Mode.EXECUTE_PRODUCE_CONSUME; } else { mode = Mode.PRODUCE_EXECUTE_CONSUME; } } break; case EITHER: // The task may be non blocking, so PC is an option. Thus we choose // between EPC and PC based on the availability of a reserved thread. try (AutoLock l = _lock.lock()) { if (_pending) { _state = State.IDLE; mode = Mode.EXECUTE_PRODUCE_CONSUME; } else if (_tryExecutor.tryExecute(this)) { _pending = true; _state = State.IDLE; mode = Mode.EXECUTE_PRODUCE_CONSUME; } else { // PC mode, but we must consume with non-blocking invocation // as we may be the last thread and we cannot block mode = Mode.PRODUCE_INVOKE_CONSUME; } } break; default: throw new IllegalStateException(toString()); } } if (LOG.isDebugEnabled()) LOG.debug("{} m={} t={}/{}", this, mode, task, Invocable.getInvocationType(task)); // Consume or execute task switch (mode) { case PRODUCE_CONSUME: _pcMode.increment(); runTask(task); return true; case PRODUCE_INVOKE_CONSUME: _picMode.increment(); invokeTask(task); return true; case PRODUCE_EXECUTE_CONSUME: _pecMode.increment(); execute(task); return true; case EXECUTE_PRODUCE_CONSUME: _epcMode.increment(); runTask(task); // Try to produce again? try (AutoLock l = _lock.lock()) { if (_state == State.IDLE) { // We beat the pending producer, so we will become the producer instead _state = State.PRODUCING; return true; } } return false; default: throw new IllegalStateException(toString()); } } private void runTask(Runnable task) { try { task.run(); } catch (Throwable x) { LOG.warn("Task run failed", x); } } private void invokeTask(Runnable task) { try { Invocable.invokeNonBlocking(task); } catch (Throwable x) { LOG.warn("Task invoke failed", x); } } private Runnable produceTask() { try { return _producer.produce(); } catch (Throwable e) { LOG.warn("Task produce failed", e); return null; } } private void execute(Runnable task) { try { _executor.execute(task); } catch (RejectedExecutionException e) { if (isRunning()) LOG.warn("Execute failed", e); else LOG.trace("IGNORED", e); if (task instanceof Closeable) { try { ((Closeable)task).close(); } catch (Throwable e2) { LOG.trace("IGNORED", e2); } } } } @ManagedAttribute(value = "number of tasks consumed with PC mode", readonly = true) public long getPCTasksConsumed() { return _pcMode.longValue(); } @ManagedAttribute(value = "number of tasks executed with PIC mode", readonly = true) public long getPICTasksExecuted() { return _picMode.longValue(); } @ManagedAttribute(value = "number of tasks executed with PEC mode", readonly = true) public long getPECTasksExecuted() { return _pecMode.longValue(); } @ManagedAttribute(value = "number of tasks consumed with EPC mode", readonly = true) public long getEPCTasksConsumed() { return _epcMode.longValue(); } @ManagedAttribute(value = "whether this execution strategy is idle", readonly = true) public boolean isIdle() { try (AutoLock l = _lock.lock()) { return _state == State.IDLE; } } @ManagedOperation(value = "resets the task counts", impact = "ACTION") public void reset() { _pcMode.reset(); _epcMode.reset(); _pecMode.reset(); _picMode.reset(); } @Override public String toString() { try (AutoLock l = _lock.lock()) { return toStringLocked(); } } public String toStringLocked() { StringBuilder builder = new StringBuilder(); getString(builder); getState(builder); return builder.toString(); } private void getString(StringBuilder builder) { builder.append(getClass().getSimpleName()); builder.append('@'); builder.append(Integer.toHexString(hashCode())); builder.append('/'); builder.append(_producer); builder.append('/'); } private void getState(StringBuilder builder) { builder.append(_state); builder.append("/p="); builder.append(_pending); builder.append('/'); builder.append(_tryExecutor); builder.append("[pc="); builder.append(getPCTasksConsumed()); builder.append(",pic="); builder.append(getPICTasksExecuted()); builder.append(",pec="); builder.append(getPECTasksExecuted()); builder.append(",epc="); builder.append(getEPCTasksConsumed()); builder.append("]"); builder.append("@"); builder.append(DateTimeFormatter.ISO_OFFSET_DATE_TIME.format(ZonedDateTime.now())); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy