jetty-9 - Refactored code that was counting the number of reentrant invocations into a common utility class, ForkInvoker.
This commit is contained in:
parent
e7db1661c6
commit
0915b2b0ab
|
@ -37,6 +37,7 @@ import java.util.concurrent.ConcurrentLinkedQueue;
|
|||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.eclipse.jetty.util.ForkInvoker;
|
||||
import org.eclipse.jetty.util.TypeUtil;
|
||||
import org.eclipse.jetty.util.annotation.Name;
|
||||
import org.eclipse.jetty.util.component.AbstractLifeCycle;
|
||||
|
@ -53,14 +54,6 @@ import org.eclipse.jetty.util.log.Logger;
|
|||
*/
|
||||
public abstract class SelectorManager extends AbstractLifeCycle implements Dumpable
|
||||
{
|
||||
private static final ThreadLocal<Integer> _submissions = new ThreadLocal<Integer>()
|
||||
{
|
||||
@Override
|
||||
protected Integer initialValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
protected static final Logger LOG = Log.getLogger(SelectorManager.class);
|
||||
|
||||
private final ManagedSelector[] _selectors;
|
||||
|
@ -274,6 +267,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
*/
|
||||
public class ManagedSelector extends AbstractLifeCycle implements Runnable, Dumpable
|
||||
{
|
||||
private final ForkInvoker<Runnable> invoker = new ManagedSelectorInvoker();
|
||||
private final Queue<Runnable> _changes = new ConcurrentLinkedQueue<>();
|
||||
private final int _id;
|
||||
private Selector _selector;
|
||||
|
@ -314,31 +308,7 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
*/
|
||||
public boolean submit(Runnable change)
|
||||
{
|
||||
int submissions = _submissions.get();
|
||||
if (Thread.currentThread() != _thread || submissions >= 4)
|
||||
{
|
||||
_changes.offer(change);
|
||||
LOG.debug("Queued change {}", change);
|
||||
boolean wakeup = _needsWakeup;
|
||||
if (wakeup)
|
||||
wakeup();
|
||||
return false;
|
||||
}
|
||||
else
|
||||
{
|
||||
_submissions.set(submissions + 1);
|
||||
try
|
||||
{
|
||||
LOG.debug("Submitted change {}", change);
|
||||
runChanges();
|
||||
runChange(change);
|
||||
return true;
|
||||
}
|
||||
finally
|
||||
{
|
||||
_submissions.set(submissions);
|
||||
}
|
||||
}
|
||||
return !invoker.invoke(change);
|
||||
}
|
||||
|
||||
private void runChanges()
|
||||
|
@ -592,6 +562,38 @@ public abstract class SelectorManager extends AbstractLifeCycle implements Dumpa
|
|||
selector != null && selector.isOpen() ? selector.selectedKeys().size() : -1);
|
||||
}
|
||||
|
||||
private class ManagedSelectorInvoker extends ForkInvoker<Runnable>
|
||||
{
|
||||
private ManagedSelectorInvoker()
|
||||
{
|
||||
super(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean condition()
|
||||
{
|
||||
return Thread.currentThread() != _thread;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fork(Runnable change)
|
||||
{
|
||||
_changes.offer(change);
|
||||
LOG.debug("Queued change {}", change);
|
||||
boolean wakeup = _needsWakeup;
|
||||
if (wakeup)
|
||||
wakeup();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(Runnable change)
|
||||
{
|
||||
LOG.debug("Submitted change {}", change);
|
||||
runChanges();
|
||||
runChange(change);
|
||||
}
|
||||
}
|
||||
|
||||
private class DumpKeys implements Runnable
|
||||
{
|
||||
private final CountDownLatch latch = new CountDownLatch(1);
|
||||
|
|
|
@ -70,6 +70,7 @@ import org.eclipse.jetty.spdy.parser.Parser;
|
|||
import org.eclipse.jetty.util.Atomics;
|
||||
import org.eclipse.jetty.util.BufferUtil;
|
||||
import org.eclipse.jetty.util.Callback;
|
||||
import org.eclipse.jetty.util.ForkInvoker;
|
||||
import org.eclipse.jetty.util.component.AggregateLifeCycle;
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
import org.eclipse.jetty.util.log.Log;
|
||||
|
@ -79,15 +80,8 @@ import org.eclipse.jetty.util.thread.Scheduler;
|
|||
public class StandardSession implements ISession, Parser.Listener, Callback<StandardSession.FrameBytes>, Dumpable
|
||||
{
|
||||
private static final Logger logger = Log.getLogger(Session.class);
|
||||
private static final ThreadLocal<Integer> handlerInvocations = new ThreadLocal<Integer>()
|
||||
{
|
||||
@Override
|
||||
protected Integer initialValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
private final ForkInvoker<Runnable> invoker = new SessionInvoker();
|
||||
private final Map<String, Object> attributes = new ConcurrentHashMap<>();
|
||||
private final List<Listener> listeners = new CopyOnWriteArrayList<>();
|
||||
private final ConcurrentMap<Integer, IStream> streams = new ConcurrentHashMap<>();
|
||||
|
@ -688,7 +682,7 @@ public class StandardSession implements ISession, Parser.Listener, Callback<Stan
|
|||
|
||||
private void onCredential(CredentialFrame frame)
|
||||
{
|
||||
logger.warn("{} frame not yet supported", ControlFrameType.CREDENTIAL);
|
||||
logger.warn("{} frame not yet supported", frame.getType());
|
||||
flush();
|
||||
}
|
||||
|
||||
|
@ -1034,34 +1028,16 @@ public class StandardSession implements ISession, Parser.Listener, Callback<Stan
|
|||
// if we call Callback.completed() only synchronously we risk
|
||||
// starvation (for the last frames sent) and stack overflow.
|
||||
// Therefore every some invocation, we dispatch to a new thread
|
||||
Integer invocations = handlerInvocations.get();
|
||||
if (invocations >= 4)
|
||||
invoker.invoke(new Runnable()
|
||||
{
|
||||
execute(new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (callback != null)
|
||||
notifyCallbackCompleted(callback, context);
|
||||
flush();
|
||||
}
|
||||
});
|
||||
}
|
||||
else
|
||||
{
|
||||
handlerInvocations.set(invocations + 1);
|
||||
try
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
if (callback != null)
|
||||
notifyCallbackCompleted(callback, context);
|
||||
flush();
|
||||
}
|
||||
finally
|
||||
{
|
||||
handlerInvocations.set(invocations);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private <C> void notifyCallbackCompleted(Callback<C> callback, C context)
|
||||
|
@ -1114,7 +1090,6 @@ public class StandardSession implements ISession, Parser.Listener, Callback<Stan
|
|||
return String.format("%s@%x{v%d,queuSize=%d,windowSize=%d,streams=%d}", getClass().getSimpleName(), hashCode(), version, queue.size(), getWindowSize(), streams.size());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public String dump()
|
||||
{
|
||||
|
@ -1128,7 +1103,25 @@ public class StandardSession implements ISession, Parser.Listener, Callback<Stan
|
|||
AggregateLifeCycle.dump(out,indent,Collections.singletonList(controller),streams.values());
|
||||
}
|
||||
|
||||
private class SessionInvoker extends ForkInvoker<Runnable>
|
||||
{
|
||||
private SessionInvoker()
|
||||
{
|
||||
super(4);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fork(Runnable task)
|
||||
{
|
||||
execute(task);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(Runnable task)
|
||||
{
|
||||
task.run();
|
||||
}
|
||||
}
|
||||
|
||||
public interface FrameBytes extends Comparable<FrameBytes>
|
||||
{
|
||||
|
|
|
@ -20,83 +20,34 @@ package org.eclipse.jetty.util;
|
|||
|
||||
import java.util.concurrent.Executor;
|
||||
|
||||
import org.eclipse.jetty.util.component.Dumpable;
|
||||
|
||||
public abstract class ExecutorCallback<C> implements Callback<C>
|
||||
{
|
||||
private final static ThreadLocal<Integer> __calls = new ThreadLocal<Integer>()
|
||||
{
|
||||
@Override
|
||||
protected Integer initialValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
|
||||
private final int _maxRecursion;
|
||||
private final ForkInvoker<C> _invoker;
|
||||
private final Executor _executor;
|
||||
private final Runnable _onNullContextCompleted = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run() { onCompleted(null); }
|
||||
};
|
||||
|
||||
public ExecutorCallback(Executor executor)
|
||||
{
|
||||
this(executor,4);
|
||||
this(executor, 4);
|
||||
}
|
||||
|
||||
public ExecutorCallback(Executor executor,int maxRecursion)
|
||||
public ExecutorCallback(Executor executor, int maxRecursion)
|
||||
{
|
||||
_executor=executor;
|
||||
_maxRecursion=maxRecursion;
|
||||
_executor = executor;
|
||||
_invoker = new ExecutorCallbackInvoker(maxRecursion);
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void completed(final C context)
|
||||
{
|
||||
// Should we execute?
|
||||
if (!alwaysDispatchCompletion())
|
||||
if (alwaysDispatchCompletion())
|
||||
{
|
||||
// Do we have a recursion limit?
|
||||
if (_maxRecursion<=0)
|
||||
{
|
||||
// No, so just call it directly
|
||||
onCompleted(context);
|
||||
return;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Has this thread exceeded the recursion limit
|
||||
Integer calls=__calls.get();
|
||||
if (calls<_maxRecursion)
|
||||
{
|
||||
// No, so increment recursion count, call, then decrement
|
||||
try
|
||||
{
|
||||
__calls.set(calls+1);
|
||||
onCompleted(context);
|
||||
return;
|
||||
}
|
||||
finally
|
||||
{
|
||||
__calls.set(calls);
|
||||
}
|
||||
}
|
||||
}
|
||||
_invoker.fork(context);
|
||||
}
|
||||
|
||||
// fallen through to here so execute
|
||||
_executor.execute(context==null?_onNullContextCompleted:new Runnable()
|
||||
else
|
||||
{
|
||||
@Override
|
||||
public void run() { onCompleted(context);}
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("ExectorCB$Completed@%x{%s}",hashCode(),context);
|
||||
}
|
||||
});
|
||||
_invoker.invoke(context);
|
||||
}
|
||||
}
|
||||
|
||||
protected abstract void onCompleted(C context);
|
||||
|
@ -105,22 +56,22 @@ public abstract class ExecutorCallback<C> implements Callback<C>
|
|||
public final void failed(final C context, final Throwable x)
|
||||
{
|
||||
// Always execute failure
|
||||
Runnable runnable=new Runnable()
|
||||
Runnable runnable = new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
onFailed(context,x);
|
||||
onFailed(context, x);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("ExectorCB$Failed@%x{%s,%s}",hashCode(),context,x);
|
||||
return String.format("ExecutorCallback@%x{%s,%s}", hashCode(), context, x);
|
||||
}
|
||||
};
|
||||
|
||||
if (_executor==null)
|
||||
if (_executor == null)
|
||||
new Thread(runnable).start();
|
||||
else
|
||||
_executor.execute(runnable);
|
||||
|
@ -132,7 +83,7 @@ public abstract class ExecutorCallback<C> implements Callback<C>
|
|||
|
||||
protected boolean alwaysDispatchCompletion()
|
||||
{
|
||||
return _executor!=null;
|
||||
return _executor != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -140,4 +91,43 @@ public abstract class ExecutorCallback<C> implements Callback<C>
|
|||
{
|
||||
return String.format("%s@%x", getClass(), hashCode());
|
||||
}
|
||||
|
||||
private class ExecutorCallbackInvoker extends ForkInvoker<C> implements Runnable
|
||||
{
|
||||
private ExecutorCallbackInvoker(int maxInvocations)
|
||||
{
|
||||
super(maxInvocations);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void fork(final C context)
|
||||
{
|
||||
_executor.execute(context == null ? this : new Runnable()
|
||||
{
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
call(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString()
|
||||
{
|
||||
return String.format("ExecutorCallback@%x{%s}", hashCode(), context);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void call(C context)
|
||||
{
|
||||
onCompleted(context);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run()
|
||||
{
|
||||
call(null);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,138 @@
|
|||
//
|
||||
// ========================================================================
|
||||
// Copyright (c) 1995-2012 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;
|
||||
|
||||
/**
|
||||
* Utility class that splits calls to {@link #invoke(T)} into calls to {@link #fork(T)} or {@link #call(T)}
|
||||
* depending on the max number of reentrant calls to {@link #invoke(T)}.
|
||||
* <p/>
|
||||
* This class prevents {@link StackOverflowError}s in case of methods that end up invoking themselves,
|
||||
* such is common for {@link Callback#completed(Object)}.
|
||||
* <p/>
|
||||
* Typical use case is:
|
||||
* <pre>
|
||||
* public void reentrantMethod(Object param)
|
||||
* {
|
||||
* if (condition || tooManyReenters)
|
||||
* fork(param)
|
||||
* else
|
||||
* call(param)
|
||||
* }
|
||||
* </pre>
|
||||
* Calculating {@code tooManyReenters} usually involves using a {@link ThreadLocal} and algebra on the
|
||||
* number of reentrant invocations, which is factored out in this class for convenience.
|
||||
* <p />
|
||||
* The same code using this class becomes:
|
||||
* <pre>
|
||||
* private final ForkInvoker invoker = ...;
|
||||
*
|
||||
* public void reentrantMethod(Object param)
|
||||
* {
|
||||
* invoker.invoke(param);
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @param <T> the generic type of this class
|
||||
*/
|
||||
public abstract class ForkInvoker<T>
|
||||
{
|
||||
private static final ThreadLocal<Integer> __invocations = new ThreadLocal<Integer>()
|
||||
{
|
||||
@Override
|
||||
protected Integer initialValue()
|
||||
{
|
||||
return 0;
|
||||
}
|
||||
};
|
||||
private final int _maxInvocations;
|
||||
|
||||
/**
|
||||
* Creates an instance with the given max number of reentrant calls to {@link #invoke(T)}
|
||||
* <p/>
|
||||
* If {@code maxInvocations} is zero or negative, it is interpreted
|
||||
* as if the max number of reentrant calls is infinite.
|
||||
*
|
||||
* @param maxInvocations the max number of reentrant calls to {@link #invoke(T)}
|
||||
*/
|
||||
public ForkInvoker(int maxInvocations)
|
||||
{
|
||||
_maxInvocations = maxInvocations;
|
||||
}
|
||||
|
||||
/**
|
||||
* Invokes either {@link #fork(T)} or {@link #call(T)}.
|
||||
* If {@link #condition()} returns true, {@link #fork(T)} is invoked.
|
||||
* Otherwise, if the max number of reentrant calls is positive and the
|
||||
* actual number of reentrant invocations exceeds it, {@link #fork(T)} is invoked.
|
||||
* Otherwise, {@link #call(T)} is invoked.
|
||||
*
|
||||
* @param context the invocation context
|
||||
* @return true if {@link #fork(T)} has been called, false otherwise
|
||||
*/
|
||||
public boolean invoke(T context)
|
||||
{
|
||||
boolean countInvocations = _maxInvocations > 0;
|
||||
int invocations = __invocations.get();
|
||||
if (condition() || countInvocations && invocations > _maxInvocations)
|
||||
{
|
||||
fork(context);
|
||||
return true;
|
||||
}
|
||||
else
|
||||
{
|
||||
if (countInvocations)
|
||||
__invocations.set(invocations + 1);
|
||||
try
|
||||
{
|
||||
call(context);
|
||||
return false;
|
||||
}
|
||||
finally
|
||||
{
|
||||
if (countInvocations)
|
||||
__invocations.set(invocations);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Subclasses should override this method returning true if they want
|
||||
* {@link #invoke(T)} to call {@link #fork(T)}.
|
||||
*
|
||||
* @return true if {@link #invoke(T)} should call {@link #fork(T)}, false otherwise
|
||||
*/
|
||||
protected boolean condition()
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the forked invocation
|
||||
*
|
||||
* @param context the invocation context
|
||||
*/
|
||||
public abstract void fork(T context);
|
||||
|
||||
/**
|
||||
* Executes the direct, non-forked, invocation
|
||||
*
|
||||
* @param context the invocation context
|
||||
*/
|
||||
public abstract void call(T context);
|
||||
}
|
Loading…
Reference in New Issue