Repository: wicket Updated Branches: refs/heads/suspend_request_cycle [created] 64f746fba
experimenting with suspension of requestCycle via servlet 3 async Notes: - enabled async-supported in web.xml for ComponentReferenceApplication - LinkPage shows usage of RequestCycle#suspend() - pageAccessSynchronizer uses the requestCycle instead of the thread now, since two threads might process a page (PageAccessSynchronizerTest still fails) - ServletWebRequest must not call httpServletRequest#getContextPath(), since it returns null during async processing Project: http://git-wip-us.apache.org/repos/asf/wicket/repo Commit: http://git-wip-us.apache.org/repos/asf/wicket/commit/64f746fb Tree: http://git-wip-us.apache.org/repos/asf/wicket/tree/64f746fb Diff: http://git-wip-us.apache.org/repos/asf/wicket/diff/64f746fb Branch: refs/heads/suspend_request_cycle Commit: 64f746fba8bb0235dd870e2a9d2ba1eaa192ada5 Parents: 05a1741 Author: Sven Meier <svenme...@apache.org> Authored: Fri Feb 17 18:30:51 2017 +0100 Committer: Sven Meier <svenme...@apache.org> Committed: Fri Feb 17 19:58:44 2017 +0100 ---------------------------------------------------------------------- .../wicket/page/PageAccessSynchronizer.java | 45 +++--- .../http/servlet/ServletWebRequest.java | 2 +- .../wicket/request/cycle/RequestCycle.java | 153 ++++++++++++++++++- .../wicket/examples/compref/LinkPage.java | 15 ++ wicket-examples/src/main/webapp/WEB-INF/web.xml | 22 +-- 5 files changed, 191 insertions(+), 46 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/wicket/blob/64f746fb/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java b/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java index 86fbb8b..df359e6 100644 --- a/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java +++ b/wicket-core/src/main/java/org/apache/wicket/page/PageAccessSynchronizer.java @@ -23,6 +23,7 @@ import java.util.concurrent.ConcurrentMap; import java.util.function.Supplier; import org.apache.wicket.Application; +import org.apache.wicket.request.cycle.RequestCycle; import org.apache.wicket.settings.ExceptionSettings.ThreadDumpStrategy; import org.apache.wicket.util.LazyInitializer; import org.apache.wicket.util.lang.Threads; @@ -93,8 +94,8 @@ public class PageAccessSynchronizer implements Serializable */ public void lockPage(int pageId) throws CouldNotLockPageException { - final Thread thread = Thread.currentThread(); - final PageLock lock = new PageLock(pageId, thread); + final RequestCycle cycle = RequestCycle.get(); + final PageLock lock = new PageLock(pageId, cycle); final Time start = Time.now(); boolean locked = false; @@ -110,12 +111,12 @@ public class PageAccessSynchronizer implements Serializable if (isDebugEnabled) { logger.debug("'{}' attempting to acquire lock to page with id '{}'", - thread.getName(), pageId); + cycle.getStartTime(), pageId); } previous = locks.get().putIfAbsent(pageId, lock); - if (previous == null || previous.thread == thread) + if (previous == null || previous.cycle == cycle) { // first thread to acquire lock or lock is already owned by this thread locked = true; @@ -134,7 +135,7 @@ public class PageAccessSynchronizer implements Serializable { if (isDebugEnabled) { - logger.debug("{} acquired lock to page {}", thread.getName(), pageId); + logger.debug("{} acquired lock to page {}", cycle.getStartTime(), pageId); } } else @@ -144,8 +145,8 @@ public class PageAccessSynchronizer implements Serializable logger.warn( "Thread '{}' failed to acquire lock to page with id '{}', attempted for {} out of allowed {}." + " The thread that holds the lock has name '{}'.", - thread.getName(), pageId, start.elapsedSince(), timeout, - previous.thread.getName()); + cycle.getStartTime(), pageId, start.elapsedSince(), timeout, + previous.cycle.getStartTime()); if (Application.exists()) { ThreadDumpStrategy strategy = Application.get() @@ -157,7 +158,7 @@ public class PageAccessSynchronizer implements Serializable Threads.dumpAllThreads(logger); break; case THREAD_HOLDING_LOCK : - Threads.dumpSingleThread(logger, previous.thread); +// Threads.dumpSingleThread(logger, previous.thread); break; case NO_THREADS : default : @@ -165,7 +166,7 @@ public class PageAccessSynchronizer implements Serializable } } } - throw new CouldNotLockPageException(pageId, thread.getName(), timeout); + throw new CouldNotLockPageException(pageId, "" + cycle.getStartTime(), timeout); } } @@ -190,7 +191,7 @@ public class PageAccessSynchronizer implements Serializable private void internalUnlockPages(final Integer pageId) { - final Thread thread = Thread.currentThread(); + final RequestCycle cycle = RequestCycle.get(); final Iterator<PageLock> locks = this.locks.get().values().iterator(); final boolean isDebugEnabled = logger.isDebugEnabled(); @@ -200,12 +201,12 @@ public class PageAccessSynchronizer implements Serializable // remove all locks held by this thread if 'pageId' is not specified // otherwise just the lock for this 'pageId' final PageLock lock = locks.next(); - if ((pageId == null || pageId == lock.pageId) && lock.thread == thread) + if ((pageId == null || pageId == lock.pageId) && lock.cycle == cycle) { locks.remove(); if (isDebugEnabled) { - logger.debug("'{}' released lock to page with id '{}'", thread.getName(), + logger.debug("'{}' released lock to page with id '{}'", cycle.getStartTime(), lock.pageId); } // if any locks were removed notify threads waiting for a lock @@ -288,8 +289,8 @@ public class PageAccessSynchronizer implements Serializable /** page id */ private final int pageId; - /** thread that owns the lock */ - private final Thread thread; + /** cycle that owns the lock */ + private final RequestCycle cycle; private volatile boolean released = false; @@ -297,12 +298,12 @@ public class PageAccessSynchronizer implements Serializable * Constructor * * @param pageId - * @param thread + * @param cycle */ - public PageLock(int pageId, Thread thread) + public PageLock(int pageId, RequestCycle cycle) { this.pageId = pageId; - this.thread = thread; + this.cycle = cycle; } /** @@ -316,9 +317,9 @@ public class PageAccessSynchronizer implements Serializable /** * @return thread that owns the lock */ - public Thread getThread() + public RequestCycle getCycle() { - return thread; + return cycle; } final synchronized void waitForRelease(long remaining, boolean isDebugEnabled) @@ -331,7 +332,7 @@ public class PageAccessSynchronizer implements Serializable { logger.debug( "lock for page with id {} no longer locked by {}, falling through", pageId, - thread.getName()); + cycle.getStartTime()); } return; } @@ -339,7 +340,7 @@ public class PageAccessSynchronizer implements Serializable if (isDebugEnabled) { logger.debug("{} waiting for lock to page {} for {}", - thread.getName(), pageId, Duration.milliseconds(remaining)); + cycle.getStartTime(), pageId, Duration.milliseconds(remaining)); } try { @@ -355,7 +356,7 @@ public class PageAccessSynchronizer implements Serializable { if (isDebugEnabled) { - logger.debug("'{}' notifying blocked threads", thread.getName()); + logger.debug("'{}' notifying blocked threads", cycle.getStartTime()); } released = true; notifyAll(); http://git-wip-us.apache.org/repos/asf/wicket/blob/64f746fb/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebRequest.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebRequest.java b/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebRequest.java index c668cdf..91b6ddf 100644 --- a/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebRequest.java +++ b/wicket-core/src/main/java/org/apache/wicket/protocol/http/servlet/ServletWebRequest.java @@ -181,7 +181,7 @@ public class ServletWebRequest extends WebRequest } StringBuilder url = new StringBuilder(); uri = Strings.stripJSessionId(uri); - String contextPath = httpServletRequest.getContextPath(); + String contextPath = getContextPath(); if (LOG.isDebugEnabled()) { http://git-wip-us.apache.org/repos/asf/wicket/blob/64f746fb/wicket-core/src/main/java/org/apache/wicket/request/cycle/RequestCycle.java ---------------------------------------------------------------------- diff --git a/wicket-core/src/main/java/org/apache/wicket/request/cycle/RequestCycle.java b/wicket-core/src/main/java/org/apache/wicket/request/cycle/RequestCycle.java index 272fcde..ea513bb 100644 --- a/wicket-core/src/main/java/org/apache/wicket/request/cycle/RequestCycle.java +++ b/wicket-core/src/main/java/org/apache/wicket/request/cycle/RequestCycle.java @@ -18,6 +18,11 @@ package org.apache.wicket.request.cycle; import java.util.Optional; +import javax.servlet.AsyncContext; +import javax.servlet.AsyncEvent; +import javax.servlet.AsyncListener; +import javax.servlet.http.HttpServletRequest; + import org.apache.wicket.Application; import org.apache.wicket.MetaDataEntry; import org.apache.wicket.MetaDataKey; @@ -45,6 +50,7 @@ import org.apache.wicket.request.UrlRenderer; import org.apache.wicket.request.component.IRequestablePage; import org.apache.wicket.request.handler.resource.ResourceReferenceRequestHandler; import org.apache.wicket.request.handler.resource.ResourceRequestHandler; +import org.apache.wicket.request.http.WebResponse; import org.apache.wicket.request.mapper.parameter.PageParameters; import org.apache.wicket.request.resource.IResource; import org.apache.wicket.request.resource.ResourceReference; @@ -123,6 +129,8 @@ public class RequestCycle implements IRequestCycle, IEventSink private Response activeResponse; + private SuspensionImpl suspension; + /** * Construct. * @@ -222,7 +230,9 @@ public class RequestCycle implements IRequestCycle, IEventSink } finally { - detach(); + if (suspension == null) { + detach(); + } } return result; } @@ -283,7 +293,12 @@ public class RequestCycle implements IRequestCycle, IEventSink IRequestHandler next = requestHandlerExecutor.execute(handler); listeners.onRequestHandlerExecuted(this, handler); - handler = next; + if (suspension == null) { + handler = next; + } else { + handler = null; + suspension.handler = next; + } } catch (RuntimeException e) { @@ -305,6 +320,22 @@ public class RequestCycle implements IRequestCycle, IEventSink } /** + * Suspend the request cycle to be resumed on another thread. + * + * @param timeout {@literal 0} for no timeout + * @return suspension to be resumed + */ + public Suspension suspend(long timeout) { + HttpServletRequest httpServletRequest = (HttpServletRequest)request.getContainerRequest(); + + AsyncContext context = httpServletRequest.startAsync(); + context.setTimeout(timeout); + suspension = new SuspensionImpl(context); + + return suspension; + } + + /** * Execute a requestHandler for the given exception. * * @param exception @@ -925,4 +956,122 @@ public class RequestCycle implements IRequestCycle, IEventSink } + public interface Suspension { + void resume(); + } + + class SuspensionImpl implements Suspension, AsyncListener { + + AsyncContext asyncContext; + + Application application; + + ClassLoader classLoader; + + IRequestHandler handler; + + SuspensionImpl(AsyncContext asyncContext) + { + this.asyncContext = asyncContext; + this.asyncContext.addListener(this); + + application = Application.get(); + classLoader = Thread.currentThread().getContextClassLoader(); + } + + /** + * Resume the suspension. + */ + public synchronized void resume() + { + if (suspension == null) { + throw new WicketRuntimeException("not longer suspended"); + } + suspension = null; + + final ThreadContext previousThreadContext = ThreadContext.detach(); + ThreadContext.setApplication(application); + + final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader(); + if (previousClassLoader != classLoader) + { + Thread.currentThread().setContextClassLoader(classLoader); + } + + try + { + set(RequestCycle.this); + + execute(handler); + + ((WebResponse)getResponse()).flush(); + + detach(); + } + finally + { + set(null); + + ThreadContext.restore(previousThreadContext); + + if (classLoader != previousClassLoader) + { + Thread.currentThread().setContextClassLoader(previousClassLoader); + } + + asyncContext.complete(); + } + } + + @Override + public void onComplete(AsyncEvent event) + { + } + + @Override + public synchronized void onTimeout(AsyncEvent event) + { + suspension = null; + + final ThreadContext previousThreadContext = ThreadContext.detach(); + ThreadContext.setApplication(application); + + final ClassLoader previousClassLoader = Thread.currentThread().getContextClassLoader(); + if (previousClassLoader != classLoader) + { + Thread.currentThread().setContextClassLoader(classLoader); + } + + try + { + set(RequestCycle.this); + + detach(); + } + finally + { + set(null); + + ThreadContext.restore(previousThreadContext); + + if (classLoader != previousClassLoader) + { + Thread.currentThread().setContextClassLoader(previousClassLoader); + } + + asyncContext.complete(); + } + } + + @Override + public void onError(AsyncEvent event) + { + } + + + @Override + public void onStartAsync(AsyncEvent event) + { + } + } } \ No newline at end of file http://git-wip-us.apache.org/repos/asf/wicket/blob/64f746fb/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LinkPage.java ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LinkPage.java b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LinkPage.java index 403838a..b5b8a9b 100644 --- a/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LinkPage.java +++ b/wicket-examples/src/main/java/org/apache/wicket/examples/compref/LinkPage.java @@ -21,6 +21,7 @@ import org.apache.wicket.lambda.Lambdas; import org.apache.wicket.markup.html.basic.Label; import org.apache.wicket.markup.html.link.Link; import org.apache.wicket.model.PropertyModel; +import org.apache.wicket.request.cycle.RequestCycle.Suspension; /** * Page with examples on {@link org.apache.wicket.markup.html.link.Link}. @@ -111,6 +112,20 @@ public class LinkPage extends WicketExamplePage public void onClick() { count3.increment(); + + Suspension suspension = getRequestCycle().suspend(0); + + new Thread(() -> { + try + { + Thread.sleep(5000); + } + catch (InterruptedException interrupted) + { + } + + suspension.resume(); + }).start(); } } add(new ButtonLink("link3")); http://git-wip-us.apache.org/repos/asf/wicket/blob/64f746fb/wicket-examples/src/main/webapp/WEB-INF/web.xml ---------------------------------------------------------------------- diff --git a/wicket-examples/src/main/webapp/WEB-INF/web.xml b/wicket-examples/src/main/webapp/WEB-INF/web.xml index e23b1a1..b100851 100644 --- a/wicket-examples/src/main/webapp/WEB-INF/web.xml +++ b/wicket-examples/src/main/webapp/WEB-INF/web.xml @@ -251,6 +251,7 @@ <filter> <filter-name>ComponentReferenceApplication</filter-name> <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> + <async-supported>true</async-supported> <init-param> <param-name>applicationClassName</param-name> <param-value>org.apache.wicket.examples.compref.ComponentReferenceApplication</param-value> @@ -796,27 +797,6 @@ </filter-mapping> - <!-- CDI EXAMPLE APPLICATION --> - <filter> - <filter-name>CdiApplication</filter-name> - <filter-class>org.apache.wicket.protocol.http.WicketFilter</filter-class> - <init-param> - <param-name>applicationClassName</param-name> - <param-value>org.apache.wicket.examples.cdi.CdiApplication</param-value> - </init-param> - </filter> - <filter-mapping> - <filter-name>CdiApplication</filter-name> - <url-pattern>/cdi/*</url-pattern> - </filter-mapping> - - <listener> - <!-- initialize Weld in servlet environment --> - <listener-class>org.jboss.weld.environment.servlet.Listener</listener-class> - </listener> - <!-- END CDI EXAMPLE APPLICATION --> - - <!-- Bean Validation EXAMPLE APPLICATION --> <filter> <filter-name>BeanValidation</filter-name>