From 786f00851cb2e47c72f9c6eaec15bf915c67dbdb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christoph=20L=C3=A4ubrich?= Date: Tue, 4 Jul 2023 07:38:03 +0200 Subject: [PATCH] Make the URLStreamHandler as lazy as possible URLStreamHandlerFactoryImpl currently opens a service tracker unconditionally for URLStreamHandlerService to discover registered handlers. This leads to any handler being fetched eagerly instead of when it is required. This changes the handling in a way where it is as lazy as possible by - not open a tracker until a protocol is requested - use a service tracker with a specific protocol filter in proxy - only fetch the service when it is first used --- .../url/URLStreamHandlerFactoryImpl.java | 56 ++-- .../internal/url/URLStreamHandlerProxy.java | 241 +++++++++--------- 2 files changed, 129 insertions(+), 168 deletions(-) diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerFactoryImpl.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerFactoryImpl.java index ddad3ba34b1..0a0b9c2d693 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerFactoryImpl.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerFactoryImpl.java @@ -15,21 +15,22 @@ package org.eclipse.osgi.internal.url; import java.lang.reflect.Method; -import java.net.*; +import java.net.URL; +import java.net.URLStreamHandler; +import java.net.URLStreamHandlerFactory; import java.security.AccessController; -import java.util.*; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Map; +import java.util.StringTokenizer; +import java.util.concurrent.ConcurrentHashMap; import org.eclipse.osgi.framework.log.FrameworkLogEntry; import org.eclipse.osgi.framework.util.SecureAction; import org.eclipse.osgi.internal.framework.EquinoxContainer; import org.eclipse.osgi.internal.location.EquinoxLocations; -import org.eclipse.osgi.internal.messages.Msg; import org.eclipse.osgi.storage.url.BundleResourceHandler; -import org.eclipse.osgi.util.NLS; import org.osgi.framework.BundleContext; -import org.osgi.framework.ServiceReference; -import org.osgi.service.url.URLConstants; -import org.osgi.service.url.URLStreamHandlerService; -import org.osgi.util.tracker.ServiceTracker; /** * This class contains the URL stream handler factory for the OSGi framework. @@ -40,10 +41,8 @@ public class URLStreamHandlerFactoryImpl extends MultiplexingFactory implements public static final String PROTOCOL_REFERENCE = "reference"; //$NON-NLS-1$ static final SecureAction secureAction = AccessController.doPrivileged(SecureAction.createSecureAction()); - private ServiceTracker handlerTracker; - private static final List> ignoredClasses = Arrays.asList(new Class[] {MultiplexingURLStreamHandler.class, URLStreamHandlerFactoryImpl.class, URL.class}); - private Map proxies; + private Map proxies; private URLStreamHandlerFactory parentFactory; private ThreadLocal> creatingProtocols = new ThreadLocal<>(); @@ -54,10 +53,7 @@ public class URLStreamHandlerFactoryImpl extends MultiplexingFactory implements */ public URLStreamHandlerFactoryImpl(BundleContext context, EquinoxContainer container) { super(context, container); - - proxies = new Hashtable<>(15); - handlerTracker = new ServiceTracker<>(context, URLSTREAMHANDLERCLASS, null); - handlerTracker.open(); + proxies = new ConcurrentHashMap<>(); } private Class getBuiltIn(String protocol, String builtInHandlers) { @@ -154,35 +150,13 @@ public URLStreamHandler createInternalURLStreamHandler(String protocol) { if (frameworkHandler != null) { return frameworkHandler; } - //Now we check the service registry - //first check to see if the handler is in the cache - URLStreamHandlerProxy handler = (URLStreamHandlerProxy) proxies.get(protocol); - if (handler != null) - return (handler); - //look through the service registry for a URLStramHandler registered for this protocol - ServiceReference[] serviceReferences = handlerTracker.getServiceReferences(); - if (serviceReferences == null) - return null; - for (ServiceReference serviceReference : serviceReferences) { - Object prop = serviceReference.getProperty(URLConstants.URL_HANDLER_PROTOCOL); - if (prop instanceof String) - prop = new String[] {(String) prop}; // TODO should this be a warning? - if (!(prop instanceof String[])) { - String message = NLS.bind(Msg.URL_HANDLER_INCORRECT_TYPE, new Object[]{URLConstants.URL_HANDLER_PROTOCOL, URLSTREAMHANDLERCLASS, serviceReference.getBundle()}); - container.getLogServices().log(EquinoxContainer.NAME, FrameworkLogEntry.WARNING, message, null); - continue; - } - String[] protocols = (String[]) prop; - for (String candidateProtocol : protocols) { - if (candidateProtocol.equals(protocol)) { - handler = new URLStreamHandlerProxy(protocol, serviceReference, context); - proxies.put(protocol, handler); - return (handler); - } - } + URLStreamHandlerProxy handler = proxies.computeIfAbsent(protocol, p -> new URLStreamHandlerProxy(p, context)); + if (handler.isActive()) { + return handler; } return null; + } protected URLStreamHandler findAuthorizedURLStreamHandler(String protocol) { diff --git a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerProxy.java b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerProxy.java index eb585627b67..a37feaf287f 100644 --- a/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerProxy.java +++ b/bundles/org.eclipse.osgi/container/src/org/eclipse/osgi/internal/url/URLStreamHandlerProxy.java @@ -17,70 +17,84 @@ import java.io.IOException; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.net.*; -import org.osgi.framework.*; +import java.net.InetAddress; +import java.net.Proxy; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLStreamHandler; +import java.util.function.Supplier; +import org.osgi.framework.BundleContext; +import org.osgi.framework.Constants; +import org.osgi.framework.Filter; +import org.osgi.framework.InvalidSyntaxException; +import org.osgi.framework.ServiceReference; import org.osgi.service.url.URLConstants; import org.osgi.service.url.URLStreamHandlerService; import org.osgi.util.tracker.ServiceTracker; import org.osgi.util.tracker.ServiceTrackerCustomizer; /** - * The URLStreamHandlerProxy is a URLStreamHandler that acts as a proxy for registered - * URLStreamHandlerServices. When a URLStreamHandler is requested from the URLStreamHandlerFactory - * and it exists in the service registry, a URLStreamHandlerProxy is created which will pass all the - * requests from the requestor to the real URLStreamHandlerService. We can't return the real - * URLStreamHandlerService from the URLStreamHandlerFactory because the JVM caches URLStreamHandlers - * and therefore would not support a dynamic environment of URLStreamHandlerServices being registered - * and unregistered. + * The URLStreamHandlerProxy is a URLStreamHandler that acts as a proxy for + * registered URLStreamHandlerServices. When a URLStreamHandler is requested + * from the URLStreamHandlerFactory and it exists in the service registry, a + * URLStreamHandlerProxy is created which will pass all the requests from the + * requestor to the real URLStreamHandlerService. We can't return the real + * URLStreamHandlerService from the URLStreamHandlerFactory because the JVM + * caches URLStreamHandlers and therefore would not support a dynamic + * environment of URLStreamHandlerServices being registered and unregistered. */ -public class URLStreamHandlerProxy extends URLStreamHandler implements ServiceTrackerCustomizer> { +public class URLStreamHandlerProxy extends URLStreamHandler { + + private static final URLStreamHandlerService NO_HANDLER = new NullURLStreamHandlerService(); // TODO lots of type-based names protected URLStreamHandlerService realHandlerService; - protected URLStreamHandlerSetter urlSetter; - - protected ServiceTracker> urlStreamHandlerServiceTracker; - - protected BundleContext context; - protected ServiceReference urlStreamServiceReference; - - protected String protocol; + protected final URLStreamHandlerSetter urlSetter; - protected int ranking = Integer.MIN_VALUE; + protected final ServiceTracker urlStreamHandlerServiceTracker; - public URLStreamHandlerProxy(String protocol, ServiceReference reference, BundleContext context) { - this.context = context; - this.protocol = protocol; + public URLStreamHandlerProxy(String protocol, BundleContext context) { urlSetter = new URLStreamHandlerSetter(this); - //set the handler and ranking - setNewHandler(reference, getRank(reference)); + Filter filter; + try { + filter = context.createFilter(String.format("(&(%s=%s)(%s=%s))", Constants.OBJECTCLASS, //$NON-NLS-1$ + URLStreamHandlerFactoryImpl.URLSTREAMHANDLERCLASS, URLConstants.URL_HANDLER_PROTOCOL, protocol)); + } catch (InvalidSyntaxException e) { + throw new AssertionError("should never happen!", e); //$NON-NLS-1$ + } - urlStreamHandlerServiceTracker = new ServiceTracker<>(context, URLStreamHandlerFactoryImpl.URLSTREAMHANDLERCLASS, this); + urlStreamHandlerServiceTracker = new ServiceTracker<>(context, filter, + new ServiceTrackerCustomizer() { + + @Override + public LazyURLStreamHandlerService addingService(ServiceReference reference) { + return new LazyURLStreamHandlerService(context, reference); + } + + @Override + public void modifiedService(ServiceReference reference, + LazyURLStreamHandlerService service) { + // nothing to do here... + } + + @Override + public void removedService(ServiceReference reference, + LazyURLStreamHandlerService service) { + service.dispose(); + } + }); URLStreamHandlerFactoryImpl.secureAction.open(urlStreamHandlerServiceTracker); } - private void setNewHandler(ServiceReference reference, int rank) { - if (urlStreamServiceReference != null) - context.ungetService(urlStreamServiceReference); - - urlStreamServiceReference = reference; - ranking = rank; - - if (reference == null) - realHandlerService = new NullURLStreamHandlerService(); - else - realHandlerService = URLStreamHandlerFactoryImpl.secureAction.getService(reference, context); - } - /** * @see java.net.URLStreamHandler#equals(URL, URL) */ @Override protected boolean equals(URL url1, URL url2) { - return realHandlerService.equals(url1, url2); + return getRealHandlerService().equals(url1, url2); } /** @@ -88,7 +102,7 @@ protected boolean equals(URL url1, URL url2) { */ @Override protected int getDefaultPort() { - return realHandlerService.getDefaultPort(); + return getRealHandlerService().getDefaultPort(); } /** @@ -96,7 +110,7 @@ protected int getDefaultPort() { */ @Override protected InetAddress getHostAddress(URL url) { - return realHandlerService.getHostAddress(url); + return getRealHandlerService().getHostAddress(url); } /** @@ -104,7 +118,7 @@ protected InetAddress getHostAddress(URL url) { */ @Override protected int hashCode(URL url) { - return realHandlerService.hashCode(url); + return getRealHandlerService().hashCode(url); } /** @@ -112,7 +126,7 @@ protected int hashCode(URL url) { */ @Override protected boolean hostsEqual(URL url1, URL url2) { - return realHandlerService.hostsEqual(url1, url2); + return getRealHandlerService().hostsEqual(url1, url2); } /** @@ -120,7 +134,7 @@ protected boolean hostsEqual(URL url1, URL url2) { */ @Override protected URLConnection openConnection(URL url) throws IOException { - return realHandlerService.openConnection(url); + return getRealHandlerService().openConnection(url); } /** @@ -128,7 +142,7 @@ protected URLConnection openConnection(URL url) throws IOException { */ @Override protected void parseURL(URL url, String str, int start, int end) { - realHandlerService.parseURL(urlSetter, url, str, start, end); + getRealHandlerService().parseURL(urlSetter, url, str, start, end); } /** @@ -136,7 +150,7 @@ protected void parseURL(URL url, String str, int start, int end) { */ @Override protected boolean sameFile(URL url1, URL url2) { - return realHandlerService.sameFile(url1, url2); + return getRealHandlerService().sameFile(url1, url2); } /** @@ -144,14 +158,16 @@ protected boolean sameFile(URL url1, URL url2) { */ @Override protected String toExternalForm(URL url) { - return realHandlerService.toExternalForm(url); + return getRealHandlerService().toExternalForm(url); } /** - * @see java.net.URLStreamHandler#setURL(URL, String, String, int, String, String, String, String, String) + * @see java.net.URLStreamHandler#setURL(URL, String, String, int, String, + * String, String, String, String) */ @Override - public void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref) { + public void setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, + String query, String ref) { super.setURL(u, protocol, host, port, authority, userInfo, file, query, ref); } @@ -159,91 +175,20 @@ public void setURL(URL u, String protocol, String host, int port, String authori @Override public void setURL(URL url, String protocol, String host, int port, String file, String ref) { - //using non-deprecated URLStreamHandler.setURL method. - //setURL(URL u, String protocol, String host, int port, String authority, String userInfo, String file, String query, String ref) + // using non-deprecated URLStreamHandler.setURL method. + // setURL(URL u, String protocol, String host, int port, String authority, + // String userInfo, String file, String query, String ref) super.setURL(url, protocol, host, port, null, null, file, null, ref); } - /** - * @see org.osgi.util.tracker.ServiceTrackerCustomizer#addingService(ServiceReference) - */ - @Override - public ServiceReference addingService(ServiceReference reference) { - //check to see if our protocol is being registered by another service - Object prop = reference.getProperty(URLConstants.URL_HANDLER_PROTOCOL); - if (prop instanceof String) { - prop = new String[] {(String) prop}; - } - if (!(prop instanceof String[])) { - return null; - } - String[] protocols = (String[]) prop; - for (String candidateProtocol : protocols) { - if (candidateProtocol.equals(protocol)) { - //If our protocol is registered by another service, check the service ranking and switch URLStreamHandlers if nessecary. - int newServiceRanking = getRank(reference); - if (newServiceRanking > ranking || urlStreamServiceReference == null) - setNewHandler(reference, newServiceRanking); - return reference; - } - } - - //we don't want to continue hearing events about a URLStreamHandlerService not registered under our protocol - return null; - } - - /** - * @see org.osgi.util.tracker.ServiceTrackerCustomizer#modifiedService(ServiceReference, Object) - */ - // check to see if the ranking has changed. If so, re-select a new URLHandler - @Override - public void modifiedService(ServiceReference reference, ServiceReference service) { - int newRank = getRank(reference); - if (reference == urlStreamServiceReference) { - if (newRank < ranking) { - // The URLHandler we are currently using has dropped it's ranking below a URLHandler registered - // for the same protocol. We need to swap out URLHandlers. - // this should get us the highest ranked service, if available - ServiceReference newReference = urlStreamHandlerServiceTracker.getServiceReference(); - if (newReference != urlStreamServiceReference && newReference != null) { - setNewHandler(newReference, ((Integer) newReference.getProperty(Constants.SERVICE_RANKING)).intValue()); - } - } - } else if (newRank > ranking) { - // the service changed is another URLHandler that we are not currently using - // If it's ranking is higher, we must swap it in. - setNewHandler(reference, newRank); - } - } - - /** - * @see org.osgi.util.tracker.ServiceTrackerCustomizer#removedService(ServiceReference, Object) - */ - @Override - public void removedService(ServiceReference reference, ServiceReference service) { - // check to see if our URLStreamHandler was unregistered. - if (reference != urlStreamServiceReference) - return; - // If so, look for a lower ranking URLHandler - // this should get us the highest ranking service left, if available - ServiceReference newReference = urlStreamHandlerServiceTracker.getServiceReference(); - // if newReference == null then we will use the NullURLStreamHandlerService here - setNewHandler(newReference, getRank(newReference)); - } - - private int getRank(ServiceReference reference) { - if (reference == null) - return Integer.MIN_VALUE; - Object property = reference.getProperty(Constants.SERVICE_RANKING); - return (property instanceof Integer) ? ((Integer) property).intValue() : 0; - } - @Override protected URLConnection openConnection(URL u, Proxy p) throws IOException { try { - Method openConn = realHandlerService.getClass().getMethod("openConnection", new Class[] {URL.class, Proxy.class}); //$NON-NLS-1$ + URLStreamHandlerService service = getRealHandlerService(); + Method openConn = service.getClass().getMethod("openConnection", //$NON-NLS-1$ + new Class[] { URL.class, Proxy.class }); openConn.setAccessible(true); - return (URLConnection) openConn.invoke(realHandlerService, new Object[] {u, p}); + return (URLConnection) openConn.invoke(service, new Object[] { u, p }); } catch (InvocationTargetException e) { if (e.getTargetException() instanceof IOException) throw (IOException) e.getTargetException(); @@ -253,4 +198,46 @@ protected URLConnection openConnection(URL u, Proxy p) throws IOException { throw new UnsupportedOperationException(e); } } + + public boolean isActive() { + return urlStreamHandlerServiceTracker.getService() != null; + } + + public URLStreamHandlerService getRealHandlerService() { + LazyURLStreamHandlerService service = urlStreamHandlerServiceTracker.getService(); + if (service != null) { + return service.get(); + } + return NO_HANDLER; + } + + private static final class LazyURLStreamHandlerService implements Supplier { + + private BundleContext bundleContext; + private ServiceReference reference; + private URLStreamHandlerService service; + private boolean disposed; + + LazyURLStreamHandlerService(BundleContext bundleContext, ServiceReference reference) { + this.bundleContext = bundleContext; + this.reference = reference; + } + + synchronized void dispose() { + disposed = true; + if (service != null) { + service = null; + bundleContext.ungetService(reference); + } + } + + @Override + public synchronized URLStreamHandlerService get() { + if (service == null && !disposed) { + service = bundleContext.getService(reference); + } + return service; + } + + } }