[ 
https://issues.apache.org/jira/browse/CAMEL-12969?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16713842#comment-16713842
 ] 

ASF GitHub Bot commented on CAMEL-12969:
----------------------------------------

bobpaulin closed pull request #2647: CAMEL-12969: Adding ServiceReference Cache 
to prevent memory leak.
URL: https://github.com/apache/camel/pull/2647
 
 
   

This is a PR merged from a forked repository.
As GitHub hides the original diff on merge, it is displayed below for
the sake of provenance:

As this is a foreign pull request (from a fork), the diff is supplied
below (as it won't show otherwise due to GitHub magic):

diff --git 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiCamelContextHelper.java
 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiCamelContextHelper.java
index 08ff669c59f..2b9b1fc4e60 100644
--- 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiCamelContextHelper.java
+++ 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiCamelContextHelper.java
@@ -56,14 +56,23 @@ public static void osgiUpdate(DefaultCamelContext 
camelContext, BundleContext bu
     public static Registry wrapRegistry(CamelContext camelContext, Registry 
registry, BundleContext bundleContext) {
         ObjectHelper.notNull(bundleContext, "BundleContext");
 
-        LOG.debug("Setting up OSGi ServiceRegistry");
-        OsgiServiceRegistry osgiServiceRegistry = new 
OsgiServiceRegistry(bundleContext);
+        OsgiServiceRegistry osgiServiceRegistry = null;
+        Registry resultingRegistry = registry;
+        if(registry instanceof OsgiServiceRegistry) {
+            osgiServiceRegistry = (OsgiServiceRegistry)registry;
+        } else {
+            LOG.debug("Wrapping Registry in OsgiServiceRegistry");
+            osgiServiceRegistry = new OsgiServiceRegistry(bundleContext);
+            CompositeRegistry compositeRegistry = new CompositeRegistry();
+            compositeRegistry.addRegistry(osgiServiceRegistry);
+            compositeRegistry.addRegistry(registry);
+            resultingRegistry = compositeRegistry;
+        }
+        
         // Need to clean up the OSGi service when camel context is closed.
         camelContext.addLifecycleStrategy(osgiServiceRegistry);
-        CompositeRegistry compositeRegistry = new CompositeRegistry();
-        compositeRegistry.addRegistry(osgiServiceRegistry);
-        compositeRegistry.addRegistry(registry);
-        return compositeRegistry;
+        
+        return resultingRegistry;
     }
 
 }
diff --git 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiDefaultCamelContext.java
 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiDefaultCamelContext.java
index 20e3a21eca0..821ef5d7978 100644
--- 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiDefaultCamelContext.java
+++ 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiDefaultCamelContext.java
@@ -32,7 +32,6 @@
 public class OsgiDefaultCamelContext extends DefaultCamelContext {
 
     private final BundleContext bundleContext;
-    private final Registry registry;
 
     public OsgiDefaultCamelContext(BundleContext bundleContext) {
         this(bundleContext, new OsgiServiceRegistry(bundleContext));
@@ -41,7 +40,7 @@ public OsgiDefaultCamelContext(BundleContext bundleContext) {
     public OsgiDefaultCamelContext(BundleContext bundleContext, Registry 
registry) {
         super(registry);
         this.bundleContext = bundleContext;
-        this.registry = registry;
+        setRegistry(OsgiCamelContextHelper.wrapRegistry(this, registry, 
bundleContext));
         OsgiCamelContextHelper.osgiUpdate(this, bundleContext);
         // setup the application context classloader with the bundle 
classloader
         setApplicationContextClassLoader(new 
BundleDelegatingClassLoader(bundleContext.getBundle()));
@@ -52,15 +51,6 @@ public OsgiDefaultCamelContext(BundleContext bundleContext, 
Registry registry) {
         return BundleContextUtils.findComponents(bundleContext, this);
     }
 
-    @Override
-    protected Registry createRegistry() {
-        if (registry != null) {
-            return OsgiCamelContextHelper.wrapRegistry(this, registry, 
bundleContext);
-        } else {
-            return OsgiCamelContextHelper.wrapRegistry(this, 
super.createRegistry(), bundleContext);
-        }
-    }
-
     @Override
     protected TypeConverter createTypeConverter() {
         // CAMEL-3614: make sure we use a bundle context which imports 
org.apache.camel.impl.converter package
diff --git 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiServiceRegistry.java
 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiServiceRegistry.java
index 4569962da2d..3cbbad8e2fb 100644
--- 
a/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiServiceRegistry.java
+++ 
b/components/camel-core-osgi/src/main/java/org/apache/camel/core/osgi/OsgiServiceRegistry.java
@@ -21,15 +21,24 @@
 import java.util.Map;
 import java.util.Queue;
 import java.util.Set;
+import java.util.concurrent.BlockingQueue;
+import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentLinkedQueue;
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.LinkedBlockingQueue;
 
 import org.apache.camel.CamelContext;
+import org.apache.camel.VetoCamelContextStartException;
 import org.apache.camel.spi.Registry;
 import org.apache.camel.support.LifecycleStrategySupport;
 import org.apache.camel.util.ObjectHelper;
+import org.apache.camel.util.concurrent.CamelThreadFactory;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.Constants;
 import org.osgi.framework.InvalidSyntaxException;
+import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceListener;
 import org.osgi.framework.ServiceReference;
 import org.slf4j.Logger;
 import org.slf4j.LoggerFactory;
@@ -37,13 +46,25 @@
 /**
  * The OsgiServiceRegistry support to get the service object from the bundle 
context
  */
-public class OsgiServiceRegistry extends LifecycleStrategySupport implements 
Registry {
-    private static final Logger LOG = 
LoggerFactory.getLogger(OsgiCamelContextHelper.class);
+public class OsgiServiceRegistry extends LifecycleStrategySupport implements 
Registry, ServiceListener {
+    private static final Logger LOG = 
LoggerFactory.getLogger(OsgiServiceRegistry.class);
     private final BundleContext bundleContext;
     private final Queue<ServiceReference<?>> serviceReferenceQueue = new 
ConcurrentLinkedQueue<>();
+    private final BlockingQueue<ServiceReference<?>> 
unregisteredServiceReferenceQueue = new LinkedBlockingQueue<>();
+    private final Map<ServiceReference<?>, Object> serviceCacheMap = new 
ConcurrentHashMap<>();
+    private ExecutorService executorService;
     
     public OsgiServiceRegistry(BundleContext bc) {
         bundleContext = bc;
+        bundleContext.addServiceListener(this);
+    }
+    
+    @Override
+    public void onContextStart(CamelContext context) throws 
VetoCamelContextStartException {
+        //Start the ServiceReference Cleanup Task.
+        executorService = Executors.newSingleThreadExecutor(new 
CamelThreadFactory("Camel (" + context.getName() + ") thread ##counter# - 
#name#", "OSGiServiceReferenceCleanupThread", true));
+
+        executorService.execute(new OsgiServiceReferenceCleanupTask());
     }
 
     /**
@@ -57,8 +78,7 @@ public OsgiServiceRegistry(BundleContext bc) {
             if (refs != null && refs.length > 0) {
                 // just return the first one
                 sr = refs[0];
-                serviceReferenceQueue.add(sr);
-                service = bundleContext.getService(sr);
+                service = getService(sr);
             }
         } catch (Exception ex) {
             throw ObjectHelper.wrapRuntimeCamelException(ex);
@@ -90,8 +110,7 @@ public Object lookupByName(String name) {
         if (sr != null) {
             // Need to keep the track of Service
             // and call ungetService when the camel context is closed 
-            serviceReferenceQueue.add(sr);
-            service = bundleContext.getService(sr);
+            service = getService(sr);
         }
         return service;
     }
@@ -104,8 +123,7 @@ public Object lookupByName(String name) {
             if (refs != null) {
                 for (ServiceReference<?> sr : refs) {
                     if (sr != null) {
-                        Object service = bundleContext.getService(sr);
-                        serviceReferenceQueue.add(sr);
+                        Object service = getService(sr);
                         if (service != null) {
                             String name = (String)sr.getProperty("name");
                             if (name != null) {
@@ -152,6 +170,48 @@ public void onContextStop(CamelContext context) {
         }
         // Clean up the OSGi Service Cache
         serviceReferenceQueue.clear();
+        serviceCacheMap.clear();
+        unregisteredServiceReferenceQueue.clear();
+        executorService.shutdownNow();
+        executorService = null;
+    }
+    
+    @Override
+    public void serviceChanged(ServiceEvent event) {
+        if( event.getType() == ServiceEvent.UNREGISTERING) {
+                
this.unregisteredServiceReferenceQueue.add(event.getServiceReference());
+        }
+    }
+    
+    private Object getService(ServiceReference<?> sr) {
+        Object service = this.serviceCacheMap.get(sr);
+        if(service == null) {
+            service = this.bundleContext.getService(sr);
+            serviceReferenceQueue.add(sr);
+            if(service != null) {
+                this.serviceCacheMap.put(sr, service);
+            }
+        }
+        return service;
+    }
+    
+    class OsgiServiceReferenceCleanupTask implements Runnable {
+        @Override
+        public void run() {
+            ServiceReference<?> serviceReference = null;
+            try {
+                while((serviceReference = 
unregisteredServiceReferenceQueue.take()) != null) {
+                    if(!bundleContext.ungetService(serviceReference)) {
+                        serviceCacheMap.remove(serviceReference);
+                        serviceReferenceQueue.remove(serviceReference);
+                    }
+                    else {
+                        
unregisteredServiceReferenceQueue.add(serviceReference);
+                    }
+                }
+            } catch (InterruptedException e) {
+                LOG.info("Camel Osgi Service Reference Clean up Interrupted", 
e);
+            }
+        }
     }
-
 }


 

----------------------------------------------------------------
This is an automated message from the Apache Git Service.
To respond to the message, please log on GitHub and use the
URL above to go to the specific comment.
 
For queries about this service, please contact Infrastructure at:
[email protected]


> camel-core-osgi: Slow Memory Leak in OsgiServiceRegistry
> --------------------------------------------------------
>
>                 Key: CAMEL-12969
>                 URL: https://issues.apache.org/jira/browse/CAMEL-12969
>             Project: Camel
>          Issue Type: Bug
>          Components: camel-osgi
>    Affects Versions: 2.18.0, 2.19.0, 2.20.0, 2.21.0, 2.22.0, 2.23.0
>         Environment: Java 10
> Karaf 4.2.1
> Camel 2.22.0
>            Reporter: Bob Paulin
>            Priority: Major
>             Fix For: 2.22.3, 2.24.0, 2.23.1
>
>         Attachments: ServiceReferenceQueueLeak.PNG, 
> ServiceReferenceQueuePostContextStop.PNG, 
> ServiceReferenceQueuePreContextStop.PNG, karafCamelContextStop.PNG
>
>
> The OsgiServiceRegistry has a slow memory leak in the serviceReferenceQueue.  
> Currently every time a service is looked up by any method an item is added to 
> the serviceReferenceQueue.  This is required because of OSGi ServiceReference 
> counting.  However left unchecked the system just continues to add 
> ConcurrentLinkedQueue$Node objects until memory is exhausted.
> !ServiceReferenceQueueLeak.PNG! . 
>  
> There is also a second problem with how the registry is being managed within 
> the OsgiDefaultCamelContext.  OsgiServiceRegistry is currently extends 
> LifecycleStrategySupport which is suppose to unload the serviceReferenceQueue 
> onContextStop.  However the registry is never getting added to the 
> CamelContext to manage the Lifecycle because the overridden createRegistry 
> method in OsgiDefaultCamelContext is not being called.  This is because the 
> registry is being set in the constructor of OsgiDefaultCamelContext with
> {code:java}
> super(registry);{code}
> this calls the DefaultCamelContext implementation of createRegistry which 
> does not add the registry to lifecyclemanagement since
> {code:java}
> OsgiCamelContextHelper.wrapRegistry(this, registry, bundleContext);{code}
> is never called. 
> See serviceReferenceQueue  pre context stop
>   !ServiceReferenceQueuePreContextStop.PNG!
> !karafCamelContextStop.PNG!
> See serviceReferenceQueue   post context stop (still contain objects)
>   !ServiceReferenceQueuePostContextStop.PNG!
> Both issues would have existed for some time but may have gone unnoticed 
> because the leak was so slow (ConcurrentLinkedQueue$Node takes up very little 
> memory).  It appears the removal of the cache in 
> https://issues.apache.org/jira/browse/CAMEL-9631 makes the leak occur more 
> quickly. 
>  
> I have a patch that involves reintroducing the cache but with an invalidation 
> strategy using the OSGi ServiceListener that leverages a single clean up 
> thread to remain non-blocking.  I'm working on an upstream adaptation and 
> will post a PR for community review.
>  
>  



--
This message was sent by Atlassian JIRA
(v7.6.3#76005)

Reply via email to