Merge remote-tracking branch 'origin/jetty-9.4.x'

This commit is contained in:
Greg Wilkins 2017-09-28 07:58:15 +10:00
commit 1ce96be964
19 changed files with 558 additions and 96 deletions

View File

@ -32,6 +32,7 @@ import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
/**
* A Jetty server with multiple connectors.
@ -46,12 +47,14 @@ public class ManyConnectors
// probably be a direct path to your own keystore.
String jettyDistKeystore = "../../jetty-distribution/target/distribution/demo-base/etc/test-keystore";
String keystorePath = System.getProperty(
"example.keystore", jettyDistKeystore);
String keystorePath = System.getProperty("example.keystore", jettyDistKeystore);
File keystoreFile = new File(keystorePath);
if (!keystoreFile.exists())
{
throw new FileNotFoundException(keystoreFile.getAbsolutePath());
keystorePath = "jetty-distribution/target/distribution/demo-base/etc/keystore";
keystoreFile = new File(keystorePath);
if (!keystoreFile.exists())
throw new FileNotFoundException(keystoreFile.getAbsolutePath());
}
// Create a basic jetty server object without declaring the port. Since

View File

@ -73,6 +73,7 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadBudget;
import org.eclipse.jetty.util.thread.ThreadPool;
/**

View File

@ -362,28 +362,100 @@ You define a `JDBCLoginService` with the name of the realm and the location of t
[source, xml, subs="{sub-order}"]
----
<New class="org.eclipse.jetty.security.JDBCLoginService">
<Set name="name">Test JDBC Realm</Set>
<Set name="config">etc/jdbcRealm.properties</Set>
</New>
----
==== Authorization
As far as the http://jcp.org/aboutJava/communityprocess/final/jsr340/[Servlet Specification] is concerned, authorization is based on roles.
As we have seen, a LoginService associates a user with a set of roles.
When a user requests a resource that is access protected, the LoginService will be asked to authenticate the user if they are not already, and then asked to confirm if that user possesses one of the roles permitted access to the resource.
As far as the https://jcp.org/en/jsr/detail?id=340[Servlet Specification] is concerned, authorization is based on roles.
As we have seen, a `LoginService` associates a user with a set of roles.
When a user requests a resource that is access protected, the `LoginService` will be asked to authenticate the user if they are not already, and then asked to confirm if that user possesses one of the roles permitted access to the resource.
Until Servlet 3.1, role-based authorization could define:
* Access granted to a set of named roles
* Access totally forbidden, regardless of role
* Access granted to a user in any of the roles defined in the effective web.xml.
This is indicated by the special value of `*` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`
* Access granted to a set of named roles:
With the advent of Servlet 3.1, there is now another authorization:
[source, xml, subs="{sub-order}"]
----
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo Admin Data</web-resource-name>
<url-pattern>/foo/admin/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
<role-name>manager</role-name>
</auth-constraint>
</security-constraint>
----
* Access totally forbidden, regardless of role:
[source, xml, subs="{sub-order}"]
----
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo Protected Data</web-resource-name>
<url-pattern>/foo/protected/*</url-pattern>
</web-resource-collection>
<auth-constraint>
</auth-constraint>
</security-constraint>
----
* Access granted to a user in any of the roles defined in the effective `web.xml`.
This is indicated by the special value of `*` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`:
[source, xml, subs="{sub-order}"]
----
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo Role Data</web-resource-name>
<url-pattern>/foo/role/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>*</role-name>
</auth-constraint>
</security-constraint>
----
Servlet 3.1 introduced an additional authorization:
* Access granted to any user who is authenticated, regardless of roles.
This is indicated by the special value of `**` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`
This is indicated by the special value of `**` for the `<role-name>` of a `<auth-constraint>` in the `<security-constraint>`:
[source, xml, subs="{sub-order}"]
----
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo Authenticated Data</web-resource-name>
<url-pattern>/foo/authenticated/*</url-pattern>
</web-resource-collection>
<auth-constraint>
<role-name>**</role-name>
</auth-constraint>
</security-constraint>
----
Additionally, when configuring your security constraints you can protect various HTTP methods as well, such as `PUT`, `GET`, `POST`, `HEAD` or `DELETE`.
This is done by adding the method you want to protect as a `<http-method>` in the `<web-resource-collection>`.
You can then define roles that should be able to perform these protected methods in an `<auth-constraint>`:
[source, xml, subs="{sub-order}"]
----
<security-constraint>
<web-resource-collection>
<web-resource-name>Foo Authenticated Data</web-resource-name>
<url-pattern>/foo/authenticated/*</url-pattern>
<http-method>DELETE</http-method>
<http-method>POST</http-method>
</web-resource-collection>
<auth-constraint>
<role-name>admin</role-name>
</auth-constraint>
</security-constraint>
----
In the above example, only users with an `admin` role will be able to perform `DELETE` or `POST` methods.

View File

@ -38,6 +38,7 @@ import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadBudget;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.util.thread.strategy.EatWhatYouKill;
@ -295,13 +296,15 @@ public abstract class SelectorManager extends ContainerLifeCycle implements Dump
@Override
protected void doStart() throws Exception
{
addBean(new ReservedThreadExecutor(getExecutor(),_reservedThreads),true);
addBean(new ReservedThreadExecutor(getExecutor(),_reservedThreads,this),true);
for (int i = 0; i < _selectors.length; i++)
{
ManagedSelector selector = newSelector(i);
_selectors[i] = selector;
addBean(selector);
}
super.doStart();
}

View File

@ -53,6 +53,8 @@ import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.ScheduledExecutorScheduler;
import org.eclipse.jetty.util.thread.Scheduler;
import org.eclipse.jetty.util.thread.ThreadBudget;
import org.eclipse.jetty.util.thread.ThreadPool;
/**
* <p>An abstract implementation of {@link Connector} that provides a {@link ConnectionFactory} mechanism
@ -158,6 +160,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
private String _name;
private int _acceptorPriorityDelta=-2;
private boolean _accepting = true;
private ThreadBudget.Lease lease;
/**
@ -273,6 +276,7 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
throw new IllegalStateException("No protocol factory for SSL next protocol: '" + next + "' in " + this);
}
lease = ThreadBudget.leaseFrom(getExecutor(),this,_acceptors.length);
super.doStart();
_stopping=new CountDownLatch(_acceptors.length);
@ -308,6 +312,9 @@ public abstract class AbstractConnector extends ContainerLifeCycle implements Co
@Override
protected void doStop() throws Exception
{
if (lease!=null)
lease.close();
// Tell the acceptors we are stopping
interruptAcceptors();

View File

@ -23,6 +23,7 @@ import java.net.InetSocketAddress;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Supplier;
@ -522,7 +523,7 @@ public class HttpChannel implements Runnable, HttpOutput.Interceptor
if (LOG.isDebugEnabled())
LOG.debug(_request.getRequestURI(), failure);
}
else if (failure instanceof BadMessageException)
else if (failure instanceof BadMessageException | failure instanceof IOException | failure instanceof TimeoutException)
{
if (LOG.isDebugEnabled())
LOG.debug(_request.getRequestURI(), failure);

View File

@ -80,6 +80,7 @@ import org.eclipse.jetty.http.HttpVersion;
import org.eclipse.jetty.http.MetaData;
import org.eclipse.jetty.http.MimeTypes;
import org.eclipse.jetty.http.pathmap.PathSpec;
import org.eclipse.jetty.io.RuntimeIOException;
import org.eclipse.jetty.http.pathmap.ServletPathSpec;
import org.eclipse.jetty.server.handler.ContextHandler;
import org.eclipse.jetty.server.handler.ContextHandler.Context;
@ -132,6 +133,9 @@ import org.eclipse.jetty.util.log.Logger;
* {@link ContextHandler#getMaxFormContentSize()} or if there is no context then the "org.eclipse.jetty.server.Request.maxFormContentSize" {@link Server}
* attribute. The number of parameters keys is limited by {@link ContextHandler#getMaxFormKeys()} or if there is no context then the
* "org.eclipse.jetty.server.Request.maxFormKeys" {@link Server} attribute.
* </p>
* <p>If IOExceptions or timeouts occur while reading form parameters, these are thrown as unchecked Exceptions: ether {@link RuntimeIOException},
* {@link BadMessageException} or {@link RuntimeException} as appropriate.</p>
*/
public class Request implements HttpServletRequest
{
@ -522,10 +526,8 @@ public class Request implements HttpServletRequest
}
catch (IOException e)
{
if (LOG.isDebugEnabled())
LOG.warn(e);
else
LOG.warn(e.toString());
LOG.debug(e);
throw new RuntimeIOException(e);
}
}
@ -538,8 +540,8 @@ public class Request implements HttpServletRequest
}
catch (IOException | ServletException e)
{
LOG.warn(e);
throw new RuntimeException(e);
LOG.debug(e);
throw new RuntimeIOException(e);
}
}

View File

@ -24,12 +24,12 @@ import java.net.InetSocketAddress;
import java.net.URI;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.concurrent.TimeUnit;
@ -66,6 +66,7 @@ import org.eclipse.jetty.util.log.Logger;
import org.eclipse.jetty.util.thread.Locker;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ShutdownThread;
import org.eclipse.jetty.util.thread.ThreadBudget;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.eclipse.jetty.util.thread.ThreadPool.SizedThreadPool;
@ -379,37 +380,6 @@ public class Server extends HandlerWrapper implements Attributes
HttpGenerator.setJettyVersion(HttpConfiguration.SERVER_VERSION);
// Check that the thread pool size is enough.
SizedThreadPool pool = getBean(SizedThreadPool.class);
int max=pool==null?-1:pool.getMaxThreads();
int selectors=0;
int acceptors=0;
for (Connector connector : _connectors)
{
if (connector instanceof AbstractConnector)
{
AbstractConnector abstractConnector = (AbstractConnector)connector;
Executor connectorExecutor = connector.getExecutor();
if (connectorExecutor != pool)
{
// Do not count the selectors and acceptors from this connector at
// the server level, because the connector uses a dedicated executor.
continue;
}
acceptors += abstractConnector.getAcceptors();
if (connector instanceof ServerConnector)
selectors += ((ServerConnector)connector).getSelectorManager().getSelectorCount();
}
}
int needed=1+selectors+acceptors;
if (max>0 && needed>max)
throw new IllegalStateException(String.format("Insufficient threads: max=%d < needed(acceptors=%d + selectors=%d + request=1)",max,acceptors,selectors));
MultiException mex=new MultiException();
try
{

View File

@ -555,5 +555,11 @@ public class ServerConnector extends AbstractNetworkConnector
onEndPointClosed(endpoint);
super.endPointClosed(endpoint);
}
@Override
public String toString()
{
return String.format("SelectorManager@%s",ServerConnector.this);
}
}
}

View File

@ -19,9 +19,12 @@
package org.eclipse.jetty.server;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ThreadBudget;
import org.eclipse.jetty.util.thread.ThreadPool;
import org.junit.After;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -37,21 +40,29 @@ public class InsufficientThreadsDetectionTest
_server.stop();
}
@Test(expected = IllegalStateException.class)
@Test()
public void testConnectorUsesServerExecutorWithNotEnoughThreads() throws Exception
{
// server has 3 threads in the executor
_server = new Server(new QueuedThreadPool(3));
try
{
// server has 3 threads in the executor
_server = new Server(new QueuedThreadPool(3));
// connector will use executor from server because connectorPool is null
ThreadPool connectorPool = null;
// connector requires 7 threads(2 + 4 + 1)
ServerConnector connector = new ServerConnector(_server, connectorPool, null, null, 2, 4, new HttpConnectionFactory());
connector.setPort(0);
_server.addConnector(connector);
// connector will use executor from server because connectorPool is null
ThreadPool connectorPool = null;
// connector requires 7 threads(2 + 4 + 1)
ServerConnector connector = new ServerConnector(_server, connectorPool, null, null, 2, 4, new HttpConnectionFactory());
connector.setPort(0);
_server.addConnector(connector);
// should throw IllegalStateException because there are no required threads in server pool
_server.start();
// should throw IllegalStateException because there are no required threads in server pool
_server.start();
Assert.fail();
}
catch(IllegalStateException e)
{
Log.getLogger(ThreadBudget.class).warn(e.toString());
}
}
@Test
@ -71,20 +82,35 @@ public class InsufficientThreadsDetectionTest
_server.start();
}
@Test // Github issue #586
public void testCaseForMultipleConnectors() throws Exception {
// server has 4 threads in the executor
_server = new Server(new QueuedThreadPool(4));
// Github issue #586
// first connector consumes all 4 threads from server pool
_server.addConnector(new ServerConnector(_server, null, null, null, 1, 1, new HttpConnectionFactory()));
@Test
public void testCaseForMultipleConnectors() throws Exception
{
try
{
// server has 4 threads in the executor
_server = new Server(new QueuedThreadPool(4));
// second connect also require 4 threads but uses own executor, so its threads should not be counted
final QueuedThreadPool connectorPool = new QueuedThreadPool(4, 4);
_server.addConnector(new ServerConnector(_server, connectorPool, null, null, 1, 1, new HttpConnectionFactory()));
// first connector consumes 3 threads from server pool
_server.addConnector(new ServerConnector(_server, null, null, null, 1, 1, new HttpConnectionFactory()));
// should not throw exception because limit was not overflown
_server.start();
// second connect also require 4 threads but uses own executor, so its threads should not be counted
final QueuedThreadPool connectorPool = new QueuedThreadPool(4, 4);
_server.addConnector(new ServerConnector(_server, connectorPool, null, null, 1, 1, new HttpConnectionFactory()));
// first connector consumes 3 threads from server pool
_server.addConnector(new ServerConnector(_server, null, null, null, 1, 1, new HttpConnectionFactory()));
// should not throw exception because limit was not overflown
_server.start();
Assert.fail();
}
catch(IllegalStateException e)
{
Log.getLogger(ThreadBudget.class).warn(e.toString());
}
}
}

View File

@ -68,6 +68,7 @@ import org.eclipse.jetty.server.handler.ErrorHandler;
import org.eclipse.jetty.toolchain.test.AdvancedRunner;
import org.eclipse.jetty.toolchain.test.FS;
import org.eclipse.jetty.toolchain.test.MavenTestingUtils;
import org.eclipse.jetty.util.BufferUtil;
import org.eclipse.jetty.util.IO;
import org.eclipse.jetty.util.MultiPartInputStreamParser;
import org.eclipse.jetty.util.log.Log;
@ -100,6 +101,7 @@ public class RequestTest
http.getHttpConfiguration().addCustomizer(new ForwardedRequestCustomizer());
_connector = new LocalConnector(_server,http);
_server.addConnector(_connector);
_connector.setIdleTimeout(500);
_handler = new RequestHandler();
_server.setHandler(_handler);
@ -177,6 +179,39 @@ public class RequestTest
assertThat("Responses", responses, startsWith("HTTP/1.1 400"));
}
@Test
public void testParamExtraction_Timeout() throws Exception
{
_handler._checker = new RequestTester()
{
@Override
public boolean check(HttpServletRequest request,HttpServletResponse response)
{
Map<String, String[]> map = request.getParameterMap();
// should have thrown a BadMessageException
return false;
}
};
//Send a request with query string with illegal hex code to cause
//an exception parsing the params
String request="POST / HTTP/1.1\r\n"+
"Host: whatever\r\n"+
"Content-Type: "+MimeTypes.Type.FORM_ENCODED.asString()+"\n"+
"Connection: close\n"+
"Content-Length: 100\n"+
"\n"+
"name=value";
LocalEndPoint endp = _connector.connect();
endp.addInput(request);
String response = BufferUtil.toString(endp.waitForResponse(false, 1, TimeUnit.SECONDS));
assertThat("Responses", response, startsWith("HTTP/1.1 500"));
}
@Test
public void testEmptyHeaders() throws Exception
{

View File

@ -18,7 +18,10 @@
package org.eclipse.jetty.util.component;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Set;
/**
@ -45,6 +48,7 @@ public interface Container
* @return the list of beans of the given class (or subclass)
* @param <T> the Bean type
* @see #getBeans()
* @see #getContainedBeans(Class)
*/
public <T> Collection<T> getBeans(Class<T> clazz);
@ -93,6 +97,14 @@ public interface Container
*/
void manage(Object bean);
/**
* Test if this container manages a bean
* @param bean the bean to test
* @return whether this aggregate contains and manages the bean
*/
boolean isManaged(Object bean);
/**
* Adds the given bean, explicitly managing it or not.
*
@ -121,4 +133,11 @@ public interface Container
public interface InheritedListener extends Listener
{
}
/**
* @param clazz the class of the beans
* @return the list of beans of the given class from the entire managed hierarchy
* @param <T> the Bean type
*/
public <T> Collection<T> getContainedBeans(Class<T> clazz);
}

View File

@ -22,7 +22,9 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArrayList;
import org.eclipse.jetty.util.annotation.ManagedObject;
@ -198,6 +200,7 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
* @param bean the bean to test
* @return whether this aggregate contains and manages the bean
*/
@Override
public boolean isManaged(Object bean)
{
for (Bean b : _beans)
@ -761,6 +764,19 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
return _managed==Managed.MANAGED;
}
public boolean isManageable()
{
switch(_managed)
{
case MANAGED:
return true;
case AUTO:
return _bean instanceof LifeCycle && ((LifeCycle)_bean).isStopped();
default:
return false;
}
}
@Override
public String toString()
{
@ -822,4 +838,38 @@ public class ContainerLifeCycle extends AbstractLifeCycle implements Container,
}
}
}
/**
* @param clazz the class of the beans
* @return the list of beans of the given class from the entire managed hierarchy
* @param <T> the Bean type
*/
public <T> Collection<T> getContainedBeans(Class<T> clazz)
{
Set<T> beans = new HashSet<>();
getContainedBeans(clazz, beans);
return beans;
}
/**
* @param clazz the class of the beans
* @param <T> the Bean type
* @param beans the collection to add beans of the given class from the entire managed hierarchy
*/
protected <T> void getContainedBeans(Class<T> clazz, Collection<T> beans)
{
beans.addAll(getBeans(clazz));
for (Container c : getBeans(Container.class))
{
Bean bean = getBean(c);
if (bean!=null && bean.isManageable())
{
if (c instanceof ContainerLifeCycle)
((ContainerLifeCycle)c).getContainedBeans(clazz, beans);
else
beans.addAll(c.getContainedBeans(clazz));
}
}
}
}

View File

@ -16,7 +16,6 @@
// ========================================================================
//
package org.eclipse.jetty.util.thread;
import java.io.IOException;
@ -66,6 +65,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
private boolean _daemon = false;
private boolean _detailedDump = false;
private int _lowThreadsThreshold = 1;
private ThreadBudget _budget;
public QueuedThreadPool()
{
@ -106,6 +106,23 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
}
_jobs=queue;
_threadGroup=threadGroup;
_budget=new ThreadBudget(this);
}
@Override
public ThreadBudget getThreadBudget()
{
return _budget;
}
public void setThreadBudget(ThreadBudget budget)
{
if (budget!=null && budget.getSizedThreadPool()!=this)
throw new IllegalArgumentException();
synchronized (this)
{
_budget = budget;
}
}
@Override
@ -184,6 +201,9 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
}
}
if (_budget!=null)
_budget.reset();
synchronized (_joinLock)
{
_joinLock.notifyAll();
@ -563,7 +583,7 @@ public class QueuedThreadPool extends AbstractLifeCycle implements SizedThreadPo
@Override
public String toString()
{
return String.format("org.eclipse.jetty.util.thread.QueuedThreadPool@%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
return String.format("QueuedThreadPool@%s{%s,%d<=%d<=%d,i=%d,q=%d}", _name, getState(), getMinThreads(), getThreads(), getMaxThreads(), getIdleThreads(), (_jobs == null ? -1 : _jobs.size()));
}
private Runnable idleJobPoll() throws InterruptedException

View File

@ -45,6 +45,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo
private int _head;
private int _size;
private int _pending;
private ThreadBudget.Lease _lease;
private Object _owner;
public ReservedThreadExecutor(Executor executor)
{
@ -58,24 +60,42 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo
* thread pool size.
*/
public ReservedThreadExecutor(Executor executor,int capacity)
{
this(executor,capacity,null);
}
/**
* @param executor The executor to use to obtain threads
* @param capacity The number of threads to preallocate. If less than 0 then capacity
* is calculated based on a heuristic from the number of available processors and
* thread pool size.
*/
public ReservedThreadExecutor(Executor executor,int capacity, Object owner)
{
_executor = executor;
_queue = new ReservedThread[reservedThreads(executor,capacity)];
_owner = owner;
}
if (capacity < 0)
/**
* @param executor The executor to use to obtain threads
* @param capacity The number of threads to preallocate, If less than 0 then capacity
* is calculated based on a heuristic from the number of available processors and
* thread pool size.
* @return the number of reserved threads that would be used by a ReservedThreadExecutor
* constructed with these arguments.
*/
public static int reservedThreads(Executor executor,int capacity)
{
if (capacity>=0)
return capacity;
int cpus = Runtime.getRuntime().availableProcessors();
if (executor instanceof ThreadPool.SizedThreadPool)
{
int cpus = Runtime.getRuntime().availableProcessors();
if (executor instanceof ThreadPool.SizedThreadPool)
{
int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads();
capacity = Math.max(1, Math.min(cpus, threads / 8));
}
else
{
capacity = cpus;
}
int threads = ((ThreadPool.SizedThreadPool)executor).getMaxThreads();
return Math.max(1, Math.min(cpus, threads / 8));
}
_queue = new ReservedThread[capacity];
return cpus;
}
public Executor getExecutor()
@ -107,9 +127,18 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo
}
}
@Override
public void doStart() throws Exception
{
_lease = ThreadBudget.leaseFrom(getExecutor(),this,_queue.length);
super.doStart();
}
@Override
public void doStop() throws Exception
{
if (_lease!=null)
_lease.close();
try (Locker.Lock lock = _locker.lock())
{
while (_size>0)
@ -179,7 +208,9 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements Executo
{
try (Locker.Lock lock = _locker.lock())
{
return String.format("%s{s=%d,p=%d}",super.toString(),_size,_pending);
if (_owner==null)
return String.format("%s@%x{s=%d,p=%d}",this.getClass().getSimpleName(),hashCode(),_size,_pending);
return String.format("%s@%s{s=%d,p=%d}",this.getClass().getSimpleName(),_owner,_size,_pending);
}
}

View File

@ -0,0 +1,178 @@
//
// ========================================================================
// 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.io.IOException;
import java.util.Set;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.Executor;
import java.util.concurrent.atomic.AtomicBoolean;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.Logger;
/**
* <p>A budget of required thread usage, used to warn or error for insufficient configured threads.</p>
*
* @see ThreadPool.SizedThreadPool#getThreadBudget()
*/
public class ThreadBudget
{
static final Logger LOG = Log.getLogger(ThreadBudget.class);
public interface Lease extends Closeable
{
int getThreads();
}
/**
* An allocation of threads
*/
public class Leased implements Lease
{
final Object leasee;
final int threads;
private Leased(Object leasee,int threads)
{
this.leasee = leasee;
this.threads = threads;
}
@Override
public int getThreads()
{
return threads;
}
@Override
public void close()
{
info.remove(this);
allocations.remove(this);
warned.set(false);
}
}
private static final Lease NOOP_LEASE = new Lease()
{
@Override
public void close() throws IOException
{
}
@Override
public int getThreads()
{
return 0;
}
};
final ThreadPool.SizedThreadPool pool;
final Set<Leased> allocations = new CopyOnWriteArraySet<>();
final Set<Leased> info = new CopyOnWriteArraySet<>();
final AtomicBoolean warned = new AtomicBoolean();
final int warnAt;
/**
* Construct a bedget for a SizedThreadPool, with the warning level set by heuristic.
* @param pool The pool to budget thread allocation for.
*/
public ThreadBudget(ThreadPool.SizedThreadPool pool)
{
this(pool,Runtime.getRuntime().availableProcessors());
}
/**
* @param pool The pool to budget thread allocation for.
* @param warnAt The level of free threads at which a warning is generated.
*/
public ThreadBudget(ThreadPool.SizedThreadPool pool, int warnAt)
{
this.pool = pool;
this.warnAt = warnAt;
}
public ThreadPool.SizedThreadPool getSizedThreadPool()
{
return pool;
}
public void reset()
{
allocations.clear();
info.clear();
warned.set(false);
}
public Lease leaseTo(Object leasee, int threads)
{
Leased lease = new Leased(leasee,threads);
allocations.add(lease);
check();
return lease;
}
/**
* Check registered allocations against the budget.
* @throws IllegalStateException if insufficient threads are configured.
*/
public void check() throws IllegalStateException
{
int required = allocations.stream()
.mapToInt(Lease::getThreads)
.sum();
int maximum = pool.getMaxThreads();
if (required>=maximum)
{
infoOnLeases();
throw new IllegalStateException(String.format("Insuffient configured threads: required=%d < max=%d for %s", required, maximum, pool));
}
if ((maximum-required) < warnAt)
{
infoOnLeases();
if (warned.compareAndSet(false,true))
LOG.warn("Low configured threads: ( max={} - required={} ) < warnAt={} for {}", maximum, required, warnAt, pool);
}
}
private void infoOnLeases()
{
allocations.stream().filter(lease->!info.contains(lease))
.forEach(lease->{
info.add(lease);
LOG.info("{} requires {} threads from {}",lease.leasee,lease.getThreads(),pool);
});
}
public static Lease leaseFrom(Executor executor, Object leasee, int threads)
{
if (executor instanceof ThreadPool.SizedThreadPool)
{
ThreadBudget budget = ((ThreadPool.SizedThreadPool)executor).getThreadBudget();
if (budget!=null)
return budget.leaseTo(leasee,threads);
}
return NOOP_LEASE;
}
}

View File

@ -67,9 +67,14 @@ public interface ThreadPool extends Executor
/* ------------------------------------------------------------ */
public interface SizedThreadPool extends ThreadPool
{
public int getMinThreads();
public int getMaxThreads();
public void setMinThreads(int threads);
public void setMaxThreads(int threads);
int getMinThreads();
int getMaxThreads();
void setMinThreads(int threads);
void setMaxThreads(int threads);
default ThreadBudget getThreadBudget()
{
return null;
}
}
}

View File

@ -26,9 +26,13 @@ import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.atomic.AtomicInteger;
import org.eclipse.jetty.util.TypeUtil;
import org.hamcrest.Matchers;
import org.junit.Assert;
import org.junit.Test;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.Assert.assertThat;
public class ContainerLifeCycleTest
{
@Test
@ -608,4 +612,32 @@ public class ContainerLifeCycleTest
super.destroy();
}
}
@Test
public void testGetBeans() throws Exception
{
TestContainerLifeCycle root = new TestContainerLifeCycle();
TestContainerLifeCycle left = new TestContainerLifeCycle();
root.addBean(left);
TestContainerLifeCycle right = new TestContainerLifeCycle();
root.addBean(right);
TestContainerLifeCycle leaf = new TestContainerLifeCycle();
right.addBean(leaf);
root.addBean(Integer.valueOf(0));
root.addBean(Integer.valueOf(1));
left.addBean(Integer.valueOf(2));
right.addBean(Integer.valueOf(3));
leaf.addBean(Integer.valueOf(4));
leaf.addBean("leaf");
assertThat(root.getBeans(Container.class), containsInAnyOrder(left,right));
assertThat(root.getBeans(Integer.class), containsInAnyOrder(Integer.valueOf(0),Integer.valueOf(1)));
assertThat(root.getBeans(String.class), containsInAnyOrder());
assertThat(root.getContainedBeans(Container.class), containsInAnyOrder(left,right,leaf));
assertThat(root.getContainedBeans(Integer.class), containsInAnyOrder(Integer.valueOf(0),Integer.valueOf(1),Integer.valueOf(2),Integer.valueOf(3),Integer.valueOf(4)));
assertThat(root.getContainedBeans(String.class), containsInAnyOrder("leaf"));
}
}

View File

@ -37,6 +37,7 @@ import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.eclipse.jetty.client.HttpClient;
import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Response;
import org.eclipse.jetty.client.util.BytesContentProvider;