//
// ========================================================================
// 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.strategy;
import java.util.concurrent.Executor;
import java.util.concurrent.locks.Condition;
import org.eclipse.jetty.util.component.AbstractLifeCycle;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ExecutionStrategy;
import org.eclipse.jetty.util.thread.Invocable;
import org.eclipse.jetty.util.thread.Invocable.InvocableExecutor;
import org.eclipse.jetty.util.thread.Invocable.InvocationType;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.Locker.Lock;
/**
* <p>A strategy where the thread that produces will run the resulting task if it
* is possible to do so without thread starvation.</p>
*
* <p>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 producing. If necessary another thread will be dispatched
* to replace the pending producing thread. When operating in this pattern, the
* sub-strategy is called Execute Produce Consume (EPC)
* </p>
* <p>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 producing thread and then resume producing after the
* task has completed. This sub-strategy is also used if the strategy has been
* configured with a maximum of 0 pending threads and the thread currently producing
* does not use the {@link Invocable} API to indicate that it will not block.
* When operating in this pattern, the sub-strategy is called
* ProduceConsume (PC).
* </p>
* <p>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 producing. When operating in this pattern, the
* sub-strategy is called ProduceExecuteConsume (PEC).
* </p>
*
*/
public class EatWhatYouKill extends AbstractLifeCycle implements ExecutionStrategy, Runnable
{
private static final Logger LOG = Log.getLogger(EatWhatYouKill.class);
enum State { IDLE, PRODUCING, REPRODUCING };
private final Locker _locker = new Locker();
private State _state = State.IDLE;
private final Runnable _runProduce = new RunProduce();
private final Producer _producer;
private final InvocableExecutor _executor;
private int _pendingProducersMax;
private int _pendingProducers;
private int _pendingProducersDispatched;
private int _pendingProducersSignalled;
private Condition _produce = _locker.newCondition();
public EatWhatYouKill(Producer producer, Executor executor)
{
this(producer,executor,InvocationType.NON_BLOCKING,InvocationType.BLOCKING);
}
public EatWhatYouKill(Producer producer, Executor executor, int maxProducersPending )
{
this(producer,executor,InvocationType.NON_BLOCKING,InvocationType.BLOCKING);
}
public EatWhatYouKill(Producer producer, Executor executor, InvocationType preferredInvocationPEC, InvocationType preferredInvocationEPC)
{
this(producer,executor,preferredInvocationPEC,preferredInvocationEPC,Integer.getInteger("org.eclipse.jetty.util.thread.strategy.EatWhatYouKill.maxProducersPending",1));
}
public EatWhatYouKill(Producer producer, Executor executor, InvocationType preferredInvocationPEC, InvocationType preferredInvocationEPC, int maxProducersPending )
{
_producer = producer;
_pendingProducersMax = maxProducersPending;
_executor = new InvocableExecutor(executor,preferredInvocationPEC,preferredInvocationEPC);
}
@Override
public void produce()
{
boolean produce;
try (Lock locked = _locker.lock())
{
switch(_state)
{
case IDLE:
_state = State.PRODUCING;
produce = true;
break;
case PRODUCING:
_state = State.REPRODUCING;
produce = false;
break;
default:
produce = false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} execute {}", this, produce);
if (produce)
doProduce();
}
@Override
public void dispatch()
{
boolean dispatch = false;
try (Lock locked = _locker.lock())
{
switch(_state)
{
case IDLE:
dispatch = true;
break;
case PRODUCING:
_state = State.REPRODUCING;
dispatch = false;
break;
default:
dispatch = false;
}
}
if (LOG.isDebugEnabled())
LOG.debug("{} dispatch {}", this, dispatch);
if (dispatch)
_executor.execute(_runProduce,InvocationType.BLOCKING);
}
@Override
public void run()
{
if (LOG.isDebugEnabled())
LOG.debug("{} run", this);
if (!isRunning())
return;
boolean producing = false;
try (Lock locked = _locker.lock())
{
_pendingProducersDispatched--;
_pendingProducers++;
loop: while (isRunning())
{
try
{
_produce.await();
if (_pendingProducersSignalled==0)
{
// spurious wakeup!
continue loop;
}
_pendingProducersSignalled--;
if (_state == State.IDLE)
{
_state = State.PRODUCING;
producing = true;
}
}
catch (InterruptedException e)
{
LOG.debug(e);
_pendingProducers--;
}
break loop;
}
}
if (producing)
doProduce();
}
private void doProduce()
{
boolean may_block_caller = !Invocable.isNonBlockingInvocation();
if (LOG.isDebugEnabled())
LOG.debug("{} produce {}", this,may_block_caller?"non-blocking":"blocking");
producing: while (isRunning())
{
// If we got here, then we are the thread that is producing.
Runnable task = _producer.produce();
boolean produce;
boolean consume;
boolean execute_producer;
StringBuilder state = null;
try (Lock locked = _locker.lock())
{
if (LOG.isDebugEnabled())
{
state = new StringBuilder();
getString(state);
getState(state);
state.append("->");
}
// Did we produced a task?
if (task == null)
{
// There is no task.
// Could another one just have been queued with a produce call?
if (_state==State.REPRODUCING)
{
_state = State.PRODUCING;
continue producing;
}
// ... and no additional calls to execute, so we are idle
_state = State.IDLE;
break producing;
}
// Will we eat our own kill - ie consume the task we just produced?
if (Invocable.getInvocationType(task)==InvocationType.NON_BLOCKING)
{
// ProduceConsume
produce = true;
consume = true;
execute_producer = false;
}
else if (may_block_caller && (_pendingProducers>0 || _pendingProducersMax==0))
{
// ExecuteProduceConsume (eat what we kill!)
produce = false;
consume = true;
execute_producer = true;
_pendingProducersDispatched++;
_state = State.IDLE;
_pendingProducers--;
_pendingProducersSignalled++;
_produce.signal();
}
else
{
// ProduceExecuteConsume
produce = true;
consume = false;
execute_producer = (_pendingProducersDispatched + _pendingProducers)<_pendingProducersMax;
if (execute_producer)
_pendingProducersDispatched++;
}
if (LOG.isDebugEnabled())
getState(state);
}
if (LOG.isDebugEnabled())
{
LOG.debug("{} {} {}",
state,
consume?(execute_producer?"EPC!":"PC"):"PEC",
task);
}
if (execute_producer)
// Spawn a new thread to continue production by running the produce loop.
_executor.execute(this);
// Run or execute the task.
if (consume)
_executor.invoke(task);
else
_executor.execute(task);
// Once we have run the task, we can try producing again.
if (produce)
continue producing;
try (Lock locked = _locker.lock())
{
if (_state==State.IDLE)
{
_state = State.PRODUCING;
continue producing;
}
}
break producing;
}
if (LOG.isDebugEnabled())
LOG.debug("{} produce exit",this);
}
public Boolean isIdle()
{
try (Lock locked = _locker.lock())
{
return _state==State.IDLE;
}
}
@Override
protected void doStop() throws Exception
{
try (Lock locked = _locker.lock())
{
_pendingProducersSignalled=_pendingProducers+_pendingProducersDispatched;
_pendingProducers=0;
_produce.signalAll();
}
}
public String toString()
{
StringBuilder builder = new StringBuilder();
getString(builder);
try (Lock locked = _locker.lock())
{
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('/');
builder.append(_pendingProducers);
builder.append('/');
builder.append(_pendingProducersMax);
}
private class RunProduce implements Runnable
{
@Override
public void run()
{
produce();
}
}
}