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

This commit is contained in:
Jan Bartel 2020-02-01 11:27:09 +01:00
commit fe8b11dd48
21 changed files with 623 additions and 76 deletions

View File

@ -0,0 +1,147 @@
//
// ========================================================================
// Copyright (c) 1995-2020 Mort Bay Consulting Pty Ltd and others.
//
// This program and the accompanying materials are made available under
// the terms of the Eclipse Public License 2.0 which is available at
// https://www.eclipse.org/legal/epl-2.0
//
// This Source Code may also be made available under the following
// Secondary Licenses when the conditions for such availability set
// forth in the Eclipse Public License, v. 2.0 are satisfied:
// the Apache License v2.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.jmh;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import org.eclipse.jetty.util.component.LifeCycle;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.util.thread.ReservedThreadExecutor;
import org.eclipse.jetty.util.thread.TryExecutor;
import org.openjdk.jmh.annotations.Benchmark;
import org.openjdk.jmh.annotations.BenchmarkMode;
import org.openjdk.jmh.annotations.Measurement;
import org.openjdk.jmh.annotations.Mode;
import org.openjdk.jmh.annotations.Param;
import org.openjdk.jmh.annotations.Scope;
import org.openjdk.jmh.annotations.Setup;
import org.openjdk.jmh.annotations.State;
import org.openjdk.jmh.annotations.TearDown;
import org.openjdk.jmh.annotations.Threads;
import org.openjdk.jmh.annotations.Warmup;
import org.openjdk.jmh.infra.Blackhole;
import org.openjdk.jmh.runner.Runner;
import org.openjdk.jmh.runner.RunnerException;
import org.openjdk.jmh.runner.options.Options;
import org.openjdk.jmh.runner.options.OptionsBuilder;
@State(Scope.Benchmark)
@Warmup(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
@Measurement(iterations = 3, time = 2000, timeUnit = TimeUnit.MILLISECONDS)
public class ReservedThreadPoolBenchmark
{
public enum Type
{
RTP
}
@Param({"RTP"})
Type type;
@Param({"0", "8", "32"})
int size;
QueuedThreadPool qtp;
TryExecutor pool;
@Setup // (Level.Iteration)
public void buildPool()
{
qtp = new QueuedThreadPool();
switch (type)
{
case RTP:
{
ReservedThreadExecutor pool = new ReservedThreadExecutor(qtp, size);
pool.setIdleTimeout(1, TimeUnit.SECONDS);
this.pool = pool;
break;
}
}
LifeCycle.start(qtp);
LifeCycle.start(pool);
}
@TearDown // (Level.Iteration)
public void shutdownPool()
{
LifeCycle.stop(pool);
LifeCycle.stop(qtp);
pool = null;
qtp = null;
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@Threads(1)
public void testFew() throws Exception
{
doJob();
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@Threads(8)
public void testSome() throws Exception
{
doJob();
}
@Benchmark
@BenchmarkMode(Mode.Throughput)
@Threads(200)
public void testMany() throws Exception
{
doJob();
}
void doJob() throws Exception
{
CountDownLatch latch = new CountDownLatch(1);
Runnable task = () ->
{
Blackhole.consumeCPU(1);
Thread.yield();
Blackhole.consumeCPU(1);
latch.countDown();
Blackhole.consumeCPU(1);
};
if (!pool.tryExecute(task))
qtp.execute(task);
latch.await();
}
public static void main(String[] args) throws RunnerException
{
Options opt = new OptionsBuilder()
.include(ReservedThreadPoolBenchmark.class.getSimpleName())
.forks(1)
// .threads(400)
// .syncIterations(true) // Don't start all threads at same time
// .addProfiler(CompilerProfiler.class)
// .addProfiler(LinuxPerfProfiler.class)
// .addProfiler(LinuxPerfNormProfiler.class)
// .addProfiler(LinuxPerfAsmProfiler.class)
// .resultFormat(ResultFormatType.CSV)
.build();
new Runner(opt).run();
}
}

View File

@ -123,16 +123,42 @@
<reportSets>
<reportSet>
<reports>
<report>project-team</report>
<report>mailing-list</report>
<report>cim</report>
<report>issue-tracking</report>
<report>license</report>
<report>team</report>
<report>mailing-lists</report>
<report>ci-management</report>
<report>issue-management</report>
<report>licenses</report>
<report>scm</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>eclipse-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<executions>
<execution>
<id>site-jar</id>
<goals>
<goal>jar</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,4 @@
Eclipse Jetty Jspc Maven Plugin
========================
This plugin will help you pre-compile your jsps and works in conjunction with the Maven war plugin to put them inside an assembled war.

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project xmlns="http://maven.apache.org/DECORATION/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
<bannerLeft>
<name>${project.name}</name>
<src>https://www.eclipse.org/jetty/images/jetty-logo-80x22.png</src>
<href>https://eclipse.org/jetty/</href>
</bannerLeft>
<bannerRight>
<src>https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-426x100.png</src>
</bannerRight>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.7</version>
</skin>
<custom>
<fluidoSkin>
<topBarEnabled>true</topBarEnabled>
<sideBarEnabled>true</sideBarEnabled>
<googleSearch>
<sitesearch>${project.url}</sitesearch>
</googleSearch>
<gitHub>
<projectId>eclipse/jetty.project</projectId>
<ribbonOrientation>right</ribbonOrientation>
<ribbonColor>gray</ribbonColor>
</gitHub>
</fluidoSkin>
</custom>
<body>
<menu name="Overview">
<item name="Introduction" href="index.html"/>
</menu>
<menu ref="reports" inherit="bottom"/>
</body>
</project>

View File

@ -265,16 +265,42 @@
<reportSets>
<reportSet>
<reports>
<report>project-team</report>
<report>mailing-list</report>
<report>cim</report>
<report>issue-tracking</report>
<report>license</report>
<report>team</report>
<report>mailing-lists</report>
<report>ci-management</report>
<report>issue-management</report>
<report>licenses</report>
<report>scm</report>
</reports>
</reportSet>
</reportSets>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-plugin-plugin</artifactId>
</plugin>
</plugins>
</reporting>
<profiles>
<profile>
<id>eclipse-release</id>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-site-plugin</artifactId>
<executions>
<execution>
<id>site-jar</id>
<goals>
<goal>jar</goal>
</goals>
<phase>package</phase>
</execution>
</executions>
</plugin>
</plugins>
</build>
</profile>
</profiles>
</project>

View File

@ -0,0 +1,10 @@
Eclipse Jetty Maven Plugin
========================
The Jetty Maven plugin is useful for rapid development and testing.
You can add it to any webapp project that is structured according to the Maven defaults.
The plugin can then periodically scan your project for changes and automatically redeploy the webapp if any are found.
This makes the development cycle more productive by eliminating the build and deploy steps: you use your IDE to make changes to the project, and the running web container automatically picks them up, allowing you to test them straight away.

View File

@ -0,0 +1,44 @@
<?xml version="1.0" encoding="ISO-8859-1"?>
<project xmlns="http://maven.apache.org/DECORATION/1.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/DECORATION/1.0.0 http://maven.apache.org/xsd/decoration-1.0.0.xsd">
<bannerLeft>
<name>${project.name}</name>
<src>https://www.eclipse.org/jetty/images/jetty-logo-80x22.png</src>
<href>https://eclipse.org/jetty/</href>
</bannerLeft>
<bannerRight>
<src>https://www.eclipse.org/eclipse.org-common/themes/solstice/public/images/logo/eclipse-426x100.png</src>
</bannerRight>
<skin>
<groupId>org.apache.maven.skins</groupId>
<artifactId>maven-fluido-skin</artifactId>
<version>1.7</version>
</skin>
<custom>
<fluidoSkin>
<topBarEnabled>true</topBarEnabled>
<sideBarEnabled>true</sideBarEnabled>
<googleSearch>
<sitesearch>${project.url}</sitesearch>
</googleSearch>
<gitHub>
<projectId>eclipse/jetty.project</projectId>
<ribbonOrientation>right</ribbonOrientation>
<ribbonColor>gray</ribbonColor>
</gitHub>
</fluidoSkin>
</custom>
<body>
<menu name="Overview">
<item name="Introduction" href="index.html"/>
</menu>
<menu ref="reports" inherit="bottom"/>
</body>
</project>

View File

@ -219,8 +219,18 @@ public class Dispatcher implements RequestDispatcher
_contextHandler.handle(_pathInContext, baseRequest, (HttpServletRequest)request, (HttpServletResponse)response);
if (!baseRequest.getHttpChannelState().isAsync())
baseRequest.getResponse().softClose();
// If we are not async and not closed already, then close via the possibly wrapped response.
if (!baseRequest.getHttpChannelState().isAsync() && !baseResponse.getHttpOutput().isClosed())
{
try
{
response.getOutputStream().close();
}
catch (IllegalStateException e)
{
response.getWriter().close();
}
}
}
}
finally

View File

@ -906,7 +906,7 @@ public class HttpChannelState
throw new IllegalStateException("Response is " + _outputState);
response.setStatus(code);
response.softClose();
response.errorClose();
request.setAttribute(ErrorHandler.ERROR_CONTEXT, request.getErrorContext());
request.setAttribute(ERROR_REQUEST_URI, request.getRequestURI());

View File

@ -143,12 +143,14 @@ public class Response implements HttpServletResponse
public void reopen()
{
// Make the response mutable and reopen output.
setErrorSent(false);
_out.reopen();
}
public void softClose()
public void errorClose()
{
// Make the response immutable and soft close the output.
setErrorSent(true);
_out.softClose();
}

View File

@ -65,6 +65,7 @@ public class ErrorHandler extends AbstractHandler
public static final String ERROR_PAGE = "org.eclipse.jetty.server.error_page";
public static final String ERROR_CONTEXT = "org.eclipse.jetty.server.error_context";
boolean _showServlet = true;
boolean _showStacks = true;
boolean _disableStacks = false;
boolean _showMessageInTitle = true;
@ -441,13 +442,18 @@ public class ErrorHandler extends AbstractHandler
writer.printf("URI: %s%n", request.getRequestURI());
writer.printf("STATUS: %s%n", code);
writer.printf("MESSAGE: %s%n", message);
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
if (isShowServlet())
{
writer.printf("SERVLET: %s%n", request.getAttribute(Dispatcher.ERROR_SERVLET_NAME));
}
Throwable cause = (Throwable)request.getAttribute(Dispatcher.ERROR_EXCEPTION);
while (cause != null)
{
writer.printf("CAUSED BY %s%n", cause);
if (_showStacks && !_disableStacks)
{
cause.printStackTrace(writer);
}
cause = cause.getCause();
}
}
@ -461,7 +467,7 @@ public class ErrorHandler extends AbstractHandler
json.put("url", request.getRequestURI());
json.put("status", Integer.toString(code));
json.put("message", message);
if (servlet != null)
if (isShowServlet() && servlet != null)
{
json.put("servlet", servlet.toString());
}
@ -539,6 +545,22 @@ public class ErrorHandler extends AbstractHandler
_cacheControl = cacheControl;
}
/**
* @return True if the error page will show the Servlet that generated the error
*/
public boolean isShowServlet()
{
return _showServlet;
}
/**
* @param showServlet True if the error page will show the Servlet that generated the error
*/
public void setShowServlet(boolean showServlet)
{
_showServlet = showServlet;
}
/**
* @return True if stack traces are shown in the error pages
*/

View File

@ -21,6 +21,7 @@ package org.eclipse.jetty.server.session;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.Function;
import javax.servlet.http.HttpServletRequest;
@ -334,12 +335,13 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
*
* @param id The session to retrieve
* @param enter if true, the usage count of the session will be incremented
* @return
* @throws Exception
* @return the session if it exists, null otherwise
* @throws Exception if the session cannot be loaded
*/
protected Session getAndEnter(String id, boolean enter) throws Exception
{
Session session = null;
AtomicReference<Exception> exception = new AtomicReference<Exception>();
session = doComputeIfAbsent(id, k ->
{
@ -365,11 +367,15 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
}
catch (Exception e)
{
LOG.warn("Error loading session {}", id, e);
exception.set(e);
return null;
}
});
Exception ex = exception.get();
if (ex != null)
throw ex;
if (session != null)
{
try (AutoLock lock = session.lock())
@ -741,7 +747,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
}
catch (Exception e)
{
LOG.warn("Passivation of idle session {} failed", session.getId(), e);
LOG.warn("Passivation of idle session {} failed", session.getId());
LOG.warn(e);
}
}
}
@ -838,7 +845,8 @@ public abstract class AbstractSessionCache extends ContainerLifeCycle implements
}
catch (Exception e)
{
LOG.warn("Save of new session {} failed", id, e);
LOG.warn("Save of new session {} failed", id);
LOG.warn(e);
}
return session;
}

View File

@ -301,12 +301,13 @@ public class DefaultSessionIdManager extends ContainerLifeCycle implements Sessi
}
if (LOG.isDebugEnabled())
LOG.debug("Checked {}, in use:", id, inUse);
LOG.debug("Checked {}, in use: {}", id, inUse);
return inUse;
}
catch (Exception e)
{
LOG.warn("Problem checking if id {} is in use", id, e);
LOG.warn("Problem checking if id {} is in use", id);
LOG.warn(e);
return false;
}
}

View File

@ -258,7 +258,8 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
catch (NumberFormatException e)
{
LOG.warn("Not valid session filename {}", p.getFileName(), e);
LOG.warn("Not valid session filename {}", p.getFileName());
LOG.warn(e);
}
}
@ -299,7 +300,8 @@ public class FileSessionDataStore extends AbstractSessionDataStore
}
catch (Exception x)
{
LOG.warn("Unable to delete unrestorable file {} for session {}", filename, id, x);
LOG.warn("Unable to delete unrestorable file {} for session {}", filename, id);
LOG.warn(x);
}
}
throw e;

View File

@ -865,7 +865,8 @@ public class JDBCSessionDataStore extends AbstractSessionDataStore
}
catch (Exception e)
{
LOG.warn("{} Problem checking if potentially expired session {} exists in db", _context.getWorkerName(), k, e);
LOG.warn("{} Problem checking if potentially expired session {} exists in db", _context.getWorkerName(), k);
LOG.warn(e);
}
}
}

View File

@ -87,7 +87,7 @@ public class NullSessionCache extends AbstractSessionCache
@Override
public void setEvictionPolicy(int evictionTimeout)
{
LOG.warn("Ignoring eviction setting:" + evictionTimeout);
LOG.warn("Ignoring eviction setting: {}", evictionTimeout);
}
@Override

View File

@ -900,7 +900,8 @@ public class SessionHandler extends ScopedHandler
}
catch (Exception e)
{
LOG.warn("Invalidating session {} found to be expired when requested", id, e);
LOG.warn("Invalidating session {} found to be expired when requested", id);
LOG.warn(e);
}
return null;
@ -912,6 +913,7 @@ public class SessionHandler extends ScopedHandler
}
catch (UnreadableSessionDataException e)
{
LOG.warn("Error loading session {}", id);
LOG.warn(e);
try
{
@ -920,7 +922,8 @@ public class SessionHandler extends ScopedHandler
}
catch (Exception x)
{
LOG.warn("Error cross-context invalidating unreadable session {}", id, x);
LOG.warn("Error cross-context invalidating unreadable session {}", id);
LOG.warn(x);
}
return null;
}
@ -1195,7 +1198,7 @@ public class SessionHandler extends ScopedHandler
}
catch (Exception e)
{
LOG.warn("Session listener threw exception", e);
LOG.warn(e);
}
//call the attribute removed listeners and finally mark it as invalid
session.finishInvalidate();

View File

@ -18,7 +18,9 @@
package org.eclipse.jetty.servlet;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
@ -32,10 +34,12 @@ import javax.servlet.RequestDispatcher;
import javax.servlet.Servlet;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletOutputStream;
import javax.servlet.ServletRequest;
import javax.servlet.ServletRequestWrapper;
import javax.servlet.ServletResponse;
import javax.servlet.ServletResponseWrapper;
import javax.servlet.WriteListener;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletRequestWrapper;
@ -115,8 +119,9 @@ public class DispatcherTest
String expected =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
"Content-Length: 7\r\n" +
"\r\n" +
"FORWARD";
String responses = _connector.getResponse("GET /context/ForwardServlet?do=assertforward&do=more&test=1 HTTP/1.0\n\n");
@ -132,8 +137,9 @@ public class DispatcherTest
String expected =
"HTTP/1.1 200 OK\r\n" +
"Content-Type: text/html\r\n" +
"Content-Length: 0\r\n" +
"\r\n";
"Content-Length: 7\r\n" +
"\r\n" +
"FORWARD";
String responses = _connector.getResponse("GET /context/ForwardServlet?do=assertforward&foreign=%d2%e5%ec%ef%e5%f0%e0%f2%f3%f0%e0&test=1 HTTP/1.0\n\n");
assertEquals(expected, responses);
@ -202,7 +208,9 @@ public class DispatcherTest
String expected =
"HTTP/1.1 200 OK\r\n" +
"\r\n";
"Content-Length: 7\r\n" +
"\r\n" +
"INCLUDE";
String responses = _connector.getResponse("GET /context/IncludeServlet?do=assertinclude&do=more&test=1 HTTP/1.0\n\n");
@ -218,7 +226,9 @@ public class DispatcherTest
String expected =
"HTTP/1.1 200 OK\r\n" +
"\r\n";
"Content-Length: 7\r\n" +
"\r\n" +
"INCLUDE";
String responses = _connector.getResponse("GET /context/ForwardServlet/forwardpath?do=include HTTP/1.0\n\n");
@ -234,7 +244,9 @@ public class DispatcherTest
String expected =
"HTTP/1.1 200 OK\r\n" +
"\r\n";
"Content-Length: 7\r\n" +
"\r\n" +
"FORWARD";
String responses = _connector.getResponse("GET /context/IncludeServlet/includepath?do=forward HTTP/1.0\n\n");
@ -374,6 +386,88 @@ public class DispatcherTest
assertThat(rechoResponse, containsString("txeTohce"));
}
@Test
public void testWrappedForwardCloseIntercepted() throws Exception
{
// Add filter that wraps response, intercepts close and writes after doChain
_contextHandler.addFilter(WrappingFilter.class, "/*", EnumSet.of(DispatcherType.REQUEST));
testForward();
}
public static class WrappingFilter implements Filter
{
@Override
public void init(FilterConfig filterConfig) throws ServletException
{
}
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException
{
ResponseWrapper wrapper = new ResponseWrapper((HttpServletResponse)response);
chain.doFilter(request, wrapper);
wrapper.sendResponse(response.getOutputStream());
}
@Override
public void destroy()
{
}
}
public static class ResponseWrapper extends HttpServletResponseWrapper
{
ByteArrayOutputStream buffer = new ByteArrayOutputStream();
public ResponseWrapper(HttpServletResponse response)
{
super(response);
}
@Override
public ServletOutputStream getOutputStream() throws IOException
{
return new ServletOutputStream()
{
@Override
public boolean isReady()
{
return true;
}
@Override
public void setWriteListener(WriteListener writeListener)
{
throw new UnsupportedOperationException();
}
@Override
public void write(int b) throws IOException
{
buffer.write(b);
}
@Override
public void write(byte[] b, int off, int len) throws IOException
{
buffer.write(b, off, len);
}
@Override
public void close() throws IOException
{
buffer.close();
}
};
}
public void sendResponse(OutputStream out) throws IOException
{
out.write(buffer.toByteArray());
out.close();
}
}
public static class ForwardServlet extends HttpServlet implements Servlet
{
@Override
@ -649,6 +743,7 @@ public class DispatcherTest
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().print(request.getDispatcherType().toString());
}
}
@ -697,6 +792,7 @@ public class DispatcherTest
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().print(request.getDispatcherType().toString());
}
}
@ -725,6 +821,7 @@ public class DispatcherTest
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().print(request.getDispatcherType().toString());
}
}
@ -762,6 +859,7 @@ public class DispatcherTest
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().print(request.getDispatcherType().toString());
}
}
@ -797,6 +895,7 @@ public class DispatcherTest
response.setContentType("text/html");
response.setStatus(HttpServletResponse.SC_OK);
response.getOutputStream().print(request.getDispatcherType().toString());
}
}
}

View File

@ -21,9 +21,9 @@ package org.eclipse.jetty.util.thread;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.Executor;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.locks.Condition;
import org.eclipse.jetty.util.ProcessorUtils;
import org.eclipse.jetty.util.annotation.ManagedAttribute;
@ -217,7 +217,8 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
}
int size = _size.decrementAndGet();
thread.offer(task);
if (!thread.offer(task))
return false;
if (size == 0 && task != STOP)
startReservedThread();
@ -264,20 +265,25 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
private class ReservedThread implements Runnable
{
private final AutoLock _lock = new AutoLock();
private final Condition _wakeup = _lock.newCondition();
private final SynchronousQueue<Runnable> _task = new SynchronousQueue<>();
private boolean _starting = true;
private Runnable _task = null;
public void offer(Runnable task)
public boolean offer(Runnable task)
{
if (LOG.isDebugEnabled())
LOG.debug("{} offer {}", this, task);
try (AutoLock lock = _lock.lock())
try
{
_task = task;
_wakeup.signal();
_task.put(task);
return true;
}
catch (Throwable e)
{
LOG.ignore(e);
_size.getAndIncrement();
_stack.addFirst(this);
return false;
}
}
@ -296,37 +302,14 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
if (!isRunning())
return STOP;
boolean idle = false;
try (AutoLock lock = _lock.lock())
try
{
if (_task == null)
{
try
{
if (_idleTime == 0)
_wakeup.await();
else
idle = !_wakeup.await(_idleTime, _idleTimeUnit);
}
catch (InterruptedException e)
{
LOG.ignore(e);
}
}
else
{
Runnable task = _task;
_task = null;
if (LOG.isDebugEnabled())
LOG.debug("{} task={}", this, task);
Runnable task = _idleTime <= 0 ? _task.take() : _task.poll(_idleTime, _idleTimeUnit);
if (LOG.isDebugEnabled())
LOG.debug("{} task={}", this, task);
if (task != null)
return task;
}
}
if (idle)
{
// Because threads are held in a stack, excess threads will be
// idle. However, we cannot remove threads from the bottom of
// the stack, so we submit a poison pill job to stop the thread
@ -336,6 +319,10 @@ public class ReservedThreadExecutor extends AbstractLifeCycle implements TryExec
LOG.debug("{} IDLE", this);
tryExecute(STOP);
}
catch (InterruptedException e)
{
LOG.ignore(e);
}
}
}

View File

@ -20,18 +20,24 @@ package org.eclipse.jetty.server.session;
import java.util.Collections;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
import javax.servlet.http.HttpSession;
import javax.servlet.http.HttpSessionActivationListener;
import javax.servlet.http.HttpSessionEvent;
import org.eclipse.jetty.server.Request;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.hamcrest.MatcherAssert.assertThat;
import static org.hamcrest.Matchers.containsInAnyOrder;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertNotEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue;
@ -42,6 +48,58 @@ import static org.junit.jupiter.api.Assertions.assertTrue;
*/
public abstract class AbstractSessionCacheTest
{
public static class UnreadableSessionDataStore extends AbstractSessionDataStore
{
int _count;
int _calls;
SessionData _data;
public UnreadableSessionDataStore(int count, SessionData data)
{
_count = count;
_data = data;
}
@Override
public boolean isPassivating()
{
return false;
}
@Override
public boolean exists(String id) throws Exception
{
return _data != null;
}
@Override
public boolean delete(String id) throws Exception
{
_data = null;
return true;
}
@Override
public void doStore(String id, SessionData data, long lastSaveTime) throws Exception
{
}
@Override
public SessionData doLoad(String id) throws Exception
{
++_calls;
if (_calls <= _count)
throw new UnreadableSessionDataException(id, _context, new IllegalStateException("Throw for test"));
else
return _data;
}
@Override
public Set<String> doGetExpired(Set<String> candidates)
{
return null;
}
}
public static class TestSessionActivationListener implements HttpSessionActivationListener
{
@ -64,7 +122,58 @@ public abstract class AbstractSessionCacheTest
public abstract AbstractSessionCacheFactory newSessionCacheFactory(int evictionPolicy, boolean saveOnCreate,
boolean saveOnInactiveEvict, boolean removeUnloadableSessions,
boolean flushOnResponseCommit);
/**
* Test that a session that exists in the datastore, but that cannot be
* read will be invalidated and deleted, and thus a request to re-use that
* same id will not succeed.
*
* @throws Exception
*/
@Test
public void testUnreadableSession() throws Exception
{
Server server = new Server();
server.setSessionIdManager(new DefaultSessionIdManager(server));
ServletContextHandler context = new ServletContextHandler(ServletContextHandler.SESSIONS);
context.setContextPath("/test");
context.setServer(server);
server.setHandler(context);
AbstractSessionCacheFactory cacheFactory = newSessionCacheFactory(SessionCache.NEVER_EVICT, false, false, false, false);
SessionCache cache = cacheFactory.getSessionCache(context.getSessionHandler());
//prefill the datastore with a session that will be treated as unreadable
UnreadableSessionDataStore store = new UnreadableSessionDataStore(1, new SessionData("1234", "/test", "0.0.0.0", System.currentTimeMillis(), 0,0, -1));
cache.setSessionDataStore(store);
context.getSessionHandler().setSessionCache(cache);
server.start();
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
//check that session 1234 cannot be read, ie returns null AND
//that it is deleted in the datastore
Session session = context.getSessionHandler().getSession("1234");
assertNull(session);
assertFalse(store.exists("1234"));
//now try to make a session with the same id as if from a request with
//a SESSION_ID cookie set - the id from the cookie should not be able to
//be re-used because we just deleted the session with that id. Ids cannot
//be re-used (unless another context is already using that same id (ie cross
//context dispatch), which is not the case in this test).
Request request = new Request(null, null);
request.setRequestedSessionId("1234");
HttpSession newSession = context.getSessionHandler().newHttpSession(request);
assertNotEquals("1234", newSession.getId());
}
finally
{
server.stop();
}
}
/**
* Test that a new Session object can be created from
* previously persisted data (SessionData).

View File

@ -32,6 +32,8 @@ import org.eclipse.jetty.client.api.ContentResponse;
import org.eclipse.jetty.client.api.Request;
import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.log.Log;
import org.eclipse.jetty.util.log.StacklessLogging;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertEquals;
@ -177,7 +179,7 @@ public class SessionEvictionFailureTest
int port1 = server.getPort();
HttpClient client = new HttpClient();
client.start();
try
try (StacklessLogging stackless = new StacklessLogging(Log.getLogger("org.eclipse.jetty.server.session")))
{
String url = "http://localhost:" + port1 + contextPath + servletMapping;