HHH-9706 Review concurrency of ClassLoaderService and warn against reuse

This commit is contained in:
Sanne Grinovero 2015-04-01 19:48:45 +01:00
parent 6ec4e0cdba
commit 4c690a8839
7 changed files with 62 additions and 53 deletions

View File

@ -299,8 +299,8 @@ public class MetadataBuildingProcess {
metadataCollector.processSecondPasses( rootMetadataBuildingContext ); metadataCollector.processSecondPasses( rootMetadataBuildingContext );
LinkedHashSet<AdditionalJaxbMappingProducer> producers = classLoaderService.loadJavaServices( AdditionalJaxbMappingProducer.class ); Iterable<AdditionalJaxbMappingProducer> producers = classLoaderService.loadJavaServices( AdditionalJaxbMappingProducer.class );
if ( producers != null && !producers.isEmpty() ) { if ( producers != null ) {
final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder(); final EntityHierarchyBuilder hierarchyBuilder = new EntityHierarchyBuilder();
// final MappingBinder mappingBinder = new MappingBinder( true ); // final MappingBinder mappingBinder = new MappingBinder( true );
// We need to disable validation here. It seems Envers is not producing valid (according to schema) XML // We need to disable validation here. It seems Envers is not producing valid (according to schema) XML

View File

@ -27,7 +27,6 @@ import java.io.File;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -316,7 +315,7 @@ public class StandardServiceRegistryBuilder {
} }
private void applyServiceContributors() { private void applyServiceContributors() {
final LinkedHashSet<ServiceContributor> serviceContributors = final Iterable<ServiceContributor> serviceContributors =
bootstrapServiceRegistry.getService( ClassLoaderService.class ) bootstrapServiceRegistry.getService( ClassLoaderService.class )
.loadJavaServices( ServiceContributor.class ); .loadJavaServices( ServiceContributor.class );

View File

@ -30,31 +30,33 @@ import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.ServiceLoader; import java.util.ServiceLoader;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.hibernate.boot.registry.classloading.spi.ClassLoaderService; import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.boot.registry.classloading.spi.ClassLoadingException; import org.hibernate.boot.registry.classloading.spi.ClassLoadingException;
import org.hibernate.cfg.AvailableSettings; import org.hibernate.cfg.AvailableSettings;
import org.hibernate.internal.CoreLogging; import org.hibernate.internal.CoreLogging;
import org.hibernate.internal.CoreMessageLogger;
import org.hibernate.internal.util.ClassLoaderHelper; import org.hibernate.internal.util.ClassLoaderHelper;
import org.jboss.logging.Logger;
/** /**
* Standard implementation of the service for interacting with class loaders * Standard implementation of the service for interacting with class loaders
* *
* @author Steve Ebersole * @author Steve Ebersole
* @author Sanne Grinovero
*/ */
public class ClassLoaderServiceImpl implements ClassLoaderService { public class ClassLoaderServiceImpl implements ClassLoaderService {
private static final Logger log = CoreLogging.logger( ClassLoaderServiceImpl.class );
private final Map<Class, ServiceLoader> serviceLoaders = new HashMap<Class, ServiceLoader>(); private static final CoreMessageLogger log = CoreLogging.messageLogger( ClassLoaderServiceImpl.class );
private AggregatedClassLoader aggregatedClassLoader;
private final ConcurrentMap<Class, ServiceLoader> serviceLoaders = new ConcurrentHashMap<Class, ServiceLoader>();
private volatile AggregatedClassLoader aggregatedClassLoader;
/** /**
* Constructs a ClassLoaderServiceImpl with standard set-up * Constructs a ClassLoaderServiceImpl with standard set-up
@ -172,7 +174,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
} }
private static class AggregatedClassLoader extends ClassLoader { private static class AggregatedClassLoader extends ClassLoader {
private ClassLoader[] individualClassLoaders; private final ClassLoader[] individualClassLoaders;
private AggregatedClassLoader(final LinkedHashSet<ClassLoader> orderedClassLoaderSet) { private AggregatedClassLoader(final LinkedHashSet<ClassLoader> orderedClassLoaderSet) {
super( null ); super( null );
@ -229,16 +231,13 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
throw new ClassNotFoundException( "Could not load requested class : " + name ); throw new ClassNotFoundException( "Could not load requested class : " + name );
} }
public void destroy() {
individualClassLoaders = null;
}
} }
@Override @Override
@SuppressWarnings({"unchecked"}) @SuppressWarnings({"unchecked"})
public <T> Class<T> classForName(String className) { public <T> Class<T> classForName(String className) {
try { try {
return (Class<T>) Class.forName( className, true, aggregatedClassLoader ); return (Class<T>) Class.forName( className, true, getAggregatedClassLoader() );
} }
catch (Exception e) { catch (Exception e) {
throw new ClassLoadingException( "Unable to load class [" + className + "]", e ); throw new ClassLoadingException( "Unable to load class [" + className + "]", e );
@ -255,7 +254,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
} }
try { try {
return aggregatedClassLoader.getResource( name ); return getAggregatedClassLoader().getResource( name );
} }
catch (Exception ignore) { catch (Exception ignore) {
} }
@ -275,7 +274,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
try { try {
log.tracef( "trying via [ClassLoader.getResourceAsStream(\"%s\")]", name ); log.tracef( "trying via [ClassLoader.getResourceAsStream(\"%s\")]", name );
final InputStream stream = aggregatedClassLoader.getResourceAsStream( name ); final InputStream stream = getAggregatedClassLoader().getResourceAsStream( name );
if ( stream != null ) { if ( stream != null ) {
return stream; return stream;
} }
@ -295,7 +294,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
try { try {
log.tracef( "trying via [ClassLoader.getResourceAsStream(\"%s\")]", stripped ); log.tracef( "trying via [ClassLoader.getResourceAsStream(\"%s\")]", stripped );
final InputStream stream = aggregatedClassLoader.getResourceAsStream( stripped ); final InputStream stream = getAggregatedClassLoader().getResourceAsStream( stripped );
if ( stream != null ) { if ( stream != null ) {
return stream; return stream;
} }
@ -311,7 +310,7 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
public List<URL> locateResources(String name) { public List<URL> locateResources(String name) {
final ArrayList<URL> urls = new ArrayList<URL>(); final ArrayList<URL> urls = new ArrayList<URL>();
try { try {
final Enumeration<URL> urlEnumeration = aggregatedClassLoader.getResources( name ); final Enumeration<URL> urlEnumeration = getAggregatedClassLoader().getResources( name );
if ( urlEnumeration != null && urlEnumeration.hasMoreElements() ) { if ( urlEnumeration != null && urlEnumeration.hasMoreElements() ) {
while ( urlEnumeration.hasMoreElements() ) { while ( urlEnumeration.hasMoreElements() ) {
urls.add( urlEnumeration.nextElement() ); urls.add( urlEnumeration.nextElement() );
@ -326,16 +325,12 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
@Override @Override
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public <S> LinkedHashSet<S> loadJavaServices(Class<S> serviceContract) { public <S> Collection<S> loadJavaServices(Class<S> serviceContract) {
ServiceLoader<S> serviceLoader; ServiceLoader<S> serviceLoader = serviceLoaders.get( serviceContract );
if ( serviceLoaders.containsKey( serviceContract ) ) { if ( serviceLoader == null ) {
serviceLoader = serviceLoaders.get( serviceContract ); serviceLoader = ServiceLoader.load( serviceContract, getAggregatedClassLoader() );
}
else {
serviceLoader = ServiceLoader.load( serviceContract, aggregatedClassLoader );
serviceLoaders.put( serviceContract, serviceLoader ); serviceLoaders.put( serviceContract, serviceLoader );
} }
final LinkedHashSet<S> services = new LinkedHashSet<S>(); final LinkedHashSet<S> services = new LinkedHashSet<S>();
for ( S service : serviceLoader ) { for ( S service : serviceLoader ) {
services.add( service ); services.add( service );
@ -343,17 +338,23 @@ public class ClassLoaderServiceImpl implements ClassLoaderService {
return services; return services;
} }
private ClassLoader getAggregatedClassLoader() {
final ClassLoader aggregated = this.aggregatedClassLoader;
if ( aggregated == null ) {
throw log.usingStoppedClassLoaderService();
}
return aggregated;
}
@Override @Override
public void stop() { public void stop() {
for ( ServiceLoader serviceLoader : serviceLoaders.values() ) { for ( ServiceLoader serviceLoader : serviceLoaders.values() ) {
serviceLoader.reload(); // clear service loader providers serviceLoader.reload(); // clear service loader providers
} }
serviceLoaders.clear(); serviceLoaders.clear();
//Avoid ClassLoader leaks
if ( aggregatedClassLoader != null ) { this.aggregatedClassLoader = null;
aggregatedClassLoader.destroy();
aggregatedClassLoader = null;
}
} }
} }

View File

@ -25,7 +25,7 @@ package org.hibernate.boot.registry.classloading.spi;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.LinkedHashSet; import java.util.Collection;
import java.util.List; import java.util.List;
import org.hibernate.service.Service; import org.hibernate.service.Service;
@ -87,5 +87,5 @@ public interface ClassLoaderService extends Service, Stoppable {
* *
* @return The ordered set of discovered services. * @return The ordered set of discovered services.
*/ */
public <S> LinkedHashSet<S> loadJavaServices(Class<S> serviceContract); public <S> Collection<S> loadJavaServices(Class<S> serviceContract);
} }

View File

@ -1700,4 +1700,6 @@ public interface CoreMessageLogger extends BasicLogger {
@Message(value = "Unable to interpret type [%s] as an AttributeConverter due to an exception : %s", id = 468) @Message(value = "Unable to interpret type [%s] as an AttributeConverter due to an exception : %s", id = 468)
void logBadHbmAttributeConverterType(String type, String exceptionMessage); void logBadHbmAttributeConverterType(String type, String exceptionMessage);
@Message(value = "The ClassLoaderService can not be reused. This instance was stopped already.", id = 469)
HibernateException usingStoppedClassLoaderService();
} }

View File

@ -5,9 +5,9 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.util.Enumeration; import java.util.Enumeration;
import java.util.LinkedHashSet;
import javax.persistence.Entity; import javax.persistence.Entity;
import org.hibernate.HibernateException;
import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder;
import org.hibernate.boot.registry.StandardServiceRegistryBuilder; import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
@ -15,13 +15,11 @@ import org.hibernate.boot.registry.classloading.spi.ClassLoaderService;
import org.hibernate.integrator.spi.Integrator; import org.hibernate.integrator.spi.Integrator;
import org.hibernate.internal.util.ConfigHelper; import org.hibernate.internal.util.ConfigHelper;
import org.hibernate.service.ServiceRegistry; import org.hibernate.service.ServiceRegistry;
import org.hibernate.testing.TestForIssue; import org.hibernate.testing.TestForIssue;
import org.junit.Assert; import org.junit.Assert;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assert.assertNotNull; import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertSame; import static org.junit.Assert.assertSame;
/** /**
@ -76,14 +74,18 @@ public class ClassLoaderServiceImplTest {
StandardServiceRegistryBuilder.destroy( serviceRegistry ); StandardServiceRegistryBuilder.destroy( serviceRegistry );
// Should return null -- aggregratedClassLoader blown away. try {
testIntegrator2 = findTestIntegrator( classLoaderService ); findTestIntegrator( classLoaderService );
assertNull( testIntegrator2 ); Assert.fail("Should have thrown an HibernateException -- the ClassLoaderService instance was closed.");
}
catch (HibernateException e) {
String message = e.getMessage();
Assert.assertEquals( "HHH000469: The ClassLoaderService can not be reused. This instance was stopped already.", message);
}
} }
private TestIntegrator findTestIntegrator(ClassLoaderService classLoaderService) { private TestIntegrator findTestIntegrator(ClassLoaderService classLoaderService) {
final LinkedHashSet<Integrator> integrators = classLoaderService.loadJavaServices( Integrator.class ); for ( Integrator integrator : classLoaderService.loadJavaServices( Integrator.class ) ) {
for (Integrator integrator : integrators) {
if ( integrator instanceof TestIntegrator ) { if ( integrator instanceof TestIntegrator ) {
return (TestIntegrator) integrator; return (TestIntegrator) integrator;
} }

View File

@ -20,7 +20,6 @@
*/ */
package org.hibernate.osgi; package org.hibernate.osgi;
import java.util.Collections;
import java.util.LinkedHashSet; import java.util.LinkedHashSet;
import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl; import org.hibernate.boot.registry.classloading.internal.ClassLoaderServiceImpl;
@ -45,10 +44,16 @@ public class OSGiClassLoaderServiceImpl extends ClassLoaderServiceImpl implement
@Override @Override
public <S> LinkedHashSet<S> loadJavaServices(Class<S> serviceContract) { public <S> LinkedHashSet<S> loadJavaServices(Class<S> serviceContract) {
LinkedHashSet<S> parentDiscoveredServices = super.loadJavaServices( serviceContract ); Iterable<S> parentDiscoveredServices = super.loadJavaServices( serviceContract );
S[] serviceImpls = osgiServiceUtil.getServiceImpls(serviceContract); S[] serviceImpls = osgiServiceUtil.getServiceImpls(serviceContract);
Collections.addAll( parentDiscoveredServices, serviceImpls ); LinkedHashSet<S> composite = new LinkedHashSet<S>();
return parentDiscoveredServices; for ( S service : parentDiscoveredServices ) {
composite.add( service );
}
for ( S service : serviceImpls ) {
composite.add( service );
}
return composite;
} }
@Override @Override