You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

VaadinSessionTest.java 12KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337
  1. /*
  2. * Copyright 2000-2016 Vaadin Ltd.
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License"); you may not
  5. * use this file except in compliance with the License. You may obtain a copy of
  6. * the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
  12. * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
  13. * License for the specific language governing permissions and limitations under
  14. * the License.
  15. */
  16. package com.vaadin.server;
  17. import java.io.ByteArrayInputStream;
  18. import java.io.ByteArrayOutputStream;
  19. import java.io.IOException;
  20. import java.io.ObjectInputStream;
  21. import java.io.ObjectOutputStream;
  22. import java.io.Serializable;
  23. import java.util.concurrent.TimeUnit;
  24. import java.util.concurrent.atomic.AtomicBoolean;
  25. import java.util.concurrent.locks.Lock;
  26. import java.util.concurrent.locks.ReentrantLock;
  27. import javax.servlet.ServletConfig;
  28. import javax.servlet.http.HttpServletRequest;
  29. import javax.servlet.http.HttpSession;
  30. import javax.servlet.http.HttpSessionBindingEvent;
  31. import org.easymock.EasyMock;
  32. import org.junit.Assert;
  33. import org.junit.Before;
  34. import org.junit.Test;
  35. import com.vaadin.server.ClientConnector.DetachEvent;
  36. import com.vaadin.server.communication.UIInitHandler;
  37. import com.vaadin.ui.Label;
  38. import com.vaadin.ui.UI;
  39. import com.vaadin.util.CurrentInstance;
  40. public class VaadinSessionTest implements Serializable {
  41. private transient VaadinSession session;
  42. private transient VaadinServlet mockServlet;
  43. private transient VaadinServletService mockService;
  44. private transient ServletConfig mockServletConfig;
  45. private transient HttpSession mockHttpSession;
  46. private transient WrappedSession mockWrappedSession;
  47. private transient VaadinServletRequest vaadinRequest;
  48. private transient UI ui;
  49. private transient Lock httpSessionLock;
  50. @Before
  51. public void setup() throws Exception {
  52. httpSessionLock = new ReentrantLock();
  53. mockServletConfig = new MockServletConfig();
  54. mockServlet = new VaadinServlet();
  55. mockServlet.init(mockServletConfig);
  56. mockService = mockServlet.getService();
  57. mockHttpSession = EasyMock.createMock(HttpSession.class);
  58. mockWrappedSession = new WrappedHttpSession(mockHttpSession) {
  59. final ReentrantLock lock = new ReentrantLock();
  60. {
  61. lock.lock();
  62. }
  63. @Override
  64. public Object getAttribute(String name) {
  65. Object res;
  66. try {
  67. Thread.sleep(100); // for deadlock testing
  68. // simulates servlet container's session locking
  69. org.junit.Assert.assertTrue("Deadlock detected",
  70. httpSessionLock.tryLock(5, TimeUnit.SECONDS));
  71. String lockAttribute = mockService.getServiceName()
  72. + ".lock";
  73. if (lockAttribute.equals(name)) {
  74. res = lock;
  75. } else if ("com.vaadin.server.VaadinSession.Mock Servlet"
  76. .equals(name)) {
  77. res = session;
  78. } else {
  79. res = super.getAttribute(name);
  80. }
  81. httpSessionLock.unlock();
  82. } catch (InterruptedException e) {
  83. throw new RuntimeException(e);
  84. }
  85. return res;
  86. }
  87. };
  88. session = new VaadinSession(mockService);
  89. mockService.storeSession(session, mockWrappedSession);
  90. ui = new MockPageUI();
  91. vaadinRequest = new VaadinServletRequest(
  92. EasyMock.createMock(HttpServletRequest.class), mockService) {
  93. @Override
  94. public String getParameter(String name) {
  95. if ("theme".equals(name) || "restartApplication".equals(name)
  96. || "ignoreRestart".equals(name)
  97. || "closeApplication".equals(name)) {
  98. return null;
  99. } else if (UIInitHandler.BROWSER_DETAILS_PARAMETER
  100. .equals(name)) {
  101. return "1";
  102. }
  103. return super.getParameter(name);
  104. }
  105. @Override
  106. public String getMethod() {
  107. return "POST";
  108. }
  109. @Override
  110. public WrappedSession getWrappedSession(
  111. boolean allowSessionCreation) {
  112. return mockWrappedSession;
  113. }
  114. };
  115. ui.doInit(vaadinRequest, session.getNextUIid(), null);
  116. ui.setSession(session);
  117. session.addUI(ui);
  118. }
  119. /**
  120. * This reproduces #14452 situation with deadlock - see diagram
  121. */
  122. @Test
  123. public void testInvalidationDeadlock() {
  124. // this simulates servlet container's session invalidation from another
  125. // thread
  126. new Thread(() -> {
  127. try {
  128. Thread.sleep(150); // delay selected so that VaadinSession
  129. // will be already locked by the main
  130. // thread
  131. // when we get here
  132. httpSessionLock.lock();// simulating servlet container's
  133. // session lock
  134. try {
  135. mockService.fireSessionDestroy(session);
  136. } finally {
  137. httpSessionLock.unlock();
  138. }
  139. } catch (InterruptedException e) {
  140. throw new RuntimeException(e);
  141. }
  142. }).start();
  143. try {
  144. mockService.findVaadinSession(vaadinRequest);
  145. } catch (Exception e) {
  146. throw new RuntimeException(e);
  147. }
  148. }
  149. @Test
  150. public void threadLocalsAfterUnderlyingSessionTimeout()
  151. throws InterruptedException {
  152. final AtomicBoolean detachCalled = new AtomicBoolean(false);
  153. ui.addDetachListener((DetachEvent event) -> {
  154. detachCalled.set(true);
  155. Assert.assertEquals(ui, UI.getCurrent());
  156. Assert.assertEquals(ui.getPage(), Page.getCurrent());
  157. Assert.assertEquals(session, VaadinSession.getCurrent());
  158. Assert.assertEquals(mockService, VaadinService.getCurrent());
  159. Assert.assertEquals(mockServlet, VaadinServlet.getCurrent());
  160. });
  161. session.valueUnbound(
  162. EasyMock.createMock(HttpSessionBindingEvent.class));
  163. mockService.runPendingAccessTasks(session); // as soon as we changed
  164. // session.accessSynchronously
  165. // to session.access in
  166. // VaadinService.fireSessionDestroy,
  167. // we need to run the
  168. // pending task ourselves
  169. Assert.assertTrue(detachCalled.get());
  170. }
  171. @Test
  172. public void threadLocalsAfterSessionDestroy() throws InterruptedException {
  173. final AtomicBoolean detachCalled = new AtomicBoolean(false);
  174. ui.addDetachListener((DetachEvent event) -> {
  175. detachCalled.set(true);
  176. Assert.assertEquals(ui, UI.getCurrent());
  177. Assert.assertEquals(ui.getPage(), Page.getCurrent());
  178. Assert.assertEquals(session, VaadinSession.getCurrent());
  179. Assert.assertEquals(mockService, VaadinService.getCurrent());
  180. Assert.assertEquals(mockServlet, VaadinServlet.getCurrent());
  181. });
  182. CurrentInstance.clearAll();
  183. session.close();
  184. mockService.cleanupSession(session);
  185. mockService.runPendingAccessTasks(session); // as soon as we changed
  186. // session.accessSynchronously
  187. // to session.access in
  188. // VaadinService.fireSessionDestroy,
  189. // we need to run the
  190. // pending task ourselves
  191. Assert.assertTrue(detachCalled.get());
  192. }
  193. @Test
  194. public void testValueUnbound() {
  195. MockVaadinSession vaadinSession = new MockVaadinSession(mockService);
  196. vaadinSession.valueUnbound(
  197. EasyMock.createMock(HttpSessionBindingEvent.class));
  198. org.junit.Assert.assertEquals(
  199. "'valueUnbound' method doesn't call 'close' for the session", 1,
  200. vaadinSession.getCloseCount());
  201. vaadinSession.valueUnbound(
  202. EasyMock.createMock(HttpSessionBindingEvent.class));
  203. org.junit.Assert.assertEquals(
  204. "'valueUnbound' method may not call 'close' "
  205. + "method for closing session",
  206. 1, vaadinSession.getCloseCount());
  207. }
  208. // Can't define as an anonymous class since it would have a reference to
  209. // VaadinSessionTest.this which isn't serializable
  210. private static class MockPageUI extends UI {
  211. Page page = new Page(this, getState(false).pageState) {
  212. @Override
  213. public void init(VaadinRequest request) {
  214. }
  215. };
  216. @Override
  217. protected void init(VaadinRequest request) {
  218. }
  219. @Override
  220. public Page getPage() {
  221. return page;
  222. }
  223. }
  224. private static class SerializationTestLabel extends Label {
  225. private transient VaadinSession session = VaadinSession.getCurrent();
  226. private void readObject(ObjectInputStream in)
  227. throws IOException, ClassNotFoundException {
  228. in.defaultReadObject();
  229. session = VaadinSession.getCurrent();
  230. }
  231. }
  232. @Test
  233. public void threadLocalsWhenDeserializing() throws Exception {
  234. VaadinSession.setCurrent(session);
  235. session.lock();
  236. SerializationTestLabel label = new SerializationTestLabel();
  237. Assert.assertEquals("Session should be set when instance is created",
  238. session, label.session);
  239. ui.setContent(label);
  240. int uiId = ui.getUIId();
  241. ByteArrayOutputStream bos = new ByteArrayOutputStream();
  242. try (ObjectOutputStream out = new ObjectOutputStream(bos)) {
  243. out.writeObject(session);
  244. }
  245. session.unlock();
  246. CurrentInstance.clearAll();
  247. ObjectInputStream in = new ObjectInputStream(
  248. new ByteArrayInputStream(bos.toByteArray()));
  249. VaadinSession deserializedSession = (VaadinSession) in.readObject();
  250. Assert.assertNull("Current session shouldn't leak from deserialisation",
  251. VaadinSession.getCurrent());
  252. Assert.assertNotSame("Should get a new session", session,
  253. deserializedSession);
  254. // Restore http session and service instance so the session can be
  255. // locked
  256. deserializedSession.refreshTransients(mockWrappedSession, mockService);
  257. deserializedSession.lock();
  258. UI deserializedUi = deserializedSession.getUIById(uiId);
  259. SerializationTestLabel deserializedLabel = (SerializationTestLabel) deserializedUi
  260. .getContent();
  261. Assert.assertEquals(
  262. "Current session should be available in SerializationTestLabel.readObject",
  263. deserializedSession, deserializedLabel.session);
  264. deserializedSession.unlock();
  265. }
  266. @Test
  267. public void lockedDuringSerialization() throws IOException {
  268. final AtomicBoolean lockChecked = new AtomicBoolean(false);
  269. ui.setContent(new Label() {
  270. private void writeObject(ObjectOutputStream out)
  271. throws IOException {
  272. Assert.assertTrue(session.hasLock());
  273. lockChecked.set(true);
  274. out.defaultWriteObject();
  275. }
  276. });
  277. session.unlock();
  278. Assert.assertFalse(session.hasLock());
  279. ObjectOutputStream out = new ObjectOutputStream(
  280. new ByteArrayOutputStream());
  281. out.writeObject(session);
  282. Assert.assertFalse(session.hasLock());
  283. Assert.assertTrue(lockChecked.get());
  284. }
  285. }