diff options
author | Artur Signell <artur@vaadin.com> | 2016-10-06 23:07:20 +0300 |
---|---|---|
committer | Pekka Hyvönen <pekka@vaadin.com> | 2016-12-09 09:39:00 +0200 |
commit | d20fc36768c9d2c74b40b34c931da8d842d0a67e (patch) | |
tree | d5939af6c068bd9483dd9fbffa86758cbde95b6e /server | |
parent | 7b39a6dd527e80499261d1e93a48af9ab629f25c (diff) | |
download | vaadin-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.java | 18 | ||||
-rw-r--r-- | server/src/test/java/com/vaadin/ui/UITest.java | 155 |
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()); + + } +} |