summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLeif Åstrand <leif@vaadin.com>2013-05-22 15:56:29 +0300
committerVaadin Code Review <review@vaadin.com>2013-05-28 07:47:48 +0000
commit4d7f190b7f36a10b16e74b1dab8ed0a274841ae1 (patch)
tree317825e84f6a8004a7f40429effcedbeb81d35b8
parent2882cf98756974f40ba98d66c7d7318db6acd538 (diff)
downloadvaadin-framework-4d7f190b7f36a10b16e74b1dab8ed0a274841ae1.tar.gz
vaadin-framework-4d7f190b7f36a10b16e74b1dab8ed0a274841ae1.zip
Make access() enqueue the runnable if the session is locked (#11897)
Change-Id: If162e81a29bbc982857e2a165a983e161ea837ee
-rw-r--r--server/src/com/vaadin/server/RequestHandler.java3
-rw-r--r--server/src/com/vaadin/server/VaadinService.java15
-rw-r--r--server/src/com/vaadin/server/VaadinSession.java174
-rw-r--r--server/src/com/vaadin/server/communication/FileUploadHandler.java2
-rw-r--r--server/src/com/vaadin/server/communication/UidlWriter.java7
-rw-r--r--server/src/com/vaadin/ui/LoginForm.java2
-rw-r--r--server/src/com/vaadin/ui/UI.java118
-rw-r--r--uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java2
-rw-r--r--uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java2
-rw-r--r--uitest/src/com/vaadin/tests/components/ui/UiAccess.html127
-rw-r--r--uitest/src/com/vaadin/tests/components/ui/UiAccess.java235
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);
+ }
+
+}