From: Artur Signell Date: Fri, 22 Mar 2013 15:31:26 +0000 (+0200) Subject: Correctly set thread locals when session times out (#11361, #10995) X-Git-Tag: 7.1.0.beta1~195 X-Git-Url: https://source.dussan.org/?a=commitdiff_plain;h=7dcccb01754a42338ec056cfda642bd8c28bb4d7;p=vaadin-framework.git Correctly set thread locals when session times out (#11361, #10995) Change-Id: I5051cef344f03af276be24a28471a6d6c15b0da6 --- diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index baa455e69e..20d87d6554 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -340,18 +340,40 @@ public abstract class VaadinService implements Serializable { SESSION_DESTROY_METHOD); } + /** + * Handles destruction of the given session. Internally ensures proper + * locking is done. + * + * @param vaadinSession + * The session to destroy + */ public void fireSessionDestroy(VaadinSession vaadinSession) { - for (UI ui : new ArrayList(vaadinSession.getUIs())) { - // close() called here for consistency so that it is always called - // before a UI is removed. UI.isClosing() is thus always true in - // UI.detach() and associated detach listeners. - if (!ui.isClosing()) { - ui.close(); + final VaadinSession session = vaadinSession; + session.runSafely(new Runnable() { + @Override + public void run() { + ArrayList uis = new ArrayList(session.getUIs()); + for (final UI ui : uis) { + ui.runSafely(new Runnable() { + @Override + public void run() { + /* + * close() called here for consistency so that it is + * always called before a UI is removed. + * UI.isClosing() is thus always true in UI.detach() + * and associated detach listeners. + */ + if (!ui.isClosing()) { + ui.close(); + } + session.removeUI(ui); + } + }); + } + eventRouter.fireEvent(new SessionDestroyEvent( + VaadinService.this, session)); } - vaadinSession.removeUI(ui); - } - - eventRouter.fireEvent(new SessionDestroyEvent(this, vaadinSession)); + }); } /** diff --git a/server/tests/src/com/vaadin/server/VaadinSessionTest.java b/server/tests/src/com/vaadin/server/VaadinSessionTest.java new file mode 100644 index 0000000000..6cad3307bf --- /dev/null +++ b/server/tests/src/com/vaadin/server/VaadinSessionTest.java @@ -0,0 +1,115 @@ +/* + * 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.server; + +import java.util.concurrent.atomic.AtomicBoolean; +import java.util.concurrent.locks.ReentrantLock; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; +import javax.servlet.http.HttpSessionBindingEvent; + +import junit.framework.Assert; + +import org.easymock.EasyMock; +import org.junit.Test; + +import com.vaadin.server.ClientConnector.DetachEvent; +import com.vaadin.server.ClientConnector.DetachListener; +import com.vaadin.ui.UI; + +public class VaadinSessionTest { + + @Test + public void threadLocalsAfterUnderlyingSessionTimeout() { + + final VaadinServlet mockServlet = new VaadinServlet() { + @Override + public String getServletName() { + return "mockServlet"; + }; + }; + + final VaadinServletService mockService = new VaadinServletService( + mockServlet, EasyMock.createMock(DeploymentConfiguration.class)); + + HttpSession mockHttpSession = EasyMock.createMock(HttpSession.class); + WrappedSession mockWrappedSession = new WrappedHttpSession( + mockHttpSession) { + final ReentrantLock lock = new ReentrantLock(); + + @Override + public Object getAttribute(String name) { + if ("mockServlet.lock".equals(name)) { + return lock; + } + return super.getAttribute(name); + } + }; + + final VaadinSession session = new VaadinSession(mockService); + session.storeInSession(mockService, mockWrappedSession); + + final UI ui = new UI() { + Page page = new Page(this) { + @Override + public void init(VaadinRequest request) { + } + }; + + @Override + protected void init(VaadinRequest request) { + } + + @Override + public Page getPage() { + return page; + } + }; + VaadinServletRequest vaadinRequest = new VaadinServletRequest( + EasyMock.createMock(HttpServletRequest.class), mockService) { + @Override + public String getParameter(String name) { + if ("theme".equals(name)) { + return null; + } + + return super.getParameter(name); + } + }; + + ui.doInit(vaadinRequest, session.getNextUIid()); + + ui.setSession(session); + session.addUI(ui); + + final AtomicBoolean detachCalled = new AtomicBoolean(false); + ui.addDetachListener(new DetachListener() { + @Override + public void detach(DetachEvent event) { + detachCalled.set(true); + Assert.assertEquals(ui, UI.getCurrent()); + Assert.assertEquals(ui.getPage(), Page.getCurrent()); + Assert.assertEquals(session, VaadinSession.getCurrent()); + Assert.assertEquals(mockService, VaadinService.getCurrent()); + Assert.assertEquals(mockServlet, VaadinServlet.getCurrent()); + } + }); + + session.valueUnbound(EasyMock.createMock(HttpSessionBindingEvent.class)); + Assert.assertTrue(detachCalled.get()); + } +}