diff options
author | Leif Åstrand <leif@vaadin.com> | 2013-05-22 15:56:29 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2013-05-28 07:47:48 +0000 |
commit | 4d7f190b7f36a10b16e74b1dab8ed0a274841ae1 (patch) | |
tree | 317825e84f6a8004a7f40429effcedbeb81d35b8 | |
parent | 2882cf98756974f40ba98d66c7d7318db6acd538 (diff) | |
download | vaadin-framework-4d7f190b7f36a10b16e74b1dab8ed0a274841ae1.tar.gz vaadin-framework-4d7f190b7f36a10b16e74b1dab8ed0a274841ae1.zip |
Make access() enqueue the runnable if the session is locked (#11897)
Change-Id: If162e81a29bbc982857e2a165a983e161ea837ee
11 files changed, 632 insertions, 55 deletions
diff --git a/server/src/com/vaadin/server/RequestHandler.java b/server/src/com/vaadin/server/RequestHandler.java index 873752c5f2..097a3e034b 100644 --- a/server/src/com/vaadin/server/RequestHandler.java +++ b/server/src/com/vaadin/server/RequestHandler.java @@ -37,7 +37,8 @@ public interface RequestHandler extends Serializable { * using VaadinSession or anything inside the VaadinSession you must ensure * the session is locked. This can be done by extending * {@link SynchronizedRequestHandler} or by using - * {@link VaadinSession#access(Runnable)} or {@link UI#access(Runnable)}. + * {@link VaadinSession#accessSynchronously(Runnable)} or + * {@link UI#accessSynchronously(Runnable)}. * </p> * * @param session diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index af0c280c19..2cb7f9059e 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -407,12 +407,12 @@ public abstract class VaadinService implements Serializable { */ public void fireSessionDestroy(VaadinSession vaadinSession) { final VaadinSession session = vaadinSession; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { ArrayList<UI> uis = new ArrayList<UI>(session.getUIs()); for (final UI ui : uis) { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { /* @@ -1087,7 +1087,7 @@ public abstract class VaadinService implements Serializable { private void removeClosedUIs(final VaadinSession session) { ArrayList<UI> uis = new ArrayList<UI>(session.getUIs()); for (final UI ui : uis) { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { if (ui.isClosing()) { @@ -1245,7 +1245,7 @@ public abstract class VaadinService implements Serializable { if (session != null) { final VaadinSession finalSession = session; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { cleanupSession(finalSession); @@ -1254,7 +1254,7 @@ public abstract class VaadinService implements Serializable { final long duration = (System.nanoTime() - (Long) request .getAttribute(REQUEST_START_TIME_ATTRIBUTE)) / 1000000; - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { finalSession.setLastRequestDuration(duration); @@ -1542,8 +1542,9 @@ public abstract class VaadinService implements Serializable { /** * Checks that another {@link VaadinSession} instance is not locked. This is - * internally used by {@link VaadinSession#access(Runnable)} and - * {@link UI#access(Runnable)} to help avoid causing deadlocks. + * internally used by {@link VaadinSession#accessSynchronously(Runnable)} + * and {@link UI#accessSynchronously(Runnable)} to help avoid causing + * deadlocks. * * @since 7.1 * @param session diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java index 317ea6cf7b..9625a3f350 100644 --- a/server/src/com/vaadin/server/VaadinSession.java +++ b/server/src/com/vaadin/server/VaadinSession.java @@ -26,6 +26,11 @@ import java.util.List; import java.util.Locale; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ConcurrentLinkedQueue; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.FutureTask; +import java.util.concurrent.TimeUnit; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.logging.Logger; @@ -130,6 +135,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { private transient Lock lock; + /* + * Pending tasks can't be serialized and the queue should be empty when the + * session is serialized as long as it doesn't happen while some other + * thread has the lock. + */ + private transient final ConcurrentLinkedQueue<FutureTask<Void>> pendingAccessQueue = new ConcurrentLinkedQueue<FutureTask<Void>>(); + /** * Create a new service session tied to a Vaadin service * @@ -820,9 +832,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { public void unlock() { assert hasLock(); try { + /* + * Run pending tasks and push if the reentrant lock will actually be + * released by this unlock() invocation. + */ if (((ReentrantLock) getLockInstance()).getHoldCount() == 1) { - // Only push if the reentrant lock will actually be released by - // this unlock() invocation. + runPendingAccessTasks(); + for (UI ui : getUIs()) { if (ui.getPushMode() == PushMode.AUTOMATIC) { ui.push(); @@ -1063,23 +1079,30 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** - * Provides exclusive access to this session from outside a request handling - * thread. + * Locks this session and runs the provided Runnable right away. * <p> - * The given runnable is executed while holding the session lock to ensure - * exclusive access to this session. The session and related thread locals - * are set properly before executing the runnable. + * It is generally recommended to use {@link #access(Runnable)} instead of + * this method for accessing a session from a different thread as + * {@link #access(Runnable)} can be used while holding the lock of another + * session. To avoid causing deadlocks, this methods throws an exception if + * it is detected than another session is also locked by the current thread. * </p> * <p> - * RPC handlers for components inside this session do not need this method - * as the session is automatically locked by the framework during request - * handling. - * </p> - * <p> - * Note that calling this method while another session is locked by the - * current thread will cause an exception. This is to prevent deadlock - * situations when two threads have locked one session each and are both - * waiting for the lock for the other session. + * This method behaves differently than {@link #access(Runnable)} in some + * situations: + * <ul> + * <li>If the current thread is currently holding the lock of this session, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time.</li> + * <li>If some other thread is currently holding the lock for this session, + * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock + * to be available whereas {@link #access(Runnable)} defers the task to a + * later point in time.</li> + * <li>If this session is currently not locked, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time unless + * there are UIs with automatic push enabled.</li> + * </ul> * </p> * * @param runnable @@ -1088,12 +1111,14 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { * @throws IllegalStateException * if the current thread holds the lock for another session * + * @since 7.1 * * @see #lock() * @see #getCurrent() - * @see UI#access(Runnable) + * @see #access(Runnable) + * @see UI#accessSynchronously(Runnable) */ - public void access(Runnable runnable) { + public void accessSynchronously(Runnable runnable) { VaadinService.verifyNoOtherSessionLocked(this); Map<Class<?>, CurrentInstance> old = null; @@ -1111,12 +1136,119 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** - * @deprecated As of 7.1.0.beta1, use {@link #access(Runnable)} instead. - * This method will be removed before the final 7.1.0 release. + * Provides exclusive access to this session from outside a request handling + * thread. + * <p> + * The given runnable is executed while holding the session lock to ensure + * exclusive access to this session. If this session is not locked, the lock + * will be acquired and the runnable is run right away. If this session is + * currently locked, the runnable will be run before that lock is released. + * </p> + * <p> + * RPC handlers for components inside this session do not need to use this + * method as the session is automatically locked by the framework during RPC + * handling. + * </p> + * <p> + * Please note that the runnable might be invoked on a different thread or + * later on the current thread, which means that custom thread locals might + * not have the expected values when the runnable is executed. The session + * and other thread locals provided by Vaadin are set properly before + * executing the runnable. + * </p> + * <p> + * The returned future can be used to check for task completion and to + * cancel the task. To help avoiding deadlocks, {@link Future#get()} throws + * an exception if it is detected that the current thread holds the lock for + * some other session. + * </p> + * + * @see #lock() + * @see #getCurrent() + * @see #accessSynchronously(Runnable) + * @see UI#access(Runnable) + * + * @since 7.1 + * + * @param runnable + * the runnable which accesses the session + * @return a future that can be used to check for task completion and to + * cancel the task + */ + public Future<Void> access(Runnable runnable) { + FutureTask<Void> future = new FutureTask<Void>(runnable, null) { + @Override + public Void get() throws InterruptedException, ExecutionException { + /* + * Help the developer avoid programming patterns that cause + * deadlocks unless implemented very carefully. get(long, + * TimeUnit) does not have the same detection since a sensible + * timeout should avoid completely locking up the application. + * + * Even though no deadlock could occur after the runnable has + * been run, the check is always done as the deterministic + * behavior makes it easier to detect potential problems. + */ + VaadinService.verifyNoOtherSessionLocked(VaadinSession.this); + return super.get(); + } + }; + pendingAccessQueue.add(future); + + /* + * If no thread is currently holding the lock, pending changes for UIs + * with automatic push would not be processed and pushed until the next + * time there is a request or someone does an explicit push call. + * + * To remedy this, we try to get the lock at this point. If the lock is + * currently held by another thread, we just back out as the queue will + * get purged once it is released. If the lock is held by the current + * thread, we just release it knowing that the queue gets purged once + * the lock is ultimately released. If the lock is not held by any + * thread and we acquire it, we just release it again to purge the queue + * right away. + */ + try { + // tryLock() would be shorter, but it does not guarantee fairness + if (getLockInstance().tryLock(0, TimeUnit.SECONDS)) { + // unlock triggers runPendingAccessTasks + unlock(); + } + } catch (InterruptedException e) { + // Just ignore + } + + return future; + } + + /** + * Purges the queue of pending access invocations enqueued with + * {@link #access(Runnable)}. + * <p> + * This method is automatically run by the framework at appropriate + * situations and is not intended to be used by application developers. + * + * @since 7.1 + */ + public void runPendingAccessTasks() { + assert hasLock(); + + FutureTask<Void> pendingAccess; + while ((pendingAccess = pendingAccessQueue.poll()) != null) { + if (!pendingAccess.isCancelled()) { + accessSynchronously(pendingAccess); + } + } + } + + /** + * @deprecated As of 7.1.0.beta1, use {@link #accessSynchronously(Runnable)} + * or {@link #access(Runnable)} instead. This method will be + * removed before the final 7.1.0 release. */ @Deprecated public void runSafely(Runnable runnable) { - access(runnable); + accessSynchronously(runnable); } /** diff --git a/server/src/com/vaadin/server/communication/FileUploadHandler.java b/server/src/com/vaadin/server/communication/FileUploadHandler.java index e875a4e861..e9569d45a1 100644 --- a/server/src/com/vaadin/server/communication/FileUploadHandler.java +++ b/server/src/com/vaadin/server/communication/FileUploadHandler.java @@ -632,7 +632,7 @@ public class FileUploadHandler implements RequestHandler { private void cleanStreamVariable(VaadinSession session, final ClientConnector owner, final String variableName) { - session.access(new Runnable() { + session.accessSynchronously(new Runnable() { @Override public void run() { owner.getUI() diff --git a/server/src/com/vaadin/server/communication/UidlWriter.java b/server/src/com/vaadin/server/communication/UidlWriter.java index fbe2fb86d5..60a884a635 100644 --- a/server/src/com/vaadin/server/communication/UidlWriter.java +++ b/server/src/com/vaadin/server/communication/UidlWriter.java @@ -74,9 +74,14 @@ public class UidlWriter implements Serializable { public void write(UI ui, Writer writer, boolean repaintAll, boolean analyzeLayouts, boolean async) throws IOException, JSONException { + VaadinSession session = ui.getSession(); + + // Purge pending access calls as they might produce additional changes + // to write out + session.runPendingAccessTasks(); + ArrayList<ClientConnector> dirtyVisibleConnectors = ui .getConnectorTracker().getDirtyVisibleConnectors(); - VaadinSession session = ui.getSession(); LegacyCommunicationManager manager = session.getCommunicationManager(); // Paints components ConnectorTracker uiConnectorTracker = ui.getConnectorTracker(); diff --git a/server/src/com/vaadin/ui/LoginForm.java b/server/src/com/vaadin/ui/LoginForm.java index d06882927e..67d7182ecb 100644 --- a/server/src/com/vaadin/ui/LoginForm.java +++ b/server/src/com/vaadin/ui/LoginForm.java @@ -68,7 +68,7 @@ public class LoginForm extends CustomComponent { } final StringBuilder responseBuilder = new StringBuilder(); - getUI().access(new Runnable() { + getUI().accessSynchronously(new Runnable() { @Override public void run() { String method = VaadinServletService.getCurrentServletRequest() diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index e077b003b8..234a309c06 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -22,6 +22,7 @@ import java.util.Collections; import java.util.Iterator; import java.util.LinkedHashSet; import java.util.Map; +import java.util.concurrent.Future; import com.vaadin.event.Action; import com.vaadin.event.Action.Handler; @@ -86,7 +87,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements /** * The application to which this UI belongs */ - private VaadinSession session; + private volatile VaadinSession session; /** * List of windows in this UI. @@ -1098,24 +1099,34 @@ public abstract class UI extends AbstractSingleComponentContainer implements } /** - * Provides exclusive access to this UI from outside a request handling - * thread. + * Locks the session of this UI and runs the provided Runnable right away. * <p> - * The given runnable is executed while holding the session lock to ensure - * exclusive access to this UI and its session. The UI and related thread - * locals are set properly before executing the runnable. + * It is generally recommended to use {@link #access(Runnable)} instead of + * this method for accessing a session from a different thread as + * {@link #access(Runnable)} can be used while holding the lock of another + * session. To avoid causing deadlocks, this methods throws an exception if + * it is detected than another session is also locked by the current thread. * </p> * <p> - * RPC handlers for components inside this UI do not need this method as the - * session is automatically locked by the framework during request handling. - * </p> - * <p> - * Note that calling this method while another session is locked by the - * current thread will cause an exception. This is to prevent deadlock - * situations when two threads have locked one session each and are both - * waiting for the lock for the other session. + * This method behaves differently than {@link #access(Runnable)} in some + * situations: + * <ul> + * <li>If the current thread is currently holding the lock of the session, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time.</li> + * <li>If some other thread is currently holding the lock for the session, + * {@link #accessSynchronously(Runnable)} blocks while waiting for the lock + * to be available whereas {@link #access(Runnable)} defers the task to a + * later point in time.</li> + * <li>If the session is currently not locked, + * {@link #accessSynchronously(Runnable)} runs the task right away whereas + * {@link #access(Runnable)} defers the task to a later point in time unless + * there are UIs with automatic push enabled.</li> + * </ul> * </p> * + * @since 7.1 + * * @param runnable * the runnable which accesses the UI * @throws UIDetachedException @@ -1124,11 +1135,11 @@ public abstract class UI extends AbstractSingleComponentContainer implements * @throws IllegalStateException * if the current thread holds the lock for another session * - * @see #getCurrent() - * @see VaadinSession#access(Runnable) - * @see VaadinSession#lock() + * @see #access(Runnable) + * @see VaadinSession#accessSynchronously(Runnable) */ - public void access(Runnable runnable) throws UIDetachedException { + public void accessSynchronously(Runnable runnable) + throws UIDetachedException { Map<Class<?>, CurrentInstance> old = null; VaadinSession session = getSession(); @@ -1158,12 +1169,69 @@ public abstract class UI extends AbstractSingleComponentContainer implements } /** - * @deprecated As of 7.1.0.beta1, use {@link #access(Runnable)} instead. - * This method will be removed before the final 7.1.0 release. + * Provides exclusive access to this UI from outside a request handling + * thread. + * <p> + * The given runnable is executed while holding the session lock to ensure + * exclusive access to this UI. If the session is not locked, the lock will + * be acquired and the runnable is run right away. If the session is + * currently locked, the runnable will be run before that lock is released. + * </p> + * <p> + * RPC handlers for components inside this UI do not need to use this method + * as the session is automatically locked by the framework during RPC + * handling. + * </p> + * <p> + * Please note that the runnable might be invoked on a different thread or + * later on the current thread, which means that custom thread locals might + * not have the expected values when the runnable is executed. The UI and + * other thread locals provided by Vaadin are set properly before executing + * the runnable. + * </p> + * <p> + * The returned future can be used to check for task completion and to + * cancel the task. + * </p> + * + * @see #getCurrent() + * @see #accessSynchronously(Runnable) + * @see VaadinSession#access(Runnable) + * @see VaadinSession#lock() + * + * @since 7.1 + * + * @param runnable + * the runnable which accesses the UI + * @throws UIDetachedException + * if the UI is not attached to a session (and locking can + * therefore not be done) + * @return a future that can be used to check for task completion and to + * cancel the task + */ + public Future<Void> access(final Runnable runnable) { + VaadinSession session = getSession(); + + if (session == null) { + throw new UIDetachedException(); + } + + return session.access(new Runnable() { + @Override + public void run() { + accessSynchronously(runnable); + } + }); + } + + /** + * @deprecated As of 7.1.0.beta1, use {@link #accessSynchronously(Runnable)} + * or {@link #access(Runnable)} instead. This method will be + * removed before the final 7.1.0 release. */ @Deprecated public void runSafely(Runnable runnable) throws UIDetachedException { - access(runnable); + accessSynchronously(runnable); } /** @@ -1204,6 +1272,14 @@ public abstract class UI extends AbstractSingleComponentContainer implements VaadinSession session = getSession(); if (session != null) { assert session.hasLock(); + + /* + * Purge the pending access queue as it might mark a connector as + * dirty when the push would otherwise be ignored because there are + * no changes to push. + */ + session.runPendingAccessTasks(); + if (!getConnectorTracker().hasDirtyConnectors()) { // Do not push if there is nothing to push return; diff --git a/uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java b/uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java index bec8c0a10f..c88f482a7b 100644 --- a/uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java +++ b/uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java @@ -119,7 +119,7 @@ public class CloseUI extends AbstractTestUI { @Override public void run() { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { diff --git a/uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java b/uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java index ddc0f28664..c9af2c000d 100644 --- a/uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java +++ b/uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java @@ -11,7 +11,7 @@ public abstract class UIRunSafelyThread extends Thread { @Override public void run() { - ui.access(new Runnable() { + ui.accessSynchronously(new Runnable() { @Override public void run() { diff --git a/uitest/src/com/vaadin/tests/components/ui/UiAccess.html b/uitest/src/com/vaadin/tests/components/ui/UiAccess.html new file mode 100644 index 0000000000..664b15c16f --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/ui/UiAccess.html @@ -0,0 +1,127 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd"> +<html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> +<head profile="http://selenium-ide.openqa.org/profiles/test-case"> +<meta http-equiv="Content-Type" content="text/html; charset=UTF-8" /> +<link rel="selenium.base" href="" /> +<title>New Test</title> +</head> +<body> +<table cellpadding="1" cellspacing="1" border="1"> +<thead> +<tr><td rowspan="1" colspan="3">New Test</td></tr> +</thead><tbody> +<tr> + <td>open</td> + <td>/run/com.vaadin.tests.components.ui.UiAccess?restartApplication</td> + <td></td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[0]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td> + <td>exact:0. Access from UI thread future is done? false</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td> + <td>1. Access from UI thread is run</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>exact:2. beforeClientResponse future is done? true</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[1]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td> + <td>0. Initial background message</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>exact:1. Thread has current response? false</td> +</tr> +<tr> + <td>waitForText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_4</td> + <td>0. Initial background message</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td> + <td>exact:2. Thread got lock, inital future done? true</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td> + <td>exact:3. Access has current response? true</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>exact:4. Thread is still alive? false</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[2]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td> + <td>0. Throwing exception in access</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td> + <td>exact:1. firstFuture is done? true</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>2. Got exception from firstFuture: java.lang.RuntimeException: Catch me if you can</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[3]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>0. future was cancled, should not start</td> +</tr> +<tr> + <td>click</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[4]/VButton[0]/domChild[0]/domChild[0]</td> + <td></td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td> + <td>0. Waiting for thread to start</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td> + <td>1. Thread started, waiting for interruption</td> +</tr> +<tr> + <td>assertText</td> + <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td> + <td>2. I was interrupted</td> +</tr> + +</tbody></table> +</body> +</html> diff --git a/uitest/src/com/vaadin/tests/components/ui/UiAccess.java b/uitest/src/com/vaadin/tests/components/ui/UiAccess.java new file mode 100644 index 0000000000..c68da6ee54 --- /dev/null +++ b/uitest/src/com/vaadin/tests/components/ui/UiAccess.java @@ -0,0 +1,235 @@ +/* + * Copyright 2000-2013 Vaadin Ltd. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not + * use this file except in compliance with the License. You may obtain a copy of + * the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ + +package com.vaadin.tests.components.ui; + +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.Future; +import java.util.concurrent.locks.ReentrantLock; + +import com.vaadin.server.VaadinRequest; +import com.vaadin.server.VaadinService; +import com.vaadin.tests.components.AbstractTestUIWithLog; +import com.vaadin.ui.Button; +import com.vaadin.ui.Button.ClickEvent; + +public class UiAccess extends AbstractTestUIWithLog { + + private Future<Void> checkFromBeforeClientResponse; + + @Override + protected void setup(VaadinRequest request) { + addComponent(new Button("Access from UI thread", + new Button.ClickListener() { + + @Override + public void buttonClick(ClickEvent event) { + log.clear(); + // Ensure beforeClientResponse is invoked + markAsDirty(); + checkFromBeforeClientResponse = access(new Runnable() { + @Override + public void run() { + log("Access from UI thread is run"); + } + }); + log("Access from UI thread future is done? " + + checkFromBeforeClientResponse.isDone()); + } + })); + addComponent(new Button("Access from background thread", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + log.clear(); + final CountDownLatch latch = new CountDownLatch(1); + + new Thread() { + @Override + public void run() { + final boolean threadHasCurrentResponse = VaadinService + .getCurrentResponse() != null; + // session is locked by request thread at this + // point + final Future<Void> initialFuture = access(new Runnable() { + @Override + public void run() { + log("Initial background message"); + log("Thread has current response? " + + threadHasCurrentResponse); + } + }); + + // Let request thread continue + latch.countDown(); + + // Wait until thread can be locked + while (!getSession().getLockInstance() + .tryLock()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + try { + log("Thread got lock, inital future done? " + + initialFuture.isDone()); + setPollInterval(-1); + } finally { + getSession().unlock(); + } + final Thread thisThread = this; + access(new Runnable() { + @Override + public void run() { + log("Access has current response? " + + (VaadinService + .getCurrentResponse() != null)); + log("Thread is still alive? " + + thisThread.isAlive()); + } + }); + } + }.start(); + + // Wait for thread to do initialize before continuing + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + + setPollInterval(3000); + } + })); + addComponent(new Button("Access throwing exception", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + log.clear(); + final Future<Void> firstFuture = access(new Runnable() { + @Override + public void run() { + log("Throwing exception in access"); + throw new RuntimeException( + "Catch me if you can"); + } + }); + access(new Runnable() { + @Override + public void run() { + log("firstFuture is done? " + + firstFuture.isDone()); + try { + firstFuture.get(); + log("Should not get here"); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } catch (ExecutionException e) { + log("Got exception from firstFuture: " + + e.getMessage()); + } + } + }); + } + })); + addComponent(new Button("Cancel future before started", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + log.clear(); + Future<Void> future = access(new Runnable() { + @Override + public void run() { + log("Should not get here"); + } + }); + future.cancel(false); + log("future was cancled, should not start"); + } + })); + addComponent(new Button("Cancel running future", + new Button.ClickListener() { + @Override + public void buttonClick(ClickEvent event) { + log.clear(); + final ReentrantLock interruptLock = new ReentrantLock(); + + final Future<Void> future = access(new Runnable() { + @Override + public void run() { + log("Waiting for thread to start"); + while (!interruptLock.isLocked()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + log("Premature interruption"); + throw new RuntimeException(e); + } + } + + log("Thread started, waiting for interruption"); + try { + interruptLock.lockInterruptibly(); + } catch (InterruptedException e) { + log("I was interrupted"); + } + } + }); + + new Thread() { + @Override + public void run() { + interruptLock.lock(); + // Wait until UI thread has started waiting for + // the lock + while (!interruptLock.hasQueuedThreads()) { + try { + Thread.sleep(100); + } catch (InterruptedException e) { + throw new RuntimeException(e); + } + } + + future.cancel(true); + } + }.start(); + } + })); + } + + @Override + public void beforeClientResponse(boolean initial) { + if (checkFromBeforeClientResponse != null) { + log("beforeClientResponse future is done? " + + checkFromBeforeClientResponse.isDone()); + checkFromBeforeClientResponse = null; + } + } + + @Override + protected String getTestDescription() { + return "Test for various ways of using UI.access"; + } + + @Override + protected Integer getTicketNumber() { + return Integer.valueOf(11897); + } + +} |