You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@felix.apache.org by da...@apache.org on 2015/05/14 11:09:09 UTC

svn commit: r1679327 - in /felix/trunk: framework/ framework/src/main/java/org/apache/felix/framework/ framework/src/test/java/org/apache/felix/framework/ main/

Author: davidb
Date: Thu May 14 09:09:08 2015
New Revision: 1679327

URL: http://svn.apache.org/r1679327
Log:
Felix Framework Service Registry performance improvements

Implemented through the use of Java 5 concurrency APIs.
Unit tests included for the code that I changed.

Modified:
    felix/trunk/framework/pom.xml
    felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
    felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
    felix/trunk/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java
    felix/trunk/main/pom.xml

Modified: felix/trunk/framework/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/framework/pom.xml?rev=1679327&r1=1679326&r2=1679327&view=diff
==============================================================================
--- felix/trunk/framework/pom.xml (original)
+++ felix/trunk/framework/pom.xml Thu May 14 09:09:08 2015
@@ -144,5 +144,11 @@
         <version>4.2</version>
         <scope>test</scope>
     </dependency>
+    <dependency>
+        <groupId>org.mockito</groupId>
+        <artifactId>mockito-all</artifactId>
+        <version>1.10.19</version>
+        <scope>test</scope>
+    </dependency>
   </dependencies>
 </project>

Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java?rev=1679327&r1=1679326&r2=1679327&view=diff
==============================================================================
--- felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java (original)
+++ felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistrationImpl.java Thu May 14 09:09:08 2015
@@ -65,8 +65,8 @@ class ServiceRegistrationImpl implements
     private final ServiceReferenceImpl m_ref;
     // Flag indicating that we are unregistering.
     private volatile boolean m_isUnregistering = false;
-
-    private volatile Thread lock;
+    // This threadlocal is used to detect cycles.
+    private final ThreadLocal<Boolean> m_threadLoopDetection = new ThreadLocal<Boolean>();
 
     private final Object syncObject = new Object();
 
@@ -90,7 +90,7 @@ class ServiceRegistrationImpl implements
         m_ref = new ServiceReferenceImpl();
     }
 
-    protected synchronized boolean isValid()
+    protected boolean isValid()
     {
         return (m_svcObj != null);
     }
@@ -758,35 +758,18 @@ class ServiceRegistrationImpl implements
         }
     }
 
-    public boolean isLocked()
+    boolean currentThreadMarked()
     {
-        return this.lock == Thread.currentThread();
+        return m_threadLoopDetection.get() != null;
     }
 
-    public void lock()
+    void markCurrentThread()
     {
-        synchronized ( this.syncObject )
-        {
-            while ( this.lock != null )
-            {
-                try
-                {
-                    this.syncObject.wait();
-                }
-                catch ( final InterruptedException re) {
-                    // nothing to do
-                }
-            }
-            this.lock = Thread.currentThread();
-        }
+        m_threadLoopDetection.set(Boolean.TRUE);
     }
 
-    public void unlock()
+    void unmarkCurrentThread()
     {
-        synchronized ( this.syncObject )
-        {
-            this.lock = null;
-            this.syncObject.notifyAll();
-        }
+        m_threadLoopDetection.set(null);
     }
 }
\ No newline at end of file

Modified: felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java?rev=1679327&r1=1679326&r2=1679327&view=diff
==============================================================================
--- felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java (original)
+++ felix/trunk/framework/src/main/java/org/apache/felix/framework/ServiceRegistry.java Thu May 14 09:09:08 2015
@@ -27,7 +27,10 @@ import java.util.List;
 import java.util.Map;
 import java.util.concurrent.ConcurrentHashMap;
 import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicInteger;
 import java.util.concurrent.atomic.AtomicLong;
+import java.util.concurrent.atomic.AtomicReference;
 
 import org.apache.felix.framework.capabilityset.CapabilitySet;
 import org.apache.felix.framework.capabilityset.SimpleFilter;
@@ -112,7 +115,7 @@ public class ServiceRegistry
         final Bundle bundle,
         final String[] classNames,
         final Object svcObj,
-        final Dictionary dict)
+        final Dictionary<?,?> dict)
     {
         // Create the service registration.
         final ServiceRegistrationImpl reg = new ServiceRegistrationImpl(
@@ -150,12 +153,6 @@ public class ServiceRegistry
         // If this is a hook, it should be removed.
         this.hookRegistry.removeHooks(reg.getReference());
 
-        // Note that we don't lock the service registration here using
-        // the m_lockedRegsMap because we want to allow bundles to get
-        // the service during the unregistration process. However, since
-        // we do remove the registration from the service registry, no
-        // new bundles will be able to look up the service.
-
         // Now remove the registered service.
         final List<ServiceRegistration<?>> regs = m_regsMap.get(bundle);
         if (regs != null)
@@ -197,7 +194,7 @@ public class ServiceRegistry
             {
                 if (usages[x].m_ref.equals(ref))
                 {
-                    ungetService(clients[i], ref, (usages[x].m_prototype ? usages[x].m_svcObj : null));
+                    ungetService(clients[i], ref, (usages[x].m_prototype ? usages[x].getService() : null));
                 }
             }
         }
@@ -300,7 +297,7 @@ public class ServiceRegistry
             ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
 
         // We don't allow cycles when we call out to the service factory.
-        if ( reg.isLocked() )
+        if ( reg.currentThreadMarked() )
         {
             throw new ServiceException(
                     "ServiceFactory.getService() resulted in a cycle.",
@@ -308,63 +305,75 @@ public class ServiceRegistry
                     null);
         }
 
-        // no concurrent operations on the same service registration
-        reg.lock();
-        // Make sure the service registration is still valid.
-        if (reg.isValid())
-        {
-            // Get the usage count, if any.
-            // if prototype, we always create a new usage
-            usage = isPrototype ? null : getUsageCount(bundle, ref, null);
-
-            // If we don't have a usage count, then create one and
-            // since the spec says we increment usage count before
-            // actually getting the service object.
-            if (usage == null)
-            {
-                usage = addUsageCount(bundle, ref, isPrototype);
-            }
-
-            // Increment the usage count and grab the already retrieved
-            // service object, if one exists.
-            usage.m_count++;
-            svcObj = usage.m_svcObj;
-            if ( isServiceObjects )
-            {
-                usage.m_serviceObjectsCount++;
-            }
-        }
-
-        // If we have a usage count, but no service object, then we haven't
-        // cached the service object yet, so we need to create one now without
-        // holding the lock, since we will potentially call out to a service
-        // factory.
         try
         {
-            if ((usage != null) && (svcObj == null))
+            reg.markCurrentThread();
+
+            // Make sure the service registration is still valid.
+            if (reg.isValid())
             {
-                svcObj = reg.getService(bundle);
+                // Get the usage count, or create a new one. If this is a
+                // prototype, the we'll alway create a new one.
+                usage = obtainUsageCount(bundle, ref, null, isPrototype);
+
+                // Increment the usage count and grab the already retrieved
+                // service object, if one exists.
+                usage.m_count.incrementAndGet();
+                svcObj = usage.getService();
+                if ( isServiceObjects )
+                {
+                    usage.m_serviceObjectsCount.incrementAndGet();
+                }
+
+                // If we have a usage count, but no service object, then we haven't
+                // cached the service object yet, so we need to create one.
+                if ((usage != null) && (svcObj == null))
+                {
+                    ServiceHolder holder = null;
+
+                    // There is a possibility that the holder is unset between the compareAndSet() and the get()
+                    // below. If that happens get() returns null and we may have to set a new holder. This is
+                    // why the below section is in a loop.
+                    while (holder == null)
+                    {
+                        ServiceHolder h = new ServiceHolder();
+                        if (usage.m_svcHolderRef.compareAndSet(null, h))
+                        {
+                            holder = h;
+                            svcObj = reg.getService(bundle);
+                            holder.m_service = svcObj;
+                            holder.m_latch.countDown();
+                        }
+                        else
+                        {
+                            holder = usage.m_svcHolderRef.get();
+                            if (holder != null)
+                            {
+                                try
+                                {
+                                    // Need to ensure that the other thread has obtained
+                                    // the service.
+                                    holder.m_latch.await();
+                                }
+                                catch (InterruptedException e)
+                                {
+                                    throw new RuntimeException(e);
+                                }
+                                svcObj = holder.m_service;
+                            }
+                        }
+                    }
+                }
             }
         }
         finally
         {
-            // If we successfully retrieved a service object, then we should
-            // cache it in the usage count. If not, we should flush the usage
-            // count. Either way, we need to unlock the service registration
-            // so that any threads waiting for it can continue.
-
-            // Before caching the service object, double check to see if
-            // the registration is still valid, since it may have been
-            // unregistered while we didn't hold the lock.
+            reg.unmarkCurrentThread();
+
             if (!reg.isValid() || (svcObj == null))
             {
                 flushUsageCount(bundle, ref, usage);
             }
-            else
-            {
-                usage.m_svcObj = svcObj;
-            }
-            reg.unlock();
         }
 
         return (S) svcObj;
@@ -375,73 +384,66 @@ public class ServiceRegistry
         final ServiceRegistrationImpl reg =
             ((ServiceRegistrationImpl.ServiceReferenceImpl) ref).getRegistration();
 
-        if ( reg.isLocked() )
+        if ( reg.currentThreadMarked() )
         {
             throw new IllegalStateException(
                     "ServiceFactory.ungetService() resulted in a cycle.");
         }
 
-        UsageCount usage = null;
+        try
+        {
+            // Mark the current thread to avoid cycles
+            reg.markCurrentThread();
 
-        // First make sure that no existing operation is currently
-        // being performed by another thread on the service registration.
-        reg.lock();
-
-        // Get the usage count.
-        usage = getUsageCount(bundle, ref, svcObj);
-        // If there is no cached services, then just return immediately.
-        if (usage == null)
-        {
-            reg.unlock();
-            return false;
-        }
-        // if this is a call from service objects and the service was not fetched from
-        // there, return false
-        if ( svcObj != null )
-        {
-            // TODO have a proper conditional decrement and get, how???
-            usage.m_serviceObjectsCount--;
-            if (usage.m_serviceObjectsCount < 0)
+            // Get the usage count.
+            UsageCount usage = obtainUsageCount(bundle, ref, svcObj, null);
+            // If there are no cached services, then just return immediately.
+            if (usage == null)
             {
-                reg.unlock();
                 return false;
             }
-        }
+            // if this is a call from service objects and the service was not fetched from
+            // there, return false
+            if ( svcObj != null )
+            {
+                if (usage.m_serviceObjectsCount.decrementAndGet() < 0)
+                {
+                    return false;
+                }
+            }
 
-        // If usage count will go to zero, then unget the service
-        // from the registration; we do this outside the lock
-        // since this might call out to the service factory.
-        try
-        {
-            if (usage.m_count == 1)
+            // If usage count will go to zero, then unget the service
+            // from the registration.
+            try
             {
-                // Remove reference from usages array.
-                ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
-                    .getRegistration().ungetService(bundle, usage.m_svcObj);
+                if (usage.m_count.get() == 1)
+                {
+                    // Remove reference from usages array.
+                    ((ServiceRegistrationImpl.ServiceReferenceImpl) ref)
+                        .getRegistration().ungetService(bundle, usage.getService());
+                }
+            }
+            finally
+            {
+                // Finally, decrement usage count and flush if it goes to zero or
+                // the registration became invalid.
+
+                // Decrement usage count, which spec says should happen after
+                // ungetting the service object.
+                int c = usage.m_count.decrementAndGet();
+
+                // If the registration is invalid or the usage count has reached
+                // zero, then flush it.
+                if ((c <= 0) || !reg.isValid())
+                {
+                    usage.m_svcHolderRef.set(null);
+                    flushUsageCount(bundle, ref, usage);
+                }
             }
         }
         finally
         {
-            // Finally, decrement usage count and flush if it goes to zero or
-            // the registration became invalid while we were not holding the
-            // lock. Either way, unlock the service registration so that any
-            // threads waiting for it can continue.
-
-            // Decrement usage count, which spec says should happen after
-            // ungetting the service object.
-            usage.m_count--;
-
-            // If the registration is invalid or the usage count has reached
-            // zero, then flush it.
-            if (!reg.isValid() || (usage.m_count <= 0))
-            {
-                usage.m_svcObj = null;
-                flushUsageCount(bundle, ref, usage);
-            }
-
-            // Release the registration lock so any waiting threads can
-            // continue.
-            reg.unlock();
+            reg.unmarkCurrentThread();
         }
 
         return true;
@@ -471,7 +473,7 @@ public class ServiceRegistry
         for (int i = 0; i < usages.length; i++)
         {
             // Keep ungetting until all usage count is zero.
-            while (ungetService(bundle, usages[i].m_ref, usages[i].m_prototype ? usages[i].m_svcObj : null))
+            while (ungetService(bundle, usages[i].m_ref, usages[i].m_prototype ? usages[i].getService() : null))
             {
                 // Empty loop body.
             }
@@ -508,7 +510,7 @@ public class ServiceRegistry
         return bundles;
     }
 
-    void servicePropertiesModified(ServiceRegistration<?> reg, Dictionary oldProps)
+    void servicePropertiesModified(ServiceRegistration<?> reg, Dictionary<?,?> oldProps)
     {
         this.hookRegistry.updateHooks(reg.getReference());
         if (m_callbacks != null)
@@ -524,56 +526,66 @@ public class ServiceRegistry
     }
 
     /**
-     * Utility method to retrieve the specified bundle's usage count for the
-     * specified service reference.
-     * @param bundle The bundle whose usage counts are being searched.
-     * @param ref The service reference to find in the bundle's usage counts.
-     * @return The associated usage count or null if not found.
-    **/
-    private UsageCount getUsageCount(Bundle bundle, ServiceReference<?> ref, final Object svcObj)
+     * Obtain a UsageCount object, by looking for an existing one or creating a new one (if possible).
+     * This method tries to find a UsageCount object in the {@code m_inUseMap}. If one is found then
+     * this is returned, otherwise a UsageCount object will be created, but this can only be done if
+     * the {@code isPrototype} parameter is not {@code null}. If {@code isPrototype} is {@code TRUE}
+     * then a new UsageCount object will always be created.
+     * @param bundle The bundle using the service.
+     * @param ref The Service Reference.
+     * @param svcObj A Service Object, if applicable.
+     * @param isPrototype {@code TRUE} if we know that this is a prototype, {@ FALSE} if we know that
+     * it isn't. There are cases where we don't know (the pure lookup case), in that case use {@code null}.
+     * @return The UsageCount object if it could be obtained, or {@code null} otherwise.
+     */
+    UsageCount obtainUsageCount(Bundle bundle, ServiceReference<?> ref, Object svcObj, Boolean isPrototype)
     {
-        UsageCount[] usages = m_inUseMap.get(bundle);
-        for (int i = 0; (usages != null) && (i < usages.length); i++)
+        UsageCount usage = null;
+
+        // This method uses an optimistic concurrency mechanism with a conditional put/replace
+        // on the m_inUseMap. If this fails (because another thread made changes) this thread
+        // retries the operation. This is the purpose of the while loop.
+        boolean success = false;
+        while (!success)
         {
-            if (usages[i].m_ref.equals(ref)
-               && ((svcObj == null && !usages[i].m_prototype) || usages[i].m_svcObj == svcObj))
+            UsageCount[] usages = m_inUseMap.get(bundle);
+
+            // If we know it's a prototype, then we always need to create a new usage count
+            if (!Boolean.TRUE.equals(isPrototype))
             {
-                return usages[i];
+                for (int i = 0; (usages != null) && (i < usages.length); i++)
+                {
+                    if (usages[i].m_ref.equals(ref)
+                       && ((svcObj == null && !usages[i].m_prototype) || usages[i].getService() == svcObj))
+                    {
+                        return usages[i];
+                    }
+                }
             }
-        }
-        return null;
-    }
 
-    /**
-     * Utility method to update the specified bundle's usage count array to
-     * include the specified service. This method should only be called
-     * to add a usage count for a previously unreferenced service. If the
-     * service already has a usage count, then the existing usage count
-     * counter simply needs to be incremented.
-     * @param bundle The bundle acquiring the service.
-     * @param ref The service reference of the acquired service.
-     * @param svcObj The service object of the acquired service.
-    **/
-    private UsageCount addUsageCount(Bundle bundle, ServiceReference<?> ref, boolean isPrototype)
-    {
-        UsageCount[] usages = m_inUseMap.get(bundle);
-
-        UsageCount usage = new UsageCount(ref, isPrototype);
+            // We haven't found an existing usage count object so we need to create on. For this we need to
+            // know whether this is a prototype or not.
+            if (isPrototype == null)
+            {
+                // If this parameter isn't passed in we can't create a usage count.
+                return null;
+            }
 
-        if (usages == null)
-        {
-            usages = new UsageCount[] { usage };
-        }
-        else
-        {
-            UsageCount[] newUsages = new UsageCount[usages.length + 1];
-            System.arraycopy(usages, 0, newUsages, 0, usages.length);
-            newUsages[usages.length] = usage;
-            usages = newUsages;
+            // Add a new Usage Count.
+            usage = new UsageCount(ref, isPrototype);
+            if (usages == null)
+            {
+                UsageCount[] newUsages = new UsageCount[] { usage };
+                success = m_inUseMap.putIfAbsent(bundle, newUsages) == null;
+            }
+            else
+            {
+                UsageCount[] newUsages = new UsageCount[usages.length + 1];
+                System.arraycopy(usages, 0, newUsages, 0, usages.length);
+                newUsages[usages.length] = usage;
+                success = m_inUseMap.replace(bundle, usages, newUsages);
+            }
         }
-
-        m_inUseMap.put(bundle, usages);
-
         return usage;
     }
 
@@ -589,41 +601,51 @@ public class ServiceRegistry
      * @param bundle The bundle whose usage count should be removed.
      * @param ref The service reference whose usage count should be removed.
     **/
-    private void flushUsageCount(Bundle bundle, ServiceReference<?> ref, UsageCount uc)
+    void flushUsageCount(Bundle bundle, ServiceReference<?> ref, UsageCount uc)
     {
-        UsageCount[] usages = m_inUseMap.get(bundle);
-        for (int i = 0; (usages != null) && (i < usages.length); i++)
-        {
-            if ((uc == null && usages[i].m_ref.equals(ref)) || (uc == usages[i]))
+        // This method uses an optimistic concurrency mechanism with conditional modifications
+        // on the m_inUseMap. If this fails (because another thread made changes) this thread
+        // retries the operation. This is the purpose of the while loop.
+        boolean success = false;
+        while (!success)
+        {
+            UsageCount[] usages = m_inUseMap.get(bundle);
+            final UsageCount[] orgUsages = usages;
+            for (int i = 0; (usages != null) && (i < usages.length); i++)
             {
-                // If this is the only usage, then point to empty list.
-                if ((usages.length - 1) == 0)
-                {
-                    usages = null;
-                }
-                // Otherwise, we need to do some array copying.
-                else
+                if ((uc == null && usages[i].m_ref.equals(ref)) || (uc == usages[i]))
                 {
-                    UsageCount[] newUsages = new UsageCount[usages.length - 1];
-                    System.arraycopy(usages, 0, newUsages, 0, i);
-                    if (i < newUsages.length)
+                    // If this is the only usage, then point to empty list.
+                    if ((usages.length - 1) == 0)
+                    {
+                        usages = null;
+                    }
+                    // Otherwise, we need to do some array copying.
+                    else
                     {
-                        System.arraycopy(
-                            usages, i + 1, newUsages, i, newUsages.length - i);
+                        UsageCount[] newUsages = new UsageCount[usages.length - 1];
+                        System.arraycopy(usages, 0, newUsages, 0, i);
+                        if (i < newUsages.length)
+                        {
+                            System.arraycopy(
+                                usages, i + 1, newUsages, i, newUsages.length - i);
+                        }
+                        usages = newUsages;
+                        i--;
                     }
-                    usages = newUsages;
-                    i--;
                 }
             }
-        }
 
-        if (usages != null)
-        {
-            m_inUseMap.put(bundle, usages);
-        }
-        else
-        {
-            m_inUseMap.remove(bundle);
+            if (usages == orgUsages)
+                return; // no change in map
+
+            if (orgUsages != null)
+            {
+                if (usages != null)
+                    success = m_inUseMap.replace(bundle, orgUsages, usages);
+                else
+                    success = m_inUseMap.remove(bundle, orgUsages);
+            }
         }
     }
 
@@ -632,25 +654,36 @@ public class ServiceRegistry
         return this.hookRegistry;
     }
 
-    private static class UsageCount
+    static class UsageCount
     {
-        public final ServiceReference<?> m_ref;
-        public final boolean m_prototype;
-
-        public volatile int m_count;
-        public volatile int m_serviceObjectsCount;
+        final ServiceReference<?> m_ref;
+        final boolean m_prototype;
 
-        public volatile Object m_svcObj;
+        final AtomicInteger m_count = new AtomicInteger();
+        final AtomicInteger m_serviceObjectsCount = new AtomicInteger();
+        final AtomicReference<ServiceHolder> m_svcHolderRef = new AtomicReference<ServiceHolder>();
 
         UsageCount(final ServiceReference<?> ref, final boolean isPrototype)
         {
             m_ref = ref;
             m_prototype = isPrototype;
         }
+
+        Object getService()
+        {
+            ServiceHolder sh = m_svcHolderRef.get();
+            return sh == null ? null : sh.m_service;
+        }
+    }
+
+    static class ServiceHolder
+    {
+        final CountDownLatch m_latch = new CountDownLatch(1);
+        volatile Object m_service;
     }
 
     public interface ServiceRegistryCallbacks
     {
-        void serviceChanged(ServiceEvent event, Dictionary oldProps);
+        void serviceChanged(ServiceEvent event, Dictionary<?,?> oldProps);
     }
 }

Modified: felix/trunk/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java
URL: http://svn.apache.org/viewvc/felix/trunk/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java?rev=1679327&r1=1679326&r2=1679327&view=diff
==============================================================================
--- felix/trunk/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java (original)
+++ felix/trunk/framework/src/test/java/org/apache/felix/framework/ServiceRegistryTest.java Thu May 14 09:09:08 2015
@@ -18,15 +18,30 @@
  */
 package org.apache.felix.framework;
 
+import java.lang.reflect.Field;
+import java.util.ArrayList;
+import java.util.Arrays;
 import java.util.Collection;
 import java.util.Hashtable;
+import java.util.List;
+import java.util.concurrent.ConcurrentMap;
+import java.util.concurrent.atomic.AtomicBoolean;
 
 import junit.framework.TestCase;
 
+import org.apache.felix.framework.ServiceRegistrationImpl.ServiceReferenceImpl;
+import org.apache.felix.framework.ServiceRegistry.ServiceHolder;
+import org.apache.felix.framework.ServiceRegistry.UsageCount;
 import org.easymock.MockControl;
+import org.mockito.AdditionalAnswers;
+import org.mockito.InOrder;
+import org.mockito.Mockito;
+import org.mockito.invocation.InvocationOnMock;
+import org.mockito.stubbing.Answer;
 import org.osgi.framework.Bundle;
 import org.osgi.framework.BundleContext;
 import org.osgi.framework.ServiceEvent;
+import org.osgi.framework.ServiceException;
 import org.osgi.framework.ServiceFactory;
 import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
@@ -331,4 +346,703 @@ public class ServiceRegistryTest extends
         assertEquals("Unregistration should have no effect", 0, sr.getHookRegistry().getHooks(FindHook.class).size());
         assertEquals("Unregistration should have no effect", 0, sr.getHookRegistry().getHooks(ListenerHook.class).size());
     }
+
+    @SuppressWarnings("unchecked")
+    public void testGetService()
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        String svc = "foo";
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.isValid()).thenReturn(true);
+        Mockito.when(reg.getService(b)).thenReturn(svc);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        assertSame(svc, sr.getService(b, ref, false));
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testGetServiceHolderAwait() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        final String svc = "test";
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.isValid()).thenReturn(true);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        UsageCount uc = sr.obtainUsageCount(b, ref, null, false);
+
+        // Set an empty Service Holder so we can test that it waits.
+        final ServiceHolder sh = new ServiceHolder();
+        uc.m_svcHolderRef.set(sh);
+
+        final StringBuilder sb = new StringBuilder();
+        final AtomicBoolean threadException = new AtomicBoolean(false);
+        Thread t = new Thread() {
+            @Override
+            public void run()
+            {
+                try { Thread.sleep(250); } catch (InterruptedException e) {}
+                sh.m_service = svc;
+                if (sb.length() > 0)
+                {
+                    // Should not have put anything in SB until countDown() was called...
+                    threadException.set(true);
+                }
+                sh.m_latch.countDown();
+            }
+        };
+        assertFalse(t.isInterrupted());
+        t.start();
+
+        Object actualSvc = sr.getService(b, ref, false);
+        sb.append(actualSvc);
+
+        t.join();
+        assertFalse("This thread did not wait until the latch was count down",
+                threadException.get());
+
+        assertSame(svc, actualSvc);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testGetServicePrototype() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        String svc = "xyz";
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.isValid()).thenReturn(true);
+        Mockito.when(reg.getService(b)).thenReturn(svc);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        assertSame(svc, sr.getService(b, ref, true));
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+        UsageCount[] uca = inUseMap.get(b);
+        assertEquals(1, uca.length);
+        assertEquals(1, uca[0].m_serviceObjectsCount.get());
+
+        sr.getService(b, ref, true);
+        assertEquals(2, uca[0].m_serviceObjectsCount.get());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testGetServiceThreadMarking() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        sr.getService(b, ref, false);
+
+        InOrder inOrder = Mockito.inOrder(reg);
+        inOrder.verify(reg, Mockito.times(1)).currentThreadMarked();
+        inOrder.verify(reg, Mockito.times(1)).markCurrentThread();
+        inOrder.verify(reg, Mockito.times(1)).unmarkCurrentThread();
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testGetServiceThreadMarking2() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        String svc = "bar";
+
+        Bundle b = Mockito.mock(Bundle.class);
+
+        ServiceRegistrationImpl reg = (ServiceRegistrationImpl) sr.registerService(
+                b, new String [] {String.class.getName()}, svc, null);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        reg.markCurrentThread();
+        try
+        {
+            sr.getService(b, ref, false);
+            fail("Should have thrown an exception to signal reentrant behaviour");
+        }
+        catch (ServiceException se)
+        {
+            assertEquals(ServiceException.FACTORY_ERROR, se.getType());
+        }
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUngetService() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        UsageCount uc = new UsageCount(ref, false);
+        uc.m_svcHolderRef.set(new ServiceHolder());
+
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertTrue(sr.ungetService(b, ref, null));
+        assertNull(uc.m_svcHolderRef.get());
+        assertNull(inUseMap.get(b));
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUngetService2() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        UsageCount uc = new UsageCount(ref, false);
+        uc.m_svcHolderRef.set(new ServiceHolder());
+        uc.m_count.incrementAndGet();
+
+        Mockito.verify(reg, Mockito.never()).
+            ungetService(Mockito.isA(Bundle.class), Mockito.any());
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertTrue(sr.ungetService(b, ref, null));
+        assertNull(uc.m_svcHolderRef.get());
+        assertNull(inUseMap.get(b));
+
+        Mockito.verify(reg, Mockito.times(1)).
+        ungetService(Mockito.isA(Bundle.class), Mockito.any());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUngetService3() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.isValid()).thenReturn(true);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        UsageCount uc = new UsageCount(ref, false);
+        uc.m_svcHolderRef.set(new ServiceHolder());
+        uc.m_count.set(2);
+
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertTrue(sr.ungetService(b, ref, null));
+        assertNotNull(uc.m_svcHolderRef.get());
+        assertNotNull(inUseMap.get(b));
+
+        Mockito.verify(reg, Mockito.never()).
+            ungetService(Mockito.isA(Bundle.class), Mockito.any());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUngetService4() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.isValid()).thenReturn(false);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        UsageCount uc = new UsageCount(ref, false);
+        uc.m_svcHolderRef.set(new ServiceHolder());
+        uc.m_count.set(2);
+
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertTrue(sr.ungetService(b, ref, null));
+        assertNull(uc.m_svcHolderRef.get());
+        assertNull(inUseMap.get(b));
+
+        Mockito.verify(reg, Mockito.never()).
+            ungetService(Mockito.isA(Bundle.class), Mockito.any());
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testUngetService5() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.doThrow(new RuntimeException("Test!")).when(reg).
+            ungetService(Mockito.isA(Bundle.class), Mockito.any());
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+                (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        String svc = "myService";
+        UsageCount uc = new UsageCount(ref, false);
+        ServiceHolder sh = new ServiceHolder();
+        sh.m_service = svc;
+        sh.m_latch.countDown();
+        uc.m_svcHolderRef.set(sh);
+        uc.m_count.set(1);
+
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        try
+        {
+            assertTrue(sr.ungetService(b, ref, null));
+            fail("Should have propagated the runtime exception");
+        }
+        catch (RuntimeException re)
+        {
+            assertEquals("Test!", re.getMessage());
+        }
+        assertNull(uc.m_svcHolderRef.get());
+        assertNull(inUseMap.get(b));
+
+        Mockito.verify(reg, Mockito.times(1)).ungetService(b, svc);
+    }
+
+    public void testUngetServiceThreadMarking()
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        assertFalse("There is no usage count, so this method should return false",
+                sr.ungetService(b, ref, null));
+
+        InOrder inOrder = Mockito.inOrder(reg);
+        inOrder.verify(reg, Mockito.times(1)).currentThreadMarked();
+        inOrder.verify(reg, Mockito.times(1)).markCurrentThread();
+        inOrder.verify(reg, Mockito.times(1)).unmarkCurrentThread();
+    }
+
+    public void testUngetServiceThreadMarking2()
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceRegistrationImpl reg = Mockito.mock(ServiceRegistrationImpl.class);
+        Mockito.when(reg.currentThreadMarked()).thenReturn(true);
+
+        ServiceReferenceImpl ref = Mockito.mock(ServiceReferenceImpl.class);
+        Mockito.when(ref.getRegistration()).thenReturn(reg);
+
+        try
+        {
+            sr.ungetService(b, ref, null);
+            fail("The thread should be observed as marked and hence throw an exception");
+        }
+        catch (IllegalStateException ise)
+        {
+            // good
+        }
+    }
+
+    public void testObtainUsageCount() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        assertEquals("Precondition", 0, inUseMap.size());
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = sr.obtainUsageCount(b, ref, null, false);
+        assertEquals(1, inUseMap.size());
+        assertEquals(1, inUseMap.get(b).length);
+        assertSame(uc, inUseMap.get(b)[0]);
+        assertSame(ref, uc.m_ref);
+        assertFalse(uc.m_prototype);
+
+        UsageCount uc2 = sr.obtainUsageCount(b, ref, null, false);
+        assertSame(uc, uc2);
+
+        ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        UsageCount uc3 = sr.obtainUsageCount(b, ref2, null, false);
+        assertNotSame(uc3, uc2);
+        assertSame(ref2, uc3.m_ref);
+    }
+
+    public void testObtainUsageCountPrototype() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = sr.obtainUsageCount(b, ref, null, true);
+        assertEquals(1, inUseMap.size());
+        assertEquals(1, inUseMap.values().iterator().next().length);
+
+        ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        UsageCount uc2 = sr.obtainUsageCount(b, ref2, null, true);
+        assertEquals(1, inUseMap.size());
+        assertEquals(2, inUseMap.values().iterator().next().length);
+        List<UsageCount> ucl = Arrays.asList(inUseMap.get(b));
+        assertTrue(ucl.contains(uc));
+        assertTrue(ucl.contains(uc2));
+    }
+
+    public void testObtainUsageCountPrototypeUnknownLookup() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+
+        UsageCount uc = new UsageCount(ref, true);
+        ServiceHolder sh = new ServiceHolder();
+        String svc = "foobar";
+        sh.m_service = svc;
+        uc.m_svcHolderRef.set(sh);
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertNull(sr.obtainUsageCount(b, Mockito.mock(ServiceReference.class), null, null));
+
+        UsageCount uc2 = sr.obtainUsageCount(b, ref, svc, null);
+        assertSame(uc, uc2);
+    }
+
+    public void testObtainUsageCountPrototypeUnknownLookup2() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+
+        UsageCount uc = new UsageCount(ref, false);
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        assertNull(sr.obtainUsageCount(b, Mockito.mock(ServiceReference.class), null, null));
+
+        UsageCount uc2 = sr.obtainUsageCount(b, ref, null, null);
+        assertSame(uc, uc2);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testObtainUsageCountRetry1() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        final Bundle b = Mockito.mock(Bundle.class);
+
+        final ConcurrentMap<Bundle, UsageCount[]> orgInUseMap =
+            (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+            Mockito.mock(ConcurrentMap.class, AdditionalAnswers.delegatesTo(orgInUseMap));
+        Mockito.doAnswer(new Answer<UsageCount[]>()
+            {
+                @Override
+                public UsageCount[] answer(InvocationOnMock invocation) throws Throwable
+                {
+                    // This mimicks another thread putting another UsageCount in concurrently
+                    // The putIfAbsent() will fail and it has to retry
+                    UsageCount uc = new UsageCount(Mockito.mock(ServiceReference.class), false);
+                    UsageCount[] uca = new UsageCount[] {uc};
+                    orgInUseMap.put(b, uca);
+                    return uca;
+                }
+            }).when(inUseMap).putIfAbsent(Mockito.any(Bundle.class), Mockito.any(UsageCount[].class));
+        setPrivateField(sr, "m_inUseMap", inUseMap);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+
+        assertEquals(0, orgInUseMap.size());
+        UsageCount uc = sr.obtainUsageCount(b, ref, null, false);
+        assertEquals(1, orgInUseMap.size());
+        assertEquals(2, orgInUseMap.get(b).length);
+        assertSame(ref, uc.m_ref);
+        assertFalse(uc.m_prototype);
+        List<UsageCount> l = new ArrayList<UsageCount>(Arrays.asList(orgInUseMap.get(b)));
+        l.remove(uc);
+        assertEquals("There should be one UsageCount left", 1, l.size());
+        assertNotSame(ref, l.get(0).m_ref);
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testObtainUsageCountRetry2() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        final Bundle b = Mockito.mock(Bundle.class);
+
+        final ConcurrentMap<Bundle, UsageCount[]> orgInUseMap =
+            (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+        orgInUseMap.put(b, new UsageCount[] {new UsageCount(Mockito.mock(ServiceReference.class), false)});
+
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+            Mockito.mock(ConcurrentMap.class, AdditionalAnswers.delegatesTo(orgInUseMap));
+        Mockito.doAnswer(new Answer<Boolean>()
+            {
+                @Override
+                public Boolean answer(InvocationOnMock invocation) throws Throwable
+                {
+                    orgInUseMap.remove(b);
+                    return false;
+                }
+            }).when(inUseMap).replace(Mockito.any(Bundle.class),
+                    Mockito.any(UsageCount[].class), Mockito.any(UsageCount[].class));
+        setPrivateField(sr, "m_inUseMap", inUseMap);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+
+        assertEquals("Precondition", 1, inUseMap.size());
+        assertEquals("Precondition", 1, inUseMap.values().iterator().next().length);
+        assertNotSame("Precondition", ref, inUseMap.get(b)[0].m_ref);
+        sr.obtainUsageCount(b, ref, null, false);
+        assertEquals(1, inUseMap.size());
+        assertEquals(1, inUseMap.values().iterator().next().length);
+        assertSame("The old usage count should have been removed by the mock and this one should have been added",
+                ref, inUseMap.get(b)[0].m_ref);
+    }
+
+    public void testFlushUsageCount() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = new UsageCount(ref, false);
+        ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        UsageCount uc2 = new UsageCount(ref2, true);
+
+        inUseMap.put(b, new UsageCount[] {uc, uc2});
+
+        assertEquals("Precondition", 1, inUseMap.size());
+        assertEquals("Precondition", 2, inUseMap.values().iterator().next().length);
+
+        sr.flushUsageCount(b, ref, uc);
+        assertEquals(1, inUseMap.size());
+        assertEquals(1, inUseMap.values().iterator().next().length);
+        assertSame(uc2, inUseMap.values().iterator().next()[0]);
+
+        sr.flushUsageCount(b, ref2, uc2);
+        assertEquals(0, inUseMap.size());
+    }
+
+    public void testFlushUsageCountNullRef() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+        Bundle b2 = Mockito.mock(Bundle.class);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = new UsageCount(ref, false);
+        ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        UsageCount uc2 = new UsageCount(ref2, true);
+        ServiceReference<?> ref3 = Mockito.mock(ServiceReference.class);
+        UsageCount uc3 = new UsageCount(ref3, true);
+
+        inUseMap.put(b, new UsageCount[] {uc2, uc});
+        inUseMap.put(b2, new UsageCount[] {uc3});
+
+        assertEquals("Precondition", 2, inUseMap.size());
+
+        sr.flushUsageCount(b, null, uc);
+        assertEquals(2, inUseMap.size());
+
+        sr.flushUsageCount(b, null, uc2);
+        assertEquals(1, inUseMap.size());
+    }
+
+    public void testFlushUsageCountAlienObject() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = new UsageCount(ref, false);
+
+        inUseMap.put(b, new UsageCount[] {uc});
+        assertEquals("Precondition", 1, inUseMap.size());
+        assertEquals("Precondition", 1, inUseMap.values().iterator().next().length);
+
+        UsageCount uc2 = new UsageCount(Mockito.mock(ServiceReference.class), false);
+        sr.flushUsageCount(b, ref, uc2);
+        assertEquals("Should be no changes", 1, inUseMap.size());
+        assertEquals("Should be no changes", 1, inUseMap.values().iterator().next().length);
+    }
+
+    public void testFlushUsageCountNull() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        @SuppressWarnings("unchecked")
+        ConcurrentMap<Bundle, UsageCount[]> inUseMap = (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        Bundle b = Mockito.mock(Bundle.class);
+        Bundle b2 = Mockito.mock(Bundle.class);
+
+        ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        UsageCount uc = new UsageCount(ref, false);
+        ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        UsageCount uc2 = new UsageCount(ref2, true);
+        ServiceReference<?> ref3 = Mockito.mock(ServiceReference.class);
+        UsageCount uc3 = new UsageCount(ref3, true);
+
+        inUseMap.put(b, new UsageCount[] {uc2, uc});
+        inUseMap.put(b2, new UsageCount[] {uc3});
+
+        assertEquals("Precondition", 2, inUseMap.size());
+
+        sr.flushUsageCount(b, ref, null);
+        assertEquals(2, inUseMap.size());
+
+        sr.flushUsageCount(b, ref2, null);
+        assertEquals(1, inUseMap.size());
+
+    }
+
+    @SuppressWarnings("unchecked")
+    public void testFlushUsageCountRetry() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        final Bundle b = Mockito.mock(Bundle.class);
+        final ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        final UsageCount uc = new UsageCount(ref, false);
+        final ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        final UsageCount uc2 = new UsageCount(ref2, false);
+
+        final ConcurrentMap<Bundle, UsageCount[]> orgInUseMap =
+            (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+            Mockito.mock(ConcurrentMap.class, AdditionalAnswers.delegatesTo(orgInUseMap));
+        Mockito.doAnswer(new Answer<Boolean>()
+            {
+                @Override
+                public Boolean answer(InvocationOnMock invocation) throws Throwable
+                {
+                    inUseMap.put(b, new UsageCount[] {uc});
+                    return false;
+                }
+            }).when(inUseMap).replace(Mockito.isA(Bundle.class),
+                    Mockito.isA(UsageCount[].class), Mockito.isA(UsageCount[].class));
+        setPrivateField(sr, "m_inUseMap", inUseMap);
+
+        inUseMap.put(b, new UsageCount[] {uc, uc2});
+
+        sr.flushUsageCount(b, null, uc);
+
+        assertNull("A 'concurrent' process has removed uc2 as well, "
+                + "so the entry for 'b' should have been removed",
+                inUseMap.get(b));
+    }
+
+    public void testFlushUsageCountRetry2() throws Exception
+    {
+        ServiceRegistry sr = new ServiceRegistry(null, null);
+
+        final Bundle b = Mockito.mock(Bundle.class);
+        final ServiceReference<?> ref = Mockito.mock(ServiceReference.class);
+        final UsageCount uc = new UsageCount(ref, false);
+        final ServiceReference<?> ref2 = Mockito.mock(ServiceReference.class);
+        final UsageCount uc2 = new UsageCount(ref2, false);
+
+        final ConcurrentMap<Bundle, UsageCount[]> orgInUseMap =
+            (ConcurrentMap<Bundle, UsageCount[]>) getPrivateField(sr, "m_inUseMap");
+
+        final ConcurrentMap<Bundle, UsageCount[]> inUseMap =
+            Mockito.mock(ConcurrentMap.class, AdditionalAnswers.delegatesTo(orgInUseMap));
+        Mockito.doAnswer(new Answer<Boolean>()
+            {
+                @Override
+                public Boolean answer(InvocationOnMock invocation) throws Throwable
+                {
+                    inUseMap.put(b, new UsageCount[] {uc, uc2});
+                    return false;
+                }
+            }).when(inUseMap).remove(Mockito.isA(Bundle.class), Mockito.isA(UsageCount[].class));
+        setPrivateField(sr, "m_inUseMap", inUseMap);
+
+        inUseMap.put(b, new UsageCount[] {uc});
+
+        sr.flushUsageCount(b, null, uc);
+
+        assertEquals(1, inUseMap.get(b).length);
+        assertSame(uc2, inUseMap.get(b)[0]);
+    }
+
+    private Object getPrivateField(Object obj, String fieldName) throws NoSuchFieldException,
+            IllegalAccessException
+    {
+        Field f = ServiceRegistry.class.getDeclaredField(fieldName);
+        f.setAccessible(true);
+        return f.get(obj);
+    }
+
+    private void setPrivateField(ServiceRegistry obj, String fieldName, Object val) throws SecurityException,
+            NoSuchFieldException, IllegalArgumentException, IllegalAccessException
+    {
+        Field f = ServiceRegistry.class.getDeclaredField(fieldName);
+        f.setAccessible(true);
+        f.set(obj, val);
+    }
 }

Modified: felix/trunk/main/pom.xml
URL: http://svn.apache.org/viewvc/felix/trunk/main/pom.xml?rev=1679327&r1=1679326&r2=1679327&view=diff
==============================================================================
--- felix/trunk/main/pom.xml (original)
+++ felix/trunk/main/pom.xml Thu May 14 09:09:08 2015
@@ -35,7 +35,7 @@
     </scm>
   <dependencies>
     <dependency>
-      <groupId>${pom.groupId}</groupId>
+      <groupId>${project.groupId}</groupId>
       <artifactId>org.apache.felix.framework</artifactId>
       <version>${framework.version}</version>
     </dependency>
@@ -43,7 +43,7 @@
   <properties>
     <log.level>4</log.level>
     <dollar>$</dollar>
-    <framework.version>5.0.0</framework.version>
+    <framework.version>${project.version}</framework.version>
     <gogo.runtime.version>0.16.2</gogo.runtime.version>
     <gogo.shell.version>0.10.0</gogo.shell.version>
     <gogo.command.version>0.14.0</gogo.command.version>
@@ -102,7 +102,7 @@
             <configuration>
                 <artifactItems>
                   <artifactItem>
-                     <groupId>${pom.groupId}</groupId>
+                     <groupId>${project.groupId}</groupId>
                      <artifactId>org.apache.felix.gogo.runtime</artifactId>
                      <version>${gogo.runtime.version}</version>
                      <type>jar</type>
@@ -110,7 +110,7 @@
                      <outputDirectory>${project.basedir}/bundle</outputDirectory>
                    </artifactItem>
                    <artifactItem>
-                     <groupId>${pom.groupId}</groupId>
+                     <groupId>${project.groupId}</groupId>
                      <artifactId>org.apache.felix.gogo.shell</artifactId>
                      <version>${gogo.shell.version}</version>
                      <type>jar</type>
@@ -118,7 +118,7 @@
                      <outputDirectory>${project.basedir}/bundle</outputDirectory>
                    </artifactItem>
                    <artifactItem>
-                     <groupId>${pom.groupId}</groupId>
+                     <groupId>${project.groupId}</groupId>
                      <artifactId>org.apache.felix.gogo.command</artifactId>
                      <version>${gogo.command.version}</version>
                      <type>jar</type>
@@ -126,7 +126,7 @@
                      <outputDirectory>${project.basedir}/bundle</outputDirectory>
                    </artifactItem>
                    <artifactItem>
-                     <groupId>${pom.groupId}</groupId>
+                     <groupId>${project.groupId}</groupId>
                      <artifactId>org.apache.felix.bundlerepository</artifactId>
                      <version>${obr.version}</version>
                      <type>jar</type>
@@ -149,7 +149,7 @@
                 <mkdir dir="${basedir}/bin" />
                 <delete dir="${basedir}/conf" />
                 <mkdir dir="${basedir}/conf" />
-                <copy file="${basedir}/target/org.apache.felix.main-${pom.version}.jar" tofile="${basedir}/bin/felix.jar" />
+                <copy file="${basedir}/target/org.apache.felix.main-${project.version}.jar" tofile="${basedir}/bin/felix.jar" />
                 <copy file="${basedir}/target/classes/config.properties" todir="${basedir}/conf" />
               </tasks>
             </configuration>