You are viewing a plain text version of this content. The canonical link for it is here.
Posted to oak-commits@jackrabbit.apache.org by ju...@apache.org on 2014/04/28 17:43:18 UTC

svn commit: r1590684 - in /jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak: osgi/OsgiWhiteboard.java spi/whiteboard/AbstractServiceTracker.java spi/whiteboard/Tracker.java

Author: jukka
Date: Mon Apr 28 15:43:18 2014
New Revision: 1590684

URL: http://svn.apache.org/r1590684
Log:
OAK-1771: Avoid lock contention in Tracker.getServices()

Modified:
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/AbstractServiceTracker.java
    jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java?rev=1590684&r1=1590683&r2=1590684&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/osgi/OsgiWhiteboard.java Mon Apr 28 15:43:18 2014
@@ -18,13 +18,19 @@ package org.apache.jackrabbit.oak.osgi;
 
 import static com.google.common.base.Preconditions.checkArgument;
 import static com.google.common.base.Preconditions.checkNotNull;
-import static java.util.Arrays.asList;
+import static com.google.common.collect.Lists.newArrayList;
+import static com.google.common.collect.Maps.newHashMap;
+import static com.google.common.collect.Maps.newTreeMap;
+import static java.util.Collections.emptyList;
+import static java.util.Collections.singletonList;
 
 import java.util.Collections;
 import java.util.Dictionary;
 import java.util.Hashtable;
 import java.util.List;
 import java.util.Map;
+import java.util.SortedMap;
+import java.util.concurrent.atomic.AtomicReference;
 
 import javax.annotation.Nonnull;
 
@@ -32,8 +38,10 @@ import org.apache.jackrabbit.oak.spi.whi
 import org.apache.jackrabbit.oak.spi.whiteboard.Tracker;
 import org.apache.jackrabbit.oak.spi.whiteboard.Whiteboard;
 import org.osgi.framework.BundleContext;
+import org.osgi.framework.ServiceReference;
 import org.osgi.framework.ServiceRegistration;
 import org.osgi.util.tracker.ServiceTracker;
+import org.osgi.util.tracker.ServiceTrackerCustomizer;
 
 /**
  * OSGi-based whiteboard implementation.
@@ -69,17 +77,63 @@ public class OsgiWhiteboard implements W
         };
     }
 
+    /**
+     * Returns a tracker for services of the given type. The returned tracker
+     * is optimized for frequent {@link Tracker#getServices()} calls through
+     * the use of a pre-compiled list of services that's atomically updated
+     * whenever services are added, modified or removed.
+     */
     @Override
-    public <T> Tracker<T> track(Class<T> type) {
+    public <T> Tracker<T> track(final Class<T> type) {
         checkNotNull(type);
+        final AtomicReference<List<T>> list =
+                new AtomicReference<List<T>>(Collections.<T>emptyList());
+        final ServiceTrackerCustomizer customizer =
+                new ServiceTrackerCustomizer() {
+                    private final Map<ServiceReference, T> services =
+                            newHashMap();
+                    @Override @SuppressWarnings("unchecked")
+                    public synchronized Object addingService(
+                            ServiceReference reference) {
+                        Object service = context.getService(reference);
+                        if (type.isInstance(service)) {
+                            services.put(reference, (T) service);
+                            list.set(getServiceList(services));
+                            return service;
+                        } else {
+                            context.ungetService(reference);
+                            return null;
+                        }
+                    }
+                    @Override @SuppressWarnings("unchecked")
+                    public synchronized void modifiedService(
+                            ServiceReference reference, Object service) {
+                        // TODO: Figure out if the old reference instance
+                        // would automatically reflect the updated properties.
+                        // For now we play it safe by replacing the old key
+                        // with the new reference instance passed as argument.
+                        services.remove(reference);
+                        services.put(reference, (T) service);
+                        list.set(getServiceList(services));
+                    }
+                    @Override
+                    public synchronized void removedService(
+                            ServiceReference reference, Object service) {
+                        services.remove(reference);
+                        list.set(getServiceList(services));
+                        // TODO: Note that the service might still be in use
+                        // by some client that called getServices() before
+                        // this method was invoked.
+                        context.ungetService(reference);
+                    }
+                };
         final ServiceTracker tracker =
-                new ServiceTracker(context, type.getName(), null);
+                new ServiceTracker(context, type.getName(), customizer);
         tracker.open();
         return new Tracker<T>() {
-            @Override @SuppressWarnings("unchecked")
+            @Override
             public List<T> getServices() {
-                Object[] services = tracker.getServices();
-                return (List<T>) (services != null ? asList(services) : Collections.emptyList());
+                return list.get();
             }
             @Override
             public void stop() {
@@ -88,4 +142,26 @@ public class OsgiWhiteboard implements W
         };
     }
 
+    /**
+     * Utility method that sorts the service objects in the given map
+     * according to their service rankings and returns the resulting list.
+     *
+     * @param services currently available services
+     * @return ordered list of the services
+     */
+    private static <T> List<T> getServiceList(
+            Map<ServiceReference, T> services) {
+        switch (services.size()) {
+        case 0:
+            return emptyList();
+        case 1:
+            return singletonList(
+                    services.values().iterator().next());
+        default:
+            SortedMap<ServiceReference, T> sorted = newTreeMap();
+            sorted.putAll(services);
+            return newArrayList(sorted.values());
+        }
+    }
+
 }

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/AbstractServiceTracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/AbstractServiceTracker.java?rev=1590684&r1=1590683&r2=1590684&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/AbstractServiceTracker.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/AbstractServiceTracker.java Mon Apr 28 15:43:18 2014
@@ -20,6 +20,7 @@ package org.apache.jackrabbit.oak.spi.wh
 
 import static com.google.common.base.Preconditions.checkNotNull;
 import static com.google.common.base.Preconditions.checkState;
+import static java.util.Collections.emptyList;
 
 import java.util.List;
 
@@ -32,32 +33,58 @@ import javax.annotation.Nonnull;
  */
 public abstract class AbstractServiceTracker<T> {
 
+    /**
+     * Sentinel object used as the {@link #tracker} value of an already
+     * stopped instance.
+     */
+    private final Tracker<T> stopped = new Tracker<T>() {
+        @Override
+        public List<T> getServices() {
+            return emptyList();
+        }
+        @Override
+        public void stop() {
+            // do nothing
+        }
+    };
+
+    /**
+     * The type of services tracked by this instance.
+     */
     private final Class<T> type;
 
-    private Tracker<T> tracker = null;
+    /**
+     * The underlying {@link Tracker}, or the {@link #stopped} sentinel
+     * sentinel object when this instance is not active. This variable
+     * is {@code volatile} so that the {@link #getServices()} method will
+     * always see the latest state without having to be synchronized.
+     */
+    private volatile Tracker<T> tracker = stopped;
 
-    public AbstractServiceTracker(@Nonnull Class<T> type) {
+    protected AbstractServiceTracker(@Nonnull Class<T> type) {
         this.type = checkNotNull(type);
     }
 
     public synchronized void start(Whiteboard whiteboard) {
-        checkState(tracker == null);
+        checkState(tracker == stopped);
         tracker = whiteboard.track(type);
     }
 
     public synchronized void stop() {
-        checkState(tracker != null);
-        tracker.stop();
-        tracker = null;
+        checkState(tracker != stopped);
+        Tracker<T> t = tracker;
+        tracker = stopped;
+        t.stop();
     }
 
     /**
-     * Returns all services of type {@code T} currently available.
+     * Returns all services of type {@code T} that are currently available.
+     * This method is intentionally not synchronized to prevent lock
+     * contention when accessed frequently in highly concurrent code.
      *
-     * @return services currently available.
+     * @return currently available services
      */
-    protected synchronized List<T> getServices() {
-        checkState(tracker != null);
+    protected List<T> getServices() {
         return tracker.getServices();
     }
 

Modified: jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java
URL: http://svn.apache.org/viewvc/jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java?rev=1590684&r1=1590683&r2=1590684&view=diff
==============================================================================
--- jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java (original)
+++ jackrabbit/oak/trunk/oak-core/src/main/java/org/apache/jackrabbit/oak/spi/whiteboard/Tracker.java Mon Apr 28 15:43:18 2014
@@ -18,10 +18,6 @@ package org.apache.jackrabbit.oak.spi.wh
 
 import java.util.List;
 
-import javax.annotation.CheckForNull;
-
-import com.google.common.base.Predicate;
-
 /**
  * Tracker for whiteboard services.
  */