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