Change-Id: If162e81a29bbc982857e2a165a983e161ea837eetags/7.1.0
@@ -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 |
@@ -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 |
@@ -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); | |||
} | |||
/** |
@@ -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() |
@@ -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(); |
@@ -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() |
@@ -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; |
@@ -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() { |
@@ -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() { |
@@ -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> |
@@ -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); | |||
} | |||
} |