]> source.dussan.org Git - vaadin-framework.git/commitdiff
Define how CurrentInstance works with access() (#11897)
authorLeif Åstrand <leif@vaadin.com>
Tue, 28 May 2013 11:55:35 +0000 (14:55 +0300)
committerLeif Åstrand <leif@vaadin.com>
Tue, 28 May 2013 11:55:35 +0000 (14:55 +0300)
Change-Id: I7ca62c5706fd37e7c44ed46703bcdce159b367f4

server/src/com/vaadin/server/VaadinSession.java
server/src/com/vaadin/ui/UI.java
server/src/com/vaadin/util/CurrentInstance.java
uitest/src/com/vaadin/tests/components/ui/UiAccess.html
uitest/src/com/vaadin/tests/components/ui/UiAccess.java

index 9625a3f3504af6420d9dbbe3b0d60becefdfe043..eaf611caad6c8134bbc3c525b513e38f8f5f8893 100644 (file)
@@ -67,6 +67,35 @@ import com.vaadin.util.ReflectTools;
 @SuppressWarnings("serial")
 public class VaadinSession implements HttpSessionBindingListener, Serializable {
 
+    private class FutureAccess extends FutureTask<Void> {
+        /**
+         * Snapshot of all non-inheritable current instances at the time this
+         * object was created.
+         */
+        private final Map<Class<?>, CurrentInstance> instances = CurrentInstance
+                .getInstances(true);
+
+        public FutureAccess(Runnable arg0) {
+            super(arg0, null);
+        }
+
+        @Override
+        public Void get() throws InterruptedException, ExecutionException {
+            /*
+             * Help the developer avoid programming patterns that cause
+             * deadlocks unless implemented very carefully. get(long, TimeUnit)
+             * does not have the same detection since a sensible timeout should
+             * avoid completely locking up the application.
+             * 
+             * Even though no deadlock could occur after the runnable has been
+             * run, the check is always done as the deterministic behavior makes
+             * it easier to detect potential problems.
+             */
+            VaadinService.verifyNoOtherSessionLocked(VaadinSession.this);
+            return super.get();
+        }
+    }
+
     /**
      * 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.
@@ -140,7 +169,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      * session is serialized as long as it doesn't happen while some other
      * thread has the lock.
      */
-    private transient final ConcurrentLinkedQueue<FutureTask<Void>> pendingAccessQueue = new ConcurrentLinkedQueue<FutureTask<Void>>();
+    private transient final ConcurrentLinkedQueue<FutureAccess> pendingAccessQueue = new ConcurrentLinkedQueue<FutureAccess>();
 
     /**
      * Create a new service session tied to a Vaadin service
@@ -1152,9 +1181,13 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      * <p>
      * Please note that the runnable might be invoked on a different thread or
      * later on the current thread, which means that custom thread locals might
-     * not have the expected values when the runnable is executed. The session
-     * and other thread locals provided by Vaadin are set properly before
-     * executing the runnable.
+     * not have the expected values when the runnable is executed. Inheritable
+     * values in {@link CurrentInstance} will have the same values as when this
+     * method was invoked. {@link VaadinSession#getCurrent()} and
+     * {@link VaadinService#getCurrent()} are set according to this session
+     * before executing the runnable. Non-inheritable CurrentInstance values
+     * including {@link VaadinService#getCurrentRequest()} and
+     * {@link VaadinService#getCurrentResponse()} will not be defined.
      * </p>
      * <p>
      * The returned future can be used to check for task completion and to
@@ -1176,23 +1209,7 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
      *         cancel the task
      */
     public Future<Void> access(Runnable runnable) {
-        FutureTask<Void> future = new FutureTask<Void>(runnable, null) {
-            @Override
-            public Void get() throws InterruptedException, ExecutionException {
-                /*
-                 * Help the developer avoid programming patterns that cause
-                 * deadlocks unless implemented very carefully. get(long,
-                 * TimeUnit) does not have the same detection since a sensible
-                 * timeout should avoid completely locking up the application.
-                 * 
-                 * Even though no deadlock could occur after the runnable has
-                 * been run, the check is always done as the deterministic
-                 * behavior makes it easier to detect potential problems.
-                 */
-                VaadinService.verifyNoOtherSessionLocked(VaadinSession.this);
-                return super.get();
-            }
-        };
+        FutureAccess future = new FutureAccess(runnable);
         pendingAccessQueue.add(future);
 
         /*
@@ -1233,11 +1250,26 @@ public class VaadinSession implements HttpSessionBindingListener, Serializable {
     public void runPendingAccessTasks() {
         assert hasLock();
 
-        FutureTask<Void> pendingAccess;
-        while ((pendingAccess = pendingAccessQueue.poll()) != null) {
-            if (!pendingAccess.isCancelled()) {
-                accessSynchronously(pendingAccess);
+        if (pendingAccessQueue.isEmpty()) {
+            return;
+        }
+
+        Map<Class<?>, CurrentInstance> oldInstances = CurrentInstance
+                .getInstances(false);
+
+        FutureAccess pendingAccess;
+        try {
+            while ((pendingAccess = pendingAccessQueue.poll()) != null) {
+                if (!pendingAccess.isCancelled()) {
+                    CurrentInstance.clearAll();
+                    CurrentInstance
+                            .restoreThreadLocals(pendingAccess.instances);
+                    accessSynchronously(pendingAccess);
+                }
             }
+        } finally {
+            CurrentInstance.clearAll();
+            CurrentInstance.restoreThreadLocals(oldInstances);
         }
     }
 
index bbc1d0cd33b23618e813f1290ecbcb8bc9909d67..0b21112c97563de0b50f983a6cdbfaff21f19800 100644 (file)
@@ -1185,9 +1185,14 @@ public abstract class UI extends AbstractSingleComponentContainer implements
      * <p>
      * Please note that the runnable might be invoked on a different thread or
      * later on the current thread, which means that custom thread locals might
-     * not have the expected values when the runnable is executed. The UI and
-     * other thread locals provided by Vaadin are set properly before executing
-     * the runnable.
+     * not have the expected values when the runnable is executed. Inheritable
+     * values in {@link CurrentInstance} will have the same values as when this
+     * method was invoked. {@link UI#getCurrent()},
+     * {@link VaadinSession#getCurrent()} and {@link VaadinService#getCurrent()}
+     * are set according to this UI before executing the runnable.
+     * Non-inheritable CurrentInstance values including
+     * {@link VaadinService#getCurrentRequest()} and
+     * {@link VaadinService#getCurrentResponse()} will not be defined.
      * </p>
      * <p>
      * The returned future can be used to check for task completion and to
index 0854d422fde41d1f36d58a5c3fcc0935cbd3d468..8e6ef8266e146a439ee4fb234101e7df67284bb5 100644 (file)
 package com.vaadin.util;
 
 import java.io.Serializable;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.Map;
 import java.util.Map.Entry;
 
-import com.vaadin.server.VaadinPortlet;
 import com.vaadin.server.VaadinRequest;
 import com.vaadin.server.VaadinResponse;
 import com.vaadin.server.VaadinService;
-import com.vaadin.server.VaadinServlet;
 import com.vaadin.server.VaadinSession;
 import com.vaadin.ui.UI;
 
 /**
- * Keeps track of various thread local instances used by the framework.
+ * Keeps track of various current instances for the current thread. All the
+ * instances are automatically cleared after handling a request from the client
+ * to avoid leaking memory. The inheritable values are also maintained when
+ * execution is moved to another thread, both when a new thread is created and
+ * when {@link VaadinSession#access(Runnable)} or {@link UI#access(Runnable)} is
+ * used.
  * <p>
  * Currently the framework uses the following instances:
  * </p>
  * <p>
- * Inheritable: {@link UI}, {@link VaadinPortlet}, {@link VaadinService},
- * {@link VaadinServlet}, {@link VaadinSession}.
+ * Inheritable: {@link UI}, {@link VaadinService}, {@link VaadinSession}.
  * </p>
  * <p>
  * Non-inheritable: {@link VaadinRequest}, {@link VaadinResponse}.
  * </p>
  * 
  * @author Vaadin Ltd
- * @version @VERSION@
  * @since 7.0.0
  */
 public class CurrentInstance implements Serializable {
@@ -115,7 +117,9 @@ public class CurrentInstance implements Serializable {
 
     /**
      * Sets the current inheritable instance of the given type. A current
-     * instance that is inheritable will be available for child threads.
+     * instance that is inheritable will be available for child threads and in
+     * code run by {@link VaadinSession#access(Runnable)} and
+     * {@link UI#access(Runnable)}.
      * 
      * @see #set(Class, Object)
      * @see InheritableThreadLocal
@@ -183,6 +187,34 @@ public class CurrentInstance implements Serializable {
         }
     }
 
+    /**
+     * Gets the currently set instances so that they can later be restored using
+     * {@link #restoreThreadLocals(Map)}.
+     * 
+     * @since 7.1
+     * 
+     * @param onlyInheritable
+     *            <code>true</code> if only the inheritable instances should be
+     *            included; <code>false</code> to get all instances.
+     * @return a map containing the current instances
+     */
+    public static Map<Class<?>, CurrentInstance> getInstances(
+            boolean onlyInheritable) {
+        Map<Class<?>, CurrentInstance> map = instances.get();
+        if (map == null) {
+            return Collections.emptyMap();
+        } else {
+            Map<Class<?>, CurrentInstance> copy = new HashMap<Class<?>, CurrentInstance>();
+            for (Class<?> c : map.keySet()) {
+                CurrentInstance ci = map.get(c);
+                if (ci.inheritable || !onlyInheritable) {
+                    copy.put(c, ci);
+                }
+            }
+            return copy;
+        }
+    }
+
     /**
      * Sets thread locals for the UI and all related classes
      * 
index 664b15c16f03f1e72cc94859c89ce7d4fe98249c..8ae2f2f48e06926dd69d492060eb1c7ef84326a4 100644 (file)
        <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
        <td>2. I was interrupted</td>
 </tr>
-
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[5]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_3</td>
+       <td>0. accessSynchronously has request? true</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td>
+       <td>1. Test value in accessSynchronously: Set before accessSynchronosly</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td>
+       <td>2. has request after accessSynchronously? true</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
+       <td>3. Test value after accessSynchornously: Set in accessSynchronosly</td>
+</tr>
+<tr>
+       <td>click</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::/VVerticalLayout[0]/Slot[2]/VVerticalLayout[0]/Slot[6]/VButton[0]/domChild[0]/domChild[0]</td>
+       <td></td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_3</td>
+       <td>0. access has request? false</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_2</td>
+       <td>1. Test value in access: Set before access</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_1</td>
+       <td>2. has request after access? true</td>
+</tr>
+<tr>
+       <td>assertText</td>
+       <td>vaadin=runcomvaadintestscomponentsuiUiAccess::PID_SLog_row_0</td>
+       <td>3. Test value after access: Set before run pending</td>
+</tr>
 </tbody></table>
 </body>
 </html>
index c68da6ee5486b16be381dedaeb27f84c73031dd5..c39f65243d1f55d93cd84bc45a8f9af9a11cc6e5 100644 (file)
@@ -26,11 +26,25 @@ import com.vaadin.server.VaadinService;
 import com.vaadin.tests.components.AbstractTestUIWithLog;
 import com.vaadin.ui.Button;
 import com.vaadin.ui.Button.ClickEvent;
+import com.vaadin.util.CurrentInstance;
 
 public class UiAccess extends AbstractTestUIWithLog {
 
     private Future<Void> checkFromBeforeClientResponse;
 
+    private class CurrentInstanceTestType {
+        private String value;
+
+        public CurrentInstanceTestType(String value) {
+            this.value = value;
+        }
+
+        @Override
+        public String toString() {
+            return value;
+        }
+    }
+
     @Override
     protected void setup(VaadinRequest request) {
         addComponent(new Button("Access from UI thread",
@@ -211,6 +225,74 @@ public class UiAccess extends AbstractTestUIWithLog {
                         }.start();
                     }
                 }));
+        addComponent(new Button("CurrentInstance accessSynchronously values",
+                new Button.ClickListener() {
+                    @Override
+                    public void buttonClick(ClickEvent event) {
+                        log.clear();
+                        // accessSynchronously should maintain values
+                        CurrentInstance.set(CurrentInstanceTestType.class,
+                                new CurrentInstanceTestType(
+                                        "Set before accessSynchronosly"));
+                        accessSynchronously(new Runnable() {
+                            @Override
+                            public void run() {
+                                log.log("accessSynchronously has request? "
+                                        + (VaadinService.getCurrentRequest() != null));
+                                log.log("Test value in accessSynchronously: "
+                                        + CurrentInstance
+                                                .get(CurrentInstanceTestType.class));
+                                CurrentInstance.set(
+                                        CurrentInstanceTestType.class,
+                                        new CurrentInstanceTestType(
+                                                "Set in accessSynchronosly"));
+                            }
+                        });
+                        log.log("has request after accessSynchronously? "
+                                + (VaadinService.getCurrentRequest() != null));
+                        log("Test value after accessSynchornously: "
+                                + CurrentInstance
+                                        .get(CurrentInstanceTestType.class));
+                    }
+                }));
+        addComponent(new Button("CurrentInstance access values",
+                new Button.ClickListener() {
+                    @Override
+                    public void buttonClick(ClickEvent event) {
+                        log.clear();
+                        // accessSynchronously should maintain values
+                        CurrentInstance
+                                .setInheritable(CurrentInstanceTestType.class,
+                                        new CurrentInstanceTestType(
+                                                "Set before access"));
+                        access(new Runnable() {
+                            @Override
+                            public void run() {
+                                log.log("access has request? "
+                                        + (VaadinService.getCurrentRequest() != null));
+                                log.log("Test value in access: "
+                                        + CurrentInstance
+                                                .get(CurrentInstanceTestType.class));
+                                CurrentInstance.setInheritable(
+                                        CurrentInstanceTestType.class,
+                                        new CurrentInstanceTestType(
+                                                "Set in access"));
+                            }
+                        });
+                        CurrentInstance.setInheritable(
+                                CurrentInstanceTestType.class,
+                                new CurrentInstanceTestType(
+                                        "Set before run pending"));
+
+                        getSession().runPendingAccessTasks();
+
+                        log.log("has request after access? "
+                                + (VaadinService.getCurrentRequest() != null));
+                        log("Test value after access: "
+                                + CurrentInstance
+                                        .get(CurrentInstanceTestType.class));
+                    }
+                }));
     }
 
     @Override