Browse Source

Make access() enqueue the runnable if the session is locked (#11897)

Change-Id: If162e81a29bbc982857e2a165a983e161ea837ee
tags/7.1.0
Leif Åstrand 11 years ago
parent
commit
4d7f190b7f

+ 2
- 1
server/src/com/vaadin/server/RequestHandler.java View File

@@ -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

+ 8
- 7
server/src/com/vaadin/server/VaadinService.java View File

@@ -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

+ 153
- 21
server/src/com/vaadin/server/VaadinSession.java View File

@@ -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);
}

/**

+ 1
- 1
server/src/com/vaadin/server/communication/FileUploadHandler.java View File

@@ -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()

+ 6
- 1
server/src/com/vaadin/server/communication/UidlWriter.java View File

@@ -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();

+ 1
- 1
server/src/com/vaadin/ui/LoginForm.java View File

@@ -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()

+ 97
- 21
server/src/com/vaadin/ui/UI.java View File

@@ -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;

+ 1
- 1
uitest/src/com/vaadin/tests/applicationcontext/CloseUI.java View File

@@ -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() {

+ 1
- 1
uitest/src/com/vaadin/tests/applicationcontext/UIRunSafelyThread.java View File

@@ -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() {

+ 127
- 0
uitest/src/com/vaadin/tests/components/ui/UiAccess.html View File

@@ -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>

+ 235
- 0
uitest/src/com/vaadin/tests/components/ui/UiAccess.java View File

@@ -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);
}

}

Loading…
Cancel
Save