BUG-1120: introduce generations to ListenerMap 92/7592/1
authorRobert Varga <rovarga@cisco.com>
Mon, 2 Jun 2014 11:58:22 +0000 (13:58 +0200)
committerRobert Varga <rovarga@cisco.com>
Mon, 2 Jun 2014 14:17:09 +0000 (16:17 +0200)
This removes the synchronized block in the fast path, biasing the
implementation heavily towards fast readers.

The readers need only to take a volatile reference (AtomicReference),
at which point they have an isolated read-only view of the map.

Writers on the other hand, though, are fully synchronized and are
required to perform a full copy operation, then do the modifications and
finally reinstate the read-only view through setting the atomic
reference.

Change-Id: I5d118177c1508199b66b9e26499c9fc628d3f65e
Signed-off-by: Robert Varga <rovarga@cisco.com>
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/GenerationalListenerMap.java
opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java [new file with mode: 0644]

index 05ccd9f..85265e8 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Copyright (c) 2013 Cisco Systems, Inc. and others.  All rights reserved.
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
  *
  * This program and the accompanying materials are made available under the
  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
@@ -7,62 +7,54 @@
  */
 package org.opendaylight.controller.sal.binding.impl;
 
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.HashSet;
-import java.util.Set;
+import java.util.concurrent.atomic.AtomicReference;
+
+import javax.annotation.concurrent.GuardedBy;
 
 import org.opendaylight.yangtools.yang.binding.Notification;
 
-import com.google.common.base.Predicate;
 import com.google.common.collect.HashMultimap;
-import com.google.common.collect.ImmutableList;
-import com.google.common.collect.Iterables;
 import com.google.common.collect.Multimap;
 
+/**
+ * A multi-generation, isolated notification type to listener map.
+ */
 final class GenerationalListenerMap {
-    private final Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> listeners =
-            HashMultimap.create();
+    private final AtomicReference<ListenerMapGeneration> current = new AtomicReference<>(new ListenerMapGeneration());
 
-    private static Iterable<Class<?>> getNotificationTypes(final Notification notification) {
-        final Class<?>[] ifaces = notification.getClass().getInterfaces();
-        return Iterables.filter(Arrays.asList(ifaces), new Predicate<Class<?>>() {
-            @Override
-            public boolean apply(final Class<?> input) {
-                if (Notification.class.equals(input)) {
-                    return false;
-                }
-                return Notification.class.isAssignableFrom(input);
-            }
-        });
+    Iterable<NotificationListenerRegistration<?>> listenersFor(final Notification notification) {
+        return current.get().listenersFor(notification);
     }
 
-    synchronized Iterable<NotificationListenerRegistration<?>> listenersFor(final Notification notification) {
-        final Set<NotificationListenerRegistration<?>> toNotify = new HashSet<>();
-
-        for (final Class<?> type : getNotificationTypes(notification)) {
-            final Collection<NotificationListenerRegistration<?>> l = listeners.get((Class<? extends Notification>) type);
-            if (l != null) {
-                toNotify.addAll(l);
-            }
-        }
-
-        return toNotify;
+    Iterable<Class<? extends Notification>> getKnownTypes() {
+        // Note: this relies on current having immutable listeners
+        return current.get().getListeners().keySet();
     }
 
-    synchronized Iterable<Class<? extends Notification>> getKnownTypes() {
-        return ImmutableList.copyOf(listeners.keySet());
+    @GuardedBy("this")
+    private Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> mutableListeners() {
+        return HashMultimap.create(current.get().getListeners());
     }
 
     synchronized void addRegistrations(final NotificationListenerRegistration<?>... registrations) {
+        Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> listeners =
+                mutableListeners();
+
         for (NotificationListenerRegistration<?> reg : registrations) {
             listeners.put(reg.getType(), reg);
         }
+
+        current.set(new ListenerMapGeneration(listeners));
     }
 
     synchronized void removeRegistrations(final NotificationListenerRegistration<?>... registrations) {
+        Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> listeners =
+                mutableListeners();
+
         for (NotificationListenerRegistration<?> reg : registrations) {
             listeners.remove(reg.getType(), reg);
         }
+
+        current.set(new ListenerMapGeneration(listeners));
     }
 }
diff --git a/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java b/opendaylight/md-sal/sal-binding-broker/src/main/java/org/opendaylight/controller/sal/binding/impl/ListenerMapGeneration.java
new file mode 100644 (file)
index 0000000..89e3fee
--- /dev/null
@@ -0,0 +1,78 @@
+/**
+ * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+package org.opendaylight.controller.sal.binding.impl;
+
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.HashSet;
+import java.util.Set;
+
+import org.opendaylight.yangtools.yang.binding.Notification;
+
+import com.google.common.base.Predicate;
+import com.google.common.collect.ImmutableMultimap;
+import com.google.common.collect.Iterables;
+import com.google.common.collect.Multimap;
+
+/**
+ * An immutable view of the current generation of listeners.
+ */
+final class ListenerMapGeneration {
+    private final Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> listeners;
+
+    ListenerMapGeneration() {
+        listeners = ImmutableMultimap.of();
+    }
+
+    ListenerMapGeneration(final Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> listeners) {
+        this.listeners = ImmutableMultimap.copyOf(listeners);
+    }
+
+    /**
+     * Current listeners. Exposed for creating the next generation.
+     *
+     * @return Current type-to-listener map.
+     */
+    Multimap<Class<? extends Notification>, NotificationListenerRegistration<?>> getListeners() {
+        return listeners;
+    }
+
+    private static Iterable<Class<?>> getNotificationTypes(final Notification notification) {
+        final Class<?>[] ifaces = notification.getClass().getInterfaces();
+        return Iterables.filter(Arrays.asList(ifaces), new Predicate<Class<?>>() {
+            @Override
+            public boolean apply(final Class<?> input) {
+                if (Notification.class.equals(input)) {
+                    return false;
+                }
+                return Notification.class.isAssignableFrom(input);
+            }
+        });
+    }
+
+    /**
+     * Look up the listeners which need to see this notification delivered.
+     *
+     * @param notification Notification object
+     * @return Iterable of listeners, may be null
+     *
+     * FIXME: improve such that it always returns non-null.
+     */
+    public Iterable<NotificationListenerRegistration<?>> listenersFor(final Notification notification) {
+        final Set<NotificationListenerRegistration<?>> ret = new HashSet<>();
+
+        for (final Class<?> type : getNotificationTypes(notification)) {
+            final Collection<NotificationListenerRegistration<?>> l = listeners.get((Class<? extends Notification>) type);
+            if (l != null) {
+                ret.addAll(l);
+            }
+        }
+
+        return ret;
+    }
+}
\ No newline at end of file