diff options
author | Johannes Dahlström <johannesd@vaadin.com> | 2014-04-07 15:00:49 +0300 |
---|---|---|
committer | Vaadin Code Review <review@vaadin.com> | 2014-04-10 11:18:20 +0000 |
commit | 55dfd2936a1680f444be8e7357ac4ceaa6870e23 (patch) | |
tree | 67c1e78ede4ee2c4221514adf39edc442c63955f /server | |
parent | 2067d4e01045fa4ca1d512322d6442499d6aded0 (diff) | |
download | vaadin-framework-55dfd2936a1680f444be8e7357ac4ceaa6870e23.tar.gz vaadin-framework-55dfd2936a1680f444be8e7357ac4ceaa6870e23.zip |
Prevent duplicate session destroy events (#12612)
Change-Id: Ic752268a9deac350dbff29ecf73cfce2eb1ba0cc
Diffstat (limited to 'server')
-rw-r--r-- | server/src/com/vaadin/server/VaadinService.java | 12 | ||||
-rw-r--r-- | server/src/com/vaadin/server/VaadinSession.java | 82 | ||||
-rw-r--r-- | server/src/com/vaadin/ui/UI.java | 3 | ||||
-rw-r--r-- | server/tests/src/com/vaadin/server/VaadinServiceTest.java | 22 |
4 files changed, 99 insertions, 20 deletions
diff --git a/server/src/com/vaadin/server/VaadinService.java b/server/src/com/vaadin/server/VaadinService.java index ba1224568a..b96e284e6e 100644 --- a/server/src/com/vaadin/server/VaadinService.java +++ b/server/src/com/vaadin/server/VaadinService.java @@ -53,6 +53,7 @@ import org.json.JSONObject; import com.vaadin.annotations.PreserveOnRefresh; import com.vaadin.event.EventRouter; import com.vaadin.server.VaadinSession.FutureAccess; +import com.vaadin.server.VaadinSession.State; import com.vaadin.server.communication.FileUploadHandler; import com.vaadin.server.communication.HeartbeatHandler; import com.vaadin.server.communication.PublishedFileHandler; @@ -446,7 +447,10 @@ public abstract class VaadinService implements Serializable { session.accessSynchronously(new Runnable() { @Override public void run() { - if (!session.isClosing()) { + if (session.getState() == State.CLOSED) { + return; + } + if (session.getState() == State.OPEN) { closeSession(session); } ArrayList<UI> uis = new ArrayList<UI>(session.getUIs()); @@ -472,6 +476,8 @@ public abstract class VaadinService implements Serializable { // destroy listeners eventRouter.fireEvent(new SessionDestroyEvent( VaadinService.this, session), session.getErrorHandler()); + + session.setState(State.CLOSED); } }); } @@ -1127,7 +1133,7 @@ public abstract class VaadinService implements Serializable { closeInactiveUIs(session); removeClosedUIs(session); } else { - if (!session.isClosing()) { + if (session.getState() == State.OPEN) { closeSession(session); if (session.getSession() != null) { getLogger().log(Level.FINE, "Closing inactive session {0}", @@ -1279,7 +1285,7 @@ public abstract class VaadinService implements Serializable { * @return true if the session is active, false if it could be closed. */ private boolean isSessionActive(VaadinSession session) { - if (session.isClosing() || session.getSession() == null) { + if (session.getState() != State.OPEN || session.getSession() == null) { return false; } else { long now = System.currentTimeMillis(); diff --git a/server/src/com/vaadin/server/VaadinSession.java b/server/src/com/vaadin/server/VaadinSession.java index 134a026788..ac518c1902 100644 --- a/server/src/com/vaadin/server/VaadinSession.java +++ b/server/src/com/vaadin/server/VaadinSession.java @@ -167,6 +167,33 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } /** + * The lifecycle state of a VaadinSession. + * + * @since 7.2 + */ + public enum State { + /** + * The session is active and accepting client requests. + */ + OPEN, + /** + * The {@link VaadinSession#close() close} method has been called; the + * session will be closed as soon as the current request ends. + */ + CLOSING, + /** + * The session is closed; all the {@link UI}s have been removed and + * {@link SessionDestroyListener}s have been called. + */ + CLOSED; + + private boolean isValidChange(State newState) { + return (this == OPEN && newState == CLOSING) + || (this == CLOSING && newState == CLOSED); + } + } + + /** * The name of the parameter that is by default used in e.g. web.xml to * define the name of the default {@link UI} class. */ @@ -225,7 +252,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { private long lastRequestTimestamp = System.currentTimeMillis(); - private boolean closing = false; + private State state = State.OPEN; private transient WrappedSession session; @@ -279,24 +306,20 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { } else if (VaadinService.getCurrentRequest() != null && getCurrent() == this) { assert hasLock(); - /* - * Ignore if the session is being moved to a different backing - * session or if GAEVaadinServlet is doing its normal cleanup. - */ + // Ignore if the session is being moved to a different backing + // session or if GAEVaadinServlet is doing its normal cleanup. if (getAttribute(VaadinService.PRESERVE_UNBOUND_SESSION_ATTRIBUTE) == Boolean.TRUE) { return; } // There is still a request in progress for this session. The // session will be destroyed after the response has been written. - if (!isClosing()) { + if (getState() == State.OPEN) { close(); } } else { - /* - * We are not in a request related to this session so we can - * immediately destroy it - */ + // We are not in a request related to this session so we can destroy + // it as soon as we acquire the lock. service.fireSessionDestroy(this); } session = null; @@ -1226,19 +1249,52 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable { */ public void close() { assert hasLock(); - closing = true; + state = State.CLOSING; } /** - * Returns whether this session is marked to be closed. + * Returns whether this session is marked to be closed. Note that this + * method also returns true if the session is actually already closed. * * @see #close() * + * @deprecated As of 7.2, use + * <code>{@link #getState() getState() != State.OPEN}</code> + * instead. + * * @return true if this session is marked to be closed, false otherwise */ + @Deprecated public boolean isClosing() { assert hasLock(); - return closing; + return state == State.CLOSING || state == State.CLOSED; + } + + /** + * Returns the lifecycle state of this session. + * + * @since 7.2 + * @return the current state + */ + public State getState() { + assert hasLock(); + return state; + } + + /** + * Sets the lifecycle state of this session. The allowed transitions are + * OPEN to CLOSING and CLOSING to CLOSED. + * + * @since 7.2 + * @param state + * the new state + */ + protected void setState(State state) { + assert hasLock(); + assert this.state.isValidChange(state) : "Invalid session state change " + + this.state + "->" + state; + + this.state = state; } private static final Logger getLogger() { diff --git a/server/src/com/vaadin/ui/UI.java b/server/src/com/vaadin/ui/UI.java index 2b2e773601..275aeb4c79 100644 --- a/server/src/com/vaadin/ui/UI.java +++ b/server/src/com/vaadin/ui/UI.java @@ -53,6 +53,7 @@ import com.vaadin.server.VaadinRequest; import com.vaadin.server.VaadinService; import com.vaadin.server.VaadinServlet; import com.vaadin.server.VaadinSession; +import com.vaadin.server.VaadinSession.State; import com.vaadin.server.communication.PushConnection; import com.vaadin.shared.Connector; import com.vaadin.shared.EventId; @@ -1162,7 +1163,7 @@ public abstract class UI extends AbstractSingleComponentContainer implements public void close() { closing = true; - boolean sessionExpired = (session == null || session.isClosing()); + boolean sessionExpired = (session == null || session.getState() != State.OPEN); getRpcProxy(UIClientRpc.class).uiClosed(sessionExpired); if (getPushConnection() != null) { // Push the Rpc to the client. The connection will be closed when diff --git a/server/tests/src/com/vaadin/server/VaadinServiceTest.java b/server/tests/src/com/vaadin/server/VaadinServiceTest.java index cead5df79c..77eb155378 100644 --- a/server/tests/src/com/vaadin/server/VaadinServiceTest.java +++ b/server/tests/src/com/vaadin/server/VaadinServiceTest.java @@ -29,6 +29,16 @@ import org.junit.Test; */ public class VaadinServiceTest { + private class TestSessionDestroyListener implements SessionDestroyListener { + + int callCount = 0; + + @Override + public void sessionDestroy(SessionDestroyEvent event) { + callCount++; + } + } + @Test public void testFireSessionDestroy() throws ServletException { ServletConfig servletConfig = new MockServletConfig(); @@ -36,6 +46,10 @@ public class VaadinServiceTest { servlet.init(servletConfig); VaadinService service = servlet.getService(); + TestSessionDestroyListener listener = new TestSessionDestroyListener(); + + service.addSessionDestroyListener(listener); + MockVaadinSession vaadinSession = new MockVaadinSession(service); service.fireSessionDestroy(vaadinSession); Assert.assertEquals( @@ -45,9 +59,11 @@ public class VaadinServiceTest { vaadinSession.valueUnbound(EasyMock .createMock(HttpSessionBindingEvent.class)); - org.junit.Assert.assertEquals( - "'fireSessionDestroy' method may not call 'close' " - + "method for closing session", 1, + Assert.assertEquals("'fireSessionDestroy' method may not call 'close' " + + "method for closing session", 1, vaadinSession.getCloseCount()); + + Assert.assertEquals("SessionDestroyListeners not called exactly once", + 1, listener.callCount); } } |