Bug 305068 - OSGiContextStrategy cleanup is too expensive
diff --git a/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java b/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
index 333eeef..9ee77e1 100644
--- a/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
+++ b/bundles/org.eclipse.e4.core.services/src/org/eclipse/e4/internal/core/services/osgi/OSGiContextStrategy.java
@@ -1,5 +1,5 @@
/*******************************************************************************
- * Copyright (c) 2009 IBM Corporation and others.
+ * Copyright (c) 2009, 2010 IBM Corporation 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
@@ -16,9 +16,14 @@
import java.util.Map;
import java.util.WeakHashMap;
import org.eclipse.e4.core.services.IDisposable;
+import org.eclipse.e4.core.services.context.ContextChangeEvent;
import org.eclipse.e4.core.services.context.IContextFunction;
import org.eclipse.e4.core.services.context.IEclipseContext;
+import org.eclipse.e4.core.services.context.IRunAndTrack;
+import org.eclipse.e4.core.services.context.spi.IContextConstants;
import org.eclipse.e4.core.services.context.spi.ILookupStrategy;
+import org.eclipse.e4.core.services.injector.IObjectProvider;
+import org.eclipse.e4.core.services.internal.context.ObjectProviderContext;
import org.osgi.framework.BundleContext;
import org.osgi.framework.Constants;
import org.osgi.framework.InvalidSyntaxException;
@@ -31,14 +36,15 @@
* <p>
* OSGi services are looked up by service class name.
*/
-public class OSGiContextStrategy implements ILookupStrategy, IDisposable, ServiceTrackerCustomizer {
+public class OSGiContextStrategy implements ILookupStrategy, IDisposable, ServiceTrackerCustomizer,
+ IRunAndTrack {
class ServiceData {
// the service name
String name;
ServiceTracker tracker;
// the contexts using this service (IEclipseContext -> null)
- final Map users = new WeakHashMap();
+ final Map<IEclipseContext, Object> users = new WeakHashMap<IEclipseContext, Object>();
ServiceData(String name) {
this.name = name;
@@ -46,6 +52,8 @@
public void addContext(IEclipseContext originatingContext) {
users.put(originatingContext, null);
+ // track this context so we can cleanup when the context is disposed
+ originatingContext.runAndTrack(OSGiContextStrategy.this, null);
}
}
@@ -53,7 +61,8 @@
/**
* Map of String (service name) -> ServiceData
*/
- private Map services = Collections.synchronizedMap(new HashMap());
+ private Map<String, ServiceData> services = Collections
+ .synchronizedMap(new HashMap<String, ServiceData>());
public OSGiContextStrategy(BundleContext bc) {
super();
@@ -66,33 +75,16 @@
if (newValue == null)
return null;
// for performance we store the concrete service object with each context that requested it
- ServiceData data = (ServiceData) services.get(name);
+ ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data == null)
return null;
- for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
- ((IEclipseContext) it.next()).set(name, newValue);
+ for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+ it.next().set(name, newValue);
return newValue;
}
- /**
- * Discards any services that are no longer used by any strongly reachable contexts.
- */
- private void cleanReferences() {
- synchronized (services) {
- for (Iterator it = services.values().iterator(); it.hasNext();) {
- ServiceData data = (ServiceData) it.next();
- // if there are no more references, discard the service
- if (data.users.isEmpty()) {
- data.tracker.close();
- it.remove();
- }
- }
- }
- }
-
public boolean containsKey(String name, IEclipseContext context) {
- cleanReferences();
// first look for a registered IContextFunction matching the name
if (getContextFunction(name) != null)
return true;
@@ -102,15 +94,33 @@
public void dispose() {
synchronized (services) {
- for (Iterator it = services.values().iterator(); it.hasNext();)
- ((ServiceData) it.next()).tracker.close();
+ for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();)
+ it.next().tracker.close();
services.clear();
}
}
+ /**
+ * Returns the service data corresponding to the given name, or <code>null</code> if no such
+ * data is available.
+ */
+ private ServiceData getServiceData(String name) {
+ ServiceData data = services.get(name);
+ if (data == null)
+ return null;
+ if (data.users.isEmpty()) {
+ data.tracker.close();
+ services.remove(name);
+ return null;
+ }
+ return data;
+ }
+
public Object lookup(String name, IEclipseContext originatingContext) {
- cleanReferences();
- ServiceData data = (ServiceData) services.get(name);
+ // services must be fully qualified type names
+ if (name == null || name.indexOf('.') == -1)
+ return null;
+ ServiceData data = getServiceData(name);
if (data == null) {
// first look for a registered IContextFunction matching the name
ServiceReference ref = getContextFunction(name);
@@ -155,23 +165,23 @@
public void modifiedService(ServiceReference reference, Object service) {
String name = serviceName(reference);
- ServiceData data = (ServiceData) services.get(name);
+ ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data == null)
return;
- for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
- ((IEclipseContext) it.next()).set(name, service);
+ for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+ it.next().set(name, service);
}
public void removedService(ServiceReference reference, Object service) {
String name = serviceName(reference);
// must set to null rather than removing so injection continues to work
- ServiceData data = (ServiceData) services.get(name);
+ ServiceData data = getServiceData(name);
// may have been cleaned up concurrently
if (data == null)
return;
- for (Iterator it = data.users.keySet().iterator(); it.hasNext();)
- ((IEclipseContext) it.next()).set(name, null);
+ for (Iterator<IEclipseContext> it = data.users.keySet().iterator(); it.hasNext();)
+ it.next().set(name, null);
bundleContext.ungetService(reference);
}
@@ -181,4 +191,38 @@
private String serviceName(ServiceReference reference) {
return ((String[]) reference.getProperty(Constants.OBJECTCLASS))[0];
}
+
+ /**
+ * Listen for changes on all contexts that have obtained a service from this strategy, so that
+ * we can do appropriate cleanup of our caches when the requesting context is disposed.
+ */
+ public boolean notify(ContextChangeEvent event) {
+ IEclipseContext context = getContext(event);
+ if (context == null)
+ return false;
+ if (event.getEventType() != ContextChangeEvent.DISPOSE) {
+ // do a lookup so the listener isn't removed
+ context.get(IContextConstants.PARENT);
+ return true;
+ }
+ synchronized (services) {
+ for (Iterator<ServiceData> it = services.values().iterator(); it.hasNext();) {
+ ServiceData data = it.next();
+ data.users.remove(context);
+ // if there are no more references, discard the service
+ if (data.users.isEmpty()) {
+ it.remove();
+ data.tracker.close();
+ }
+ }
+ }
+ return true;
+ }
+
+ private IEclipseContext getContext(ContextChangeEvent event) {
+ IObjectProvider provider = event.getContext();
+ if (provider instanceof ObjectProviderContext)
+ return ((ObjectProviderContext) provider).getContext();
+ return null;
+ }
}