summaryrefslogtreecommitdiffstats
path: root/server
diff options
context:
space:
mode:
authorArtur Signell <artur@vaadin.com>2016-10-06 23:07:20 +0300
committerPekka Hyvönen <pekka@vaadin.com>2016-12-09 09:39:00 +0200
commitd20fc36768c9d2c74b40b34c931da8d842d0a67e (patch)
treed5939af6c068bd9483dd9fbffa86758cbde95b6e /server
parent7b39a6dd527e80499261d1e93a48af9ab629f25c (diff)
downloadvaadin-framework-d20fc36768c9d2c74b40b34c931da8d842d0a67e.tar.gz
vaadin-framework-d20fc36768c9d2c74b40b34c931da8d842d0a67e.zip
Workaround for deadlock issue (#18436)
Change-Id: I4e32550e3d3095c2c914bb93d260819414d2e6e6
Diffstat (limited to 'server')
-rw-r--r--server/src/main/java/com/vaadin/ui/UI.java18
-rw-r--r--server/src/test/java/com/vaadin/ui/UITest.java155
2 files changed, 171 insertions, 2 deletions
diff --git a/server/src/main/java/com/vaadin/ui/UI.java b/server/src/main/java/com/vaadin/ui/UI.java
index dbca873cc1..8926ec6bd4 100644
--- a/server/src/main/java/com/vaadin/ui/UI.java
+++ b/server/src/main/java/com/vaadin/ui/UI.java
@@ -508,9 +508,23 @@ public abstract class UI extends AbstractSingleComponentContainer
"Error while detaching UI from session", e);
}
// Disable push when the UI is detached. Otherwise the
- // push connection and possibly VaadinSession will live on.
+ // push connection and possibly VaadinSession will live
+ // on.
getPushConfiguration().setPushMode(PushMode.DISABLED);
- setPushConnection(null);
+
+ new Thread(new Runnable() {
+ @Override
+ public void run() {
+ // This intentionally does disconnect without locking
+ // the VaadinSession to avoid deadlocks where the server
+ // uses a lock for the websocket connection
+
+ // See https://dev.vaadin.com/ticket/18436
+ // The underlying problem is
+ // https://dev.vaadin.com/ticket/16919
+ setPushConnection(null);
+ }
+ }).start();
}
this.session = session;
}
diff --git a/server/src/test/java/com/vaadin/ui/UITest.java b/server/src/test/java/com/vaadin/ui/UITest.java
new file mode 100644
index 0000000000..322bc81a04
--- /dev/null
+++ b/server/src/test/java/com/vaadin/ui/UITest.java
@@ -0,0 +1,155 @@
+package com.vaadin.ui;
+
+import java.util.Properties;
+import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.locks.Lock;
+import java.util.concurrent.locks.ReentrantLock;
+
+import javax.servlet.ServletConfig;
+
+import org.junit.Assert;
+import org.junit.Test;
+
+import com.vaadin.server.DefaultDeploymentConfiguration;
+import com.vaadin.server.MockServletConfig;
+import com.vaadin.server.MockVaadinSession;
+import com.vaadin.server.VaadinRequest;
+import com.vaadin.server.VaadinServlet;
+import com.vaadin.server.VaadinServletService;
+import com.vaadin.server.VaadinSession;
+import com.vaadin.server.communication.PushConnection;
+import com.vaadin.shared.communication.PushMode;
+
+public class UITest {
+
+ @Test
+ public void removeFromSessionWithExternalLock() throws Exception {
+ // See https://dev.vaadin.com/ticket/18436
+ final UI ui = new UI() {
+
+ @Override
+ protected void init(VaadinRequest request) {
+ }
+
+ };
+ final Lock externalLock = new ReentrantLock();
+
+ ServletConfig servletConfig = new MockServletConfig();
+ VaadinServlet servlet = new VaadinServlet();
+ servlet.init(servletConfig);
+
+ DefaultDeploymentConfiguration deploymentConfiguration = new DefaultDeploymentConfiguration(
+ UI.class, new Properties());
+
+ MockVaadinSession session = new MockVaadinSession(
+ new VaadinServletService(servlet, deploymentConfiguration));
+ session.lock();
+ ui.setSession(session);
+ ui.getPushConfiguration().setPushMode(PushMode.MANUAL);
+ ui.setPushConnection(new PushConnection() {
+
+ private boolean connected = true;
+
+ @Override
+ public void push() {
+ }
+
+ @Override
+ public boolean isConnected() {
+ return connected;
+ }
+
+ @Override
+ public void disconnect() {
+ externalLock.lock();
+ try {
+ connected = false;
+ } finally {
+ externalLock.unlock();
+ }
+
+ }
+ });
+ session.unlock();
+
+ final CountDownLatch websocketReachedCheckpoint = new CountDownLatch(1);
+ final CountDownLatch uiDisconnectReachedCheckpoint = new CountDownLatch(
+ 1);
+
+ final VaadinSession uiSession = ui.getSession();
+ final ConcurrentLinkedQueue<Exception> exceptions = new ConcurrentLinkedQueue<Exception>();
+
+ // Simulates the websocket close thread
+ Runnable websocketClose = new Runnable() {
+ @Override
+ public void run() {
+ externalLock.lock();
+ // Wait for disconnect thread to lock VaadinSession
+ websocketReachedCheckpoint.countDown();
+ try {
+ uiDisconnectReachedCheckpoint.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ exceptions.add(e);
+ return;
+ }
+ uiSession.lock();
+ externalLock.unlock();
+ }
+ };
+
+ Runnable disconnectPushFromUI = new Runnable() {
+ @Override
+ public void run() {
+ uiSession.lock();
+ // Wait for websocket thread to lock external lock
+ uiDisconnectReachedCheckpoint.countDown();
+ try {
+ websocketReachedCheckpoint.await();
+ } catch (InterruptedException e) {
+ e.printStackTrace();
+ exceptions.add(e);
+ return;
+ }
+
+ ui.setSession(null);
+ uiSession.unlock();
+ }
+ };
+
+ Thread websocketThread = new Thread(websocketClose);
+ websocketThread.start();
+ Thread uiDisconnectThread = new Thread(disconnectPushFromUI);
+ uiDisconnectThread.start();
+
+ websocketThread.join(5000);
+ uiDisconnectThread.join(5000);
+
+ if (websocketThread.isAlive() || uiDisconnectThread.isAlive()) {
+ websocketThread.interrupt();
+ uiDisconnectThread.interrupt();
+ Assert.fail("Threads are still running");
+ }
+ if (!exceptions.isEmpty()) {
+ for (Exception e : exceptions) {
+ e.printStackTrace();
+ }
+ Assert.fail("There were exceptions in the threads");
+ }
+
+ Assert.assertNull(ui.getSession());
+
+ // PushConnection is set to null in another thread. We need to wait for
+ // that to happen
+ for (int i = 0; i < 10; i++) {
+ if (ui.getPushConnection() == null) {
+ break;
+ }
+
+ Thread.sleep(500);
+ }
+ Assert.assertNull(ui.getPushConnection());
+
+ }
+}