You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tomee.apache.org by rm...@apache.org on 2015/11/02 19:08:37 UTC

tomee git commit: TOMEE-1649 adding eviction for 'cdi' websockets

Repository: tomee
Updated Branches:
  refs/heads/master 58ec24151 -> 59e366a86


TOMEE-1649 adding eviction for 'cdi' websockets


Project: http://git-wip-us.apache.org/repos/asf/tomee/repo
Commit: http://git-wip-us.apache.org/repos/asf/tomee/commit/59e366a8
Tree: http://git-wip-us.apache.org/repos/asf/tomee/tree/59e366a8
Diff: http://git-wip-us.apache.org/repos/asf/tomee/diff/59e366a8

Branch: refs/heads/master
Commit: 59e366a86f7b9455701ef520873b7656d9626537
Parents: 58ec241
Author: Romain Manni-Bucau <rm...@gmail.com>
Authored: Mon Nov 2 10:08:13 2015 -0800
Committer: Romain Manni-Bucau <rm...@gmail.com>
Committed: Mon Nov 2 10:08:29 2015 -0800

----------------------------------------------------------------------
 .../org/apache/openejb/core/WebContext.java     |  37 ++++-
 .../tomee/catalina/JavaeeInstanceManager.java   |  16 +-
 .../tomee/catalina/OpenEJBContextConfig.java    |  36 +++-
 .../tomee/catalina/TomEEWebappClassLoader.java  |  15 ++
 .../JavaEEDefaultServerEnpointConfigurator.java | 166 ++++++++++++++++++-
 5 files changed, 258 insertions(+), 12 deletions(-)
----------------------------------------------------------------------


http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java
----------------------------------------------------------------------
diff --git a/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java b/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java
index 8674bfa..55d98c1 100644
--- a/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java
+++ b/container/openejb-core/src/main/java/org/apache/openejb/core/WebContext.java
@@ -53,7 +53,7 @@ public class WebContext {
     private Context jndiEnc;
     private final AppContext appContext;
     private Map<String, Object> bindings;
-    private final Map<Object, CreationalContext<?>> creatonalContexts = new ConcurrentHashMap<Object, CreationalContext<?>>();
+    private final Map<Object, CreationalContext<?>> creationalContexts = new ConcurrentHashMap<>();
     private WebBeansContext webbeansContext;
     private String contextRoot;
     private String host;
@@ -129,8 +129,7 @@ public class WebContext {
         return appContext;
     }
 
-    public Object newInstance(final Class beanClass) throws OpenEJBException {
-
+    public <T> Instance newWeakableInstance(final Class<T> beanClass) throws OpenEJBException {
         final WebBeansContext webBeansContext = getWebBeansContext();
         final ConstructorInjectionBean<Object> beanDefinition = getConstructorInjectionBean(beanClass, webBeansContext);
         final CreationalContext<Object> creationalContext;
@@ -156,10 +155,16 @@ public class WebContext {
         if (webBeansContext != null) {
             final InjectionTargetBean<Object> bean = InjectionTargetBean.class.cast(beanDefinition);
             bean.getInjectionTarget().inject(beanInstance, creationalContext);
+        }
+        return new Instance(beanInstance, creationalContext);
+    }
 
-            creatonalContexts.put(beanInstance, creationalContext);
+    public Object newInstance(final Class beanClass) throws OpenEJBException {
+        final Instance instance = newWeakableInstance(beanClass);
+        if (instance.getCreationalContext() != null) {
+            creationalContexts.put(instance.getValue(), instance.getCreationalContext());
         }
-        return beanInstance;
+        return instance.getValue();
     }
 
     private ConstructorInjectionBean<Object> getConstructorInjectionBean(final Class beanClass, final WebBeansContext webBeansContext) {
@@ -227,7 +232,7 @@ public class WebContext {
                 // if the bean is dependent simply cleanup the creational context once it is created
                 final Class<? extends Annotation> scope = beanDefinition.getScope();
                 if (scope == null || Dependent.class.equals(scope)) {
-                    creatonalContexts.put(beanInstance, creationalContext);
+                    creationalContexts.put(beanInstance, creationalContext);
                 }
             }
 
@@ -262,9 +267,27 @@ public class WebContext {
     }
 
     public void destroy(final Object o) {
-        final CreationalContext<?> ctx = creatonalContexts.remove(o);
+        final CreationalContext<?> ctx = creationalContexts.remove(o);
         if (ctx != null) {
             ctx.release();
         }
     }
+
+    public static class Instance {
+        private final Object value;
+        private final CreationalContext<?> cc;
+
+        public Instance(final Object value, final CreationalContext<?> cc) {
+            this.value = value;
+            this.cc = cc;
+        }
+
+        public Object getValue() {
+            return value;
+        }
+
+        public CreationalContext<?> getCreationalContext() {
+            return cc;
+        }
+    }
 }

http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java
index 506bd00..3a4540e 100644
--- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java
+++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/JavaeeInstanceManager.java
@@ -25,6 +25,7 @@ import org.apache.webbeans.exception.WebBeansCreationException;
 import javax.annotation.PostConstruct;
 import javax.annotation.PreDestroy;
 import javax.naming.NamingException;
+import javax.servlet.ServletContext;
 import java.lang.reflect.InvocationTargetException;
 import java.lang.reflect.Method;
 import java.lang.reflect.Modifier;
@@ -40,6 +41,10 @@ public class JavaeeInstanceManager implements InstanceManager {
         this.webContext = webContext;
     }
 
+    public ServletContext getServletContext() {
+        return webContext == null ? null : webContext.getServletContext();
+    }
+
     @Override
     public Object newInstance(final Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException {
         try {
@@ -51,6 +56,16 @@ public class JavaeeInstanceManager implements InstanceManager {
         }
     }
 
+    public WebContext.Instance newWeakableInstance(final Class<?> clazz) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException {
+        try {
+            final WebContext.Instance object = webContext.newWeakableInstance(clazz);
+            postConstruct(object.getValue(), clazz);
+            return object;
+        } catch (final OpenEJBException | WebBeansCreationException | WebBeansConfigurationException e) {
+            throw (InstantiationException) new InstantiationException(e.getMessage()).initCause(e);
+        }
+    }
+
     @Override
     public Object newInstance(final String className) throws IllegalAccessException, InvocationTargetException, NamingException, InstantiationException, ClassNotFoundException {
         final ClassLoader classLoader = webContext.getClassLoader();
@@ -178,5 +193,4 @@ public class JavaeeInstanceManager implements InstanceManager {
             preDestroy.setAccessible(accessibility);
         }
     }
-
 }

http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
index 357a912..7e2e410 100644
--- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
+++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/OpenEJBContextConfig.java
@@ -63,6 +63,13 @@ import org.apache.webbeans.config.WebBeansContext;
 import org.apache.webbeans.web.context.WebConversationFilter;
 import org.apache.xbean.finder.IAnnotationFinder;
 
+import javax.servlet.ServletContainerInitializer;
+import javax.servlet.ServletContext;
+import javax.servlet.ServletContextEvent;
+import javax.servlet.ServletContextListener;
+import javax.servlet.ServletException;
+import javax.servlet.http.HttpServlet;
+import javax.ws.rs.core.Application;
 import java.io.ByteArrayInputStream;
 import java.io.ByteArrayOutputStream;
 import java.io.File;
@@ -83,9 +90,6 @@ import java.util.List;
 import java.util.Map;
 import java.util.Properties;
 import java.util.Set;
-import javax.servlet.ServletContainerInitializer;
-import javax.servlet.http.HttpServlet;
-import javax.ws.rs.core.Application;
 
 public class OpenEJBContextConfig extends ContextConfig {
     private static Logger logger = Logger.getInstance(LogCategory.OPENEJB, OpenEJBContextConfig.class);
@@ -427,6 +431,32 @@ public class OpenEJBContextConfig extends ContextConfig {
 
         final ClassLoader classLoader = context.getLoader().getClassLoader();
 
+        try {
+            classLoader.loadClass("org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator");
+            context.addServletContainerInitializer(new ServletContainerInitializer() {
+                @Override
+                public void onStartup(final Set<Class<?>> c, final ServletContext ctx) throws ServletException {
+                    ctx.addListener(new ServletContextListener() {
+                        @Override
+                        public void contextInitialized(final ServletContextEvent sce) {
+                            //no -op
+                        }
+
+                        @Override
+                        public void contextDestroyed(final ServletContextEvent sce) { // ensure we cleanup our "eviction" processes
+                            try {
+                                org.apache.tomee.catalina.websocket.JavaEEDefaultServerEnpointConfigurator.unregisterProcesses(sce.getServletContext());
+                            } catch (final Throwable th) {
+                                // no-op
+                            }
+                        }
+                    });
+                }
+            }, null);
+        } catch (final Throwable noWebSocket) {
+            // no-op: ok
+        }
+
         // add myfaces auto-initializer if mojarra is not present
         try {
             classLoader.loadClass("com.sun.faces.context.SessionMap");

http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java
index 1b672b6..17fd006 100644
--- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java
+++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/TomEEWebappClassLoader.java
@@ -430,6 +430,21 @@ public class TomEEWebappClassLoader extends ParallelWebappClassLoader {
             }
             return Collections.enumeration(list);
         }
+        if ("META-INF/services/javax.websocket.ContainerProvider".equals(name)) {
+            final Collection<URL> list = new ArrayList<>(Collections.list(super.getResources(name)));
+            final Iterator<URL> it = list.iterator();
+            while (it.hasNext()) {
+                final URL next = it.next();
+                final File file = Files.toFile(next);
+                if (!file.isFile() && NewLoaderLogic.skip(next)) {
+                    it.remove();
+                }
+            }
+            if (list.size() == 1) {
+                return Collections.enumeration(list);
+            }
+            return Collections.enumeration(list);
+        }
         return URLClassLoaderFirst.filterResources(name, super.getResources(name));
     }
 

http://git-wip-us.apache.org/repos/asf/tomee/blob/59e366a8/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java
----------------------------------------------------------------------
diff --git a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java
index 34df1dc..6613c70 100644
--- a/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java
+++ b/tomee/tomee-catalina/src/main/java/org/apache/tomee/catalina/websocket/JavaEEDefaultServerEnpointConfigurator.java
@@ -16,14 +16,51 @@
  */
 package org.apache.tomee.catalina.websocket;
 
+import org.apache.openejb.core.WebContext;
 import org.apache.openejb.loader.SystemInstance;
 import org.apache.tomcat.InstanceManager;
+import org.apache.tomcat.websocket.BackgroundProcess;
+import org.apache.tomcat.websocket.BackgroundProcessManager;
+import org.apache.tomcat.websocket.WsWebSocketContainer;
+import org.apache.tomcat.websocket.pojo.PojoEndpointBase;
+import org.apache.tomcat.websocket.server.Constants;
 import org.apache.tomcat.websocket.server.DefaultServerEndpointConfigurator;
+import org.apache.tomee.catalina.JavaeeInstanceManager;
 import org.apache.tomee.catalina.TomcatWebAppBuilder;
 
+import javax.servlet.ServletContext;
+import javax.websocket.Endpoint;
+import javax.websocket.Session;
+import javax.websocket.WebSocketContainer;
+import java.lang.reflect.Field;
+import java.lang.reflect.InvocationTargetException;
+import java.lang.reflect.Method;
+import java.util.Collection;
+import java.util.LinkedList;
 import java.util.Map;
+import java.util.Set;
 
 public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpointConfigurator {
+    private static final String BG_PROCESSES_LIST = JavaEEDefaultServerEnpointConfigurator.class.getName() + ".bgProcesses";
+
+    // for websocket eviction
+    private static final Field END_POINT_SESSION_MAP_LOCK;
+    private static final Field ENDPOINT_SESSION_MAP;
+    private static final Method GET_POJO;
+    static {
+        try {
+            ENDPOINT_SESSION_MAP = WsWebSocketContainer.class.getDeclaredField("endpointSessionMap");
+            ENDPOINT_SESSION_MAP.setAccessible(true);
+            END_POINT_SESSION_MAP_LOCK = WsWebSocketContainer.class.getDeclaredField("endPointSessionMapLock");
+            END_POINT_SESSION_MAP_LOCK.setAccessible(true);
+
+            GET_POJO = PojoEndpointBase.class.getDeclaredMethod("getPojo");
+            GET_POJO.setAccessible(true);
+        } catch (final Exception e) {
+            throw new IllegalStateException("Toncat not compatible with tomee", e);
+        }
+    }
+
     private final Map<ClassLoader, InstanceManager> instanceManagers;
 
     public JavaEEDefaultServerEnpointConfigurator() {
@@ -50,7 +87,38 @@ public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpoin
         }
 
         try {
-            return clazz.cast(instanceManager.newInstance(clazz));
+            final Object instance;
+            if (JavaeeInstanceManager.class.isInstance(instanceManager)) {
+                final JavaeeInstanceManager javaeeInstanceManager = JavaeeInstanceManager.class.cast(instanceManager);
+                final WebContext.Instance cdiInstance = javaeeInstanceManager.newWeakableInstance(clazz);
+                instance = cdiInstance.getValue();
+                if (cdiInstance.getCreationalContext() != null) { // TODO: if we manage to have better listeners on tomcat we can use it rather than it
+                    final ServletContext sc = javaeeInstanceManager.getServletContext();
+                    if (sc != null) {
+                        Collection<CdiCleanUpBackgroundProcess> processes;
+                        synchronized (sc) {
+                            processes = (Collection<CdiCleanUpBackgroundProcess>) sc.getAttribute(BG_PROCESSES_LIST);
+                            if (processes == null) {
+                                processes = new LinkedList<>();
+                                sc.setAttribute(BG_PROCESSES_LIST, processes);
+                            }
+                        }
+
+                        final WebSocketContainer wsc = WebSocketContainer.class.cast(sc.getAttribute(Constants.SERVER_CONTAINER_SERVLET_CONTEXT_ATTRIBUTE));
+                        final Object lock = END_POINT_SESSION_MAP_LOCK.get(wsc);
+                        if (wsc != null && WsWebSocketContainer.class.isInstance(wsc)) {
+                            final CdiCleanUpBackgroundProcess process = new CdiCleanUpBackgroundProcess(wsc, cdiInstance, lock);
+                            synchronized (processes) {
+                                processes.add(process);
+                            }
+                            BackgroundProcessManager.getInstance().register(process);
+                        }
+                    }
+                }
+            } else {
+                instance = instanceManager.newInstance(clazz);
+            }
+            return clazz.cast(instance);
         } catch (final Exception e) {
             if (InstantiationException.class.isInstance(e)) {
                 throw InstantiationException.class.cast(e);
@@ -58,4 +126,100 @@ public class JavaEEDefaultServerEnpointConfigurator extends DefaultServerEndpoin
             throw new InstantiationException(e.getMessage());
         }
     }
+
+    public static void unregisterProcesses(final ServletContext sc) { // no sync needed at this point - no more "runtime"
+        final Collection<CdiCleanUpBackgroundProcess> processes = (Collection<CdiCleanUpBackgroundProcess>) sc.getAttribute(BG_PROCESSES_LIST);
+        if (processes == null) {
+            return;
+        }
+        for (final CdiCleanUpBackgroundProcess p : processes) {
+            try {
+                p.stop();
+            } catch (final RuntimeException e) {
+                // no-op
+            }
+        }
+    }
+
+    private static class CdiCleanUpBackgroundProcess implements BackgroundProcess {
+        private volatile int period = 1; // 1s by default
+        private volatile int acceptRetries = 3; // in case there is latency between this call and registerSession()
+        private volatile Set<Session> sessions;
+        private volatile boolean stopped;
+
+        private final WebSocketContainer container;
+        private final Object lock;
+        private final WebContext.Instance cdiInstance;
+
+        private CdiCleanUpBackgroundProcess(final WebSocketContainer wsc, final WebContext.Instance cdiInstance, final Object lock) {
+            this.container = wsc;
+            this.cdiInstance = cdiInstance;
+            this.lock = lock;
+        }
+
+        @Override
+        public void backgroundProcess() {
+            if (!hasSession() && --acceptRetries > 0) {
+                stop();
+            }
+        }
+
+        @Override
+        public void setProcessPeriod(final int period) {
+            this.period = period;
+        }
+
+        @Override
+        public int getProcessPeriod() {
+            return period;
+        }
+
+        private boolean hasSession() {
+            try {
+                if (sessions == null) { // needs to be lazy cause tomcat register sessions after
+                    final Map<Endpoint, Set<Session>> sessionsByEndpoint = (Map<Endpoint, Set<Session>>) ENDPOINT_SESSION_MAP.get(container);
+                    if (sessionsByEndpoint != null) { // find sessions
+                        synchronized (lock) {
+                            for (final Map.Entry<Endpoint, Set<Session>> e : sessionsByEndpoint.entrySet()) {
+                                if (e.getKey() == cdiInstance.getValue()) {
+                                    sessions = e.getValue();
+                                    break;
+                                }
+                                if (PojoEndpointBase.class.isInstance(e.getKey())) {
+                                    try {
+                                        final Object pojo = GET_POJO.invoke(e.getKey());
+                                        if (pojo == cdiInstance.getValue()) {
+                                            sessions = e.getValue();
+                                            break;
+                                        }
+                                    } catch (final InvocationTargetException e1) {
+                                        // no-op
+                                    }
+                                }
+                            }
+                        }
+                    }
+                }
+
+                synchronized (lock) {
+                    return sessions != null && !sessions.isEmpty();
+                }
+            } catch (final IllegalAccessException e) {
+                // no-op
+            }
+            return false;
+        }
+
+        public void stop() {
+            if (stopped) {
+                return;
+            }
+            stopped = true;
+            try {
+                cdiInstance.getCreationalContext().release();
+            } finally {
+                BackgroundProcessManager.getInstance().unregister(this);
+            }
+        }
+    }
 }