Repository: lens Updated Branches: refs/heads/master 1362e667e -> df75a4b83
LENS-1160: Fix classloader getting closed on lens session close. Project: http://git-wip-us.apache.org/repos/asf/lens/repo Commit: http://git-wip-us.apache.org/repos/asf/lens/commit/df75a4b8 Tree: http://git-wip-us.apache.org/repos/asf/lens/tree/df75a4b8 Diff: http://git-wip-us.apache.org/repos/asf/lens/diff/df75a4b8 Branch: refs/heads/master Commit: df75a4b838f5572ff6ef2c7466681853ac00ecb4 Parents: 1362e66 Author: Rajat Khandelwal <pro...@apache.org> Authored: Wed Jun 8 23:11:40 2016 +0530 Committer: Rajat Khandelwal <rajatgupt...@gmail.com> Committed: Wed Jun 8 23:11:40 2016 +0530 ---------------------------------------------------------------------- .../server/session/DatabaseResourceService.java | 96 ++-------- .../lens/server/session/LensSessionImpl.java | 148 ++++++++++----- .../lens/server/session/SessionClassLoader.java | 55 ++++++ .../server/session/UncloseableClassLoader.java | 41 +++++ .../server/session/TestSessionClassLoaders.java | 180 ++++++++++++++++--- 5 files changed, 371 insertions(+), 149 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/lens/blob/df75a4b8/lens-server/src/main/java/org/apache/lens/server/session/DatabaseResourceService.java ---------------------------------------------------------------------- diff --git a/lens-server/src/main/java/org/apache/lens/server/session/DatabaseResourceService.java b/lens-server/src/main/java/org/apache/lens/server/session/DatabaseResourceService.java index 1f63ed7..511e4cf 100644 --- a/lens-server/src/main/java/org/apache/lens/server/session/DatabaseResourceService.java +++ b/lens-server/src/main/java/org/apache/lens/server/session/DatabaseResourceService.java @@ -21,7 +21,6 @@ package org.apache.lens.server.session; import java.io.IOException; import java.net.MalformedURLException; import java.net.URL; -import java.net.URLClassLoader; import java.util.*; import org.apache.lens.server.LensServices; @@ -34,7 +33,6 @@ import org.apache.commons.lang3.StringUtils; import org.apache.hadoop.fs.FileStatus; import org.apache.hadoop.fs.FileSystem; import org.apache.hadoop.fs.Path; -import org.apache.hadoop.hive.conf.HiveConf; import org.apache.hive.service.AbstractService; import lombok.extern.slf4j.Slf4j; @@ -45,9 +43,8 @@ import lombok.extern.slf4j.Slf4j; @Slf4j public class DatabaseResourceService extends AbstractService { public static final String NAME = "database-resources"; - private Map<String, ClassLoader> classLoaderCache; - private Map<String, List<LensSessionImpl.ResourceEntry>> dbResEntryMap; - + private Map<String, UncloseableClassLoader> classLoaderCache = new HashMap<>(); + private final Map<String, List<LensSessionImpl.ResourceEntry>> dbResEntryMap = new HashMap<>(); /** * The metrics service. */ @@ -73,16 +70,8 @@ public class DatabaseResourceService extends AbstractService { } @Override - public synchronized void init(HiveConf hiveConf) { - super.init(hiveConf); - classLoaderCache = new HashMap<>(); - dbResEntryMap = new HashMap<>(); - } - - @Override public synchronized void start() { super.start(); - try { log.info("Starting loading DB specific resources"); loadDbResourceEntries(); @@ -197,90 +186,34 @@ public class DatabaseResourceService extends AbstractService { /** * Load DB specific resources */ - public void loadResources() { + private void loadResources() { for (String db : dbResEntryMap.keySet()) { - try { - createClassLoader(db); - loadDBJars(db, dbResEntryMap.get(db), true); - log.info("Loaded resources for db {} resources: {}", db, dbResEntryMap.get(db)); - } catch (LensException exc) { - incrCounter(LOAD_RESOURCES_ERRORS); - log.warn("Failed to load resources for db {}", db, exc); - classLoaderCache.remove(db); - } + loadDBJars(db, dbResEntryMap.get(db)); + log.info("Loaded resources for db {} resources: {}", db, dbResEntryMap.get(db)); } } - protected void createClassLoader(String database) throws LensException { - classLoaderCache.put(database, this.getClass().getClassLoader()); - } - /** * Add a resource to the specified database. Update class loader of the database if required. * @param database database name * @param resources resources which need to be added to the database - * @param useUri if set to true, use URI from resourceEntry to load in classLoader and update class loader of the - * database in the class loader cache - * @return class loader updated as a result of adding any JARs */ - private synchronized ClassLoader loadDBJars(String database, Collection<LensSessionImpl.ResourceEntry> resources, - boolean useUri) { - ClassLoader classLoader = classLoaderCache.get(database); - if (classLoader == null) { - // No change since there are no static resources to be added - return null; - } - - if (resources == null || resources.isEmpty()) { - // Return DB class loader directly since no resources have to be merged. - return classLoader; - } - - // Get URLs of the class loader - if (classLoader instanceof URLClassLoader) { - URLClassLoader urlLoader = (URLClassLoader) classLoader; - URL[] preUrls = urlLoader.getURLs(); - - // Add to set to remove duplicate additions - Set<URL> newUrls = new LinkedHashSet<>(); - // New class loader = URLs of DB jars + argument jars - Collections.addAll(newUrls, preUrls); - + private synchronized void loadDBJars(String database, Collection<LensSessionImpl.ResourceEntry> resources) { + URL[] urls = new URL[0]; + if (resources != null) { + urls = new URL[resources.size()]; + int i = 0; for (LensSessionImpl.ResourceEntry res : resources) { try { - if (useUri) { - newUrls.add(new URL(res.getUri())); - } else { - newUrls.add(new URL(res.getLocation())); - } + urls[i++] = new URL(res.getUri()); } catch (MalformedURLException e) { incrCounter(LOAD_RESOURCES_ERRORS); log.error("Invalid URL {} with location: {} adding to db {}", res.getUri(), res.getLocation(), database, e); } } - - URLClassLoader newClassLoader = new URLClassLoader(newUrls.toArray(new URL[newUrls.size()]), - DatabaseResourceService.class.getClassLoader()); - if (useUri) { - classLoaderCache.put(database, newClassLoader); - } - - return newClassLoader; - } else { - log.warn("Only URL class loader supported"); - return Thread.currentThread().getContextClassLoader(); } - } - - /** - * Add a resource to the specified database, return class loader with resources added. - * This call does not update the class loader cache - * @param database database name - * @param resources resources which need to be added to the database - * @return class loader updated as a result of adding any JARs - */ - protected ClassLoader loadDBJars(String database, Collection<LensSessionImpl.ResourceEntry> resources) { - return loadDBJars(database, resources, false); + classLoaderCache.put(database, + new UncloseableClassLoader(urls, getClass().getClassLoader())); } @@ -288,9 +221,8 @@ public class DatabaseResourceService extends AbstractService { * Get class loader of a database added with database specific jars * @param database database * @return class loader from cache of classloaders for each db - * @throws LensException */ - protected ClassLoader getClassLoader(String database) throws LensException { + protected ClassLoader getClassLoader(String database) { return classLoaderCache.get(database); } http://git-wip-us.apache.org/repos/asf/lens/blob/df75a4b8/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java ---------------------------------------------------------------------- diff --git a/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java b/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java index 2b84d3a..04812a6 100644 --- a/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java +++ b/lens-server/src/main/java/org/apache/lens/server/session/LensSessionImpl.java @@ -22,6 +22,8 @@ import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; +import java.net.MalformedURLException; +import java.net.URL; import java.util.*; import java.util.concurrent.atomic.AtomicInteger; @@ -88,19 +90,19 @@ public class LensSessionImpl extends HiveSessionImpl { * List of queries which are submitted in this session. */ @Getter - private List<QueryHandle> activeQueries = new ArrayList<QueryHandle>(); + private final List<QueryHandle> activeQueries = new ArrayList<>(); /** * Keep track of DB static resources which failed to be added to this session */ - private final Map<String, List<ResourceEntry>> failedDBResources = new HashMap<String, List<ResourceEntry>>(); + private final Map<String, List<ResourceEntry>> failedDBResources = new HashMap<>(); /** * Cache of database specific class loaders for this session * This is updated lazily on add/remove resource calls and switch database calls. */ - private final Map<String, ClassLoader> sessionDbClassLoaders = new HashMap<String, ClassLoader>(); + private final Map<String, SessionClassLoader> sessionDbClassLoaders = new HashMap<>(); @Setter(AccessLevel.PROTECTED) private DatabaseResourceService dbResService; @@ -137,9 +139,7 @@ public class LensSessionImpl extends HiveSessionImpl { conf.addResource("lenssession-default.xml"); conf.addResource("lens-site.xml"); sessionDefaultConfig = new Configuration(false); - Iterator<Map.Entry<String, String>> confItr = conf.iterator(); - while (confItr.hasNext()) { - Map.Entry<String, String> prop = confItr.next(); + for (Map.Entry<String, String> prop : conf) { if (!prop.getKey().startsWith(LensConfConstants.SERVER_PFX)) { sessionDefaultConfig.set(prop.getKey(), prop.getValue()); } @@ -202,17 +202,20 @@ public class LensSessionImpl extends HiveSessionImpl { @Override public void close() throws HiveSQLException { + ClassLoader nonDBClassLoader = getSessionState().getConf().getClassLoader(); super.close(); - // Release class loader resources + JavaUtils.closeClassLoadersTo(nonDBClassLoader, getClass().getClassLoader()); synchronized (sessionDbClassLoaders) { - for (Map.Entry<String, ClassLoader> entry : sessionDbClassLoaders.entrySet()) { + for (Map.Entry<String, SessionClassLoader> entry : sessionDbClassLoaders.entrySet()) { try { - // Close the class loader only if its not a class loader maintained by the DB service - if (entry.getValue() != getDbResService().getClassLoader(entry.getKey())) { - // This is a utility in hive-common - JavaUtils.closeClassLoader(entry.getValue()); - } + // Closing session level classloaders up untill the db class loader if present, or null. + // When db class loader is null, the class loader in the session is a single class loader + // which stays as it is on database switch -- provided the new db doesn't have db jars. + // The following line will close class loaders made on top of db class loaders and will close + // only one classloader without closing the parents. In case of no db class loader, the session + // classloader will already have been closed by either super.close() or before this for loop. + JavaUtils.closeClassLoadersTo(entry.getValue(), getDbResService().getClassLoader(entry.getKey())); } catch (Exception e) { log.error("Error closing session classloader for session: {}", getSessionHandle().getSessionId(), e); } @@ -242,7 +245,11 @@ public class LensSessionImpl extends HiveSessionImpl { * @see org.apache.hive.service.cli.session.HiveSessionImpl#acquire() */ public synchronized void acquire() { - super.acquire(true); + this.acquire(true); + } + @Override + public synchronized void acquire(boolean userAccess) { + super.acquire(userAccess); acquireCount.incrementAndGet(); // Update thread's class loader with current DBs class loader ClassLoader classLoader = getClassLoader(getCurrentDatabase()); @@ -256,9 +263,13 @@ public class LensSessionImpl extends HiveSessionImpl { * @see org.apache.hive.service.cli.session.HiveSessionImpl#release() */ public synchronized void release() { + this.release(true); + } + @Override + public synchronized void release(boolean userAccess) { lastAccessTime = System.currentTimeMillis(); if (acquireCount.decrementAndGet() == 0) { - super.release(true); + super.release(userAccess); } } @@ -290,7 +301,8 @@ public class LensSessionImpl extends HiveSessionImpl { itr.remove(); } } - updateSessionDbClassLoader(getSessionState().getCurrentDatabase()); + // New classloaders will be created. Remove resource is expensive, add resource is cheap. + updateAllSessionClassLoaders(); } /** @@ -303,10 +315,9 @@ public class LensSessionImpl extends HiveSessionImpl { public void addResource(String type, String path, String finalLocation) { ResourceEntry resource = new ResourceEntry(type, path, finalLocation); persistInfo.getResources().add(resource); - synchronized (sessionDbClassLoaders) { - // Update all DB class loaders - updateSessionDbClassLoader(getSessionState().getCurrentDatabase()); - } + // The following call updates the existing classloaders without creating new instances. + // Add resource is cheap :) + addResourceToAllSessionClassLoaders(resource); } protected List<ResourceEntry> getResources() { @@ -320,19 +331,71 @@ public class LensSessionImpl extends HiveSessionImpl { public void setCurrentDatabase(String currentDatabase) { persistInfo.setDatabase(currentDatabase); getSessionState().setCurrentDatabase(currentDatabase); - // Merge if resources are added + // Make sure entry is there in classloader cache synchronized (sessionDbClassLoaders) { updateSessionDbClassLoader(currentDatabase); } } + private SessionClassLoader getUpdatedSessionClassLoader(String database) { + ClassLoader dbClassLoader = getDbResService().getClassLoader(database); + if (dbClassLoader == null) { + return null; + } + URL[] urls = new URL[0]; + if (persistInfo.getResources() != null) { + int i = 0; + urls = new URL[persistInfo.getResources().size()]; + for (LensSessionImpl.ResourceEntry res : persistInfo.getResources()) { + try { + urls[i++] = new URL(res.getUri()); + } catch (MalformedURLException e) { + log.error("Invalid URL {} with location: {} adding to db {}", res.getUri(), res.getLocation(), database, e); + } + } + } + if (sessionDbClassLoaders.containsKey(database) + && Arrays.equals(sessionDbClassLoaders.get(database).getURLs(), urls)) { + return sessionDbClassLoaders.get(database); + } + return new SessionClassLoader(urls, dbClassLoader); + } + private void updateSessionDbClassLoader(String database) { - ClassLoader updatedClassLoader = getDbResService().loadDBJars(database, persistInfo.getResources()); + SessionClassLoader updatedClassLoader = getUpdatedSessionClassLoader(database); if (updatedClassLoader != null) { sessionDbClassLoaders.put(database, updatedClassLoader); } } + private void updateAllSessionClassLoaders() { + synchronized (sessionDbClassLoaders) { + // Update all DB class loaders + for (String database: sessionDbClassLoaders.keySet()) { + updateSessionDbClassLoader(database); + } + } + } + + private void addResourceToClassLoader(String database, ResourceEntry res) { + if (sessionDbClassLoaders.containsKey(database)) { + SessionClassLoader sessionClassLoader = sessionDbClassLoaders.get(database); + try { + sessionClassLoader.addURL(new URL(res.getLocation())); + } catch (MalformedURLException e) { + log.error("Invalid URL {} with location: {} adding to db {}", res.getUri(), res.getLocation(), database, e); + } + } + } + private void addResourceToAllSessionClassLoaders(ResourceEntry res) { + synchronized (sessionDbClassLoaders) { + // Update all DB class loaders + for (String database: sessionDbClassLoaders.keySet()) { + addResourceToClassLoader(database, res); + } + } + } + private boolean areResourcesAdded() { return persistInfo.getResources() != null && !persistInfo.getResources().isEmpty(); } @@ -351,25 +414,18 @@ public class LensSessionImpl extends HiveSessionImpl { if (sessionDbClassLoaders.containsKey(database)) { return sessionDbClassLoaders.get(database); } else { - try { - ClassLoader classLoader = getDbResService().getClassLoader(database); - if (classLoader == null) { - log.debug("DB resource service gave null class loader for {}", database); - } else { - if (areResourcesAdded()) { - log.debug("adding resources for {}", database); - // We need to update DB specific classloader with added resources - updateSessionDbClassLoader(database); - classLoader = sessionDbClassLoaders.get(database); - } + ClassLoader classLoader = getDbResService().getClassLoader(database); + if (classLoader == null) { + log.debug("DB resource service gave null class loader for {}", database); + } else { + if (areResourcesAdded()) { + log.debug("adding resources for {}", database); + // We need to update DB specific classloader with added resources + updateSessionDbClassLoader(database); + classLoader = sessionDbClassLoaders.get(database); } - return classLoader == null ? getSessionState().getConf().getClassLoader() : classLoader; - - } catch (LensException e) { - log.error("Error getting classloader for database {} for session {} " - + " defaulting to session state class loader", database, getSessionHandle().getSessionId(), e); - return getSessionState().getConf().getClassLoader(); } + return classLoader == null ? getSessionState().getConf().getClassLoader() : classLoader; } } } @@ -410,13 +466,13 @@ public class LensSessionImpl extends HiveSessionImpl { /** * Return resources which are added statically to the database - * @return + * @return DatabaseResources */ public Collection<ResourceEntry> getDBResources(String database) { synchronized (failedDBResources) { List<ResourceEntry> failed = failedDBResources.get(database); if (failed == null && getDbResService().getResourcesForDatabase(database) != null) { - failed = new ArrayList<ResourceEntry>(getDbResService().getResourcesForDatabase(database)); + failed = new ArrayList<>(getDbResService().getResourcesForDatabase(database)); failedDBResources.put(database, failed); } return failed; @@ -428,7 +484,7 @@ public class LensSessionImpl extends HiveSessionImpl { * Get session's resources which have to be added for the given database */ public Collection<ResourceEntry> getPendingSessionResourcesForDatabase(String database) { - List<ResourceEntry> pendingResources = new ArrayList<ResourceEntry>(); + List<ResourceEntry> pendingResources = new ArrayList<>(); for (ResourceEntry res : persistInfo.getResources()) { if (!res.isAddedToDatabase(database)) { pendingResources.add(res); @@ -439,7 +495,7 @@ public class LensSessionImpl extends HiveSessionImpl { /** * Get effective class loader for this session - * @return + * @return current classloader */ public ClassLoader getClassLoader() { return getClassLoader(getCurrentDatabase()); @@ -503,7 +559,7 @@ public class LensSessionImpl extends HiveSessionImpl { /** * Returns the value of restoreCount for the resource - * @return + * @return restore count */ public int getRestoreCount() { return restoreCount.get(); @@ -541,10 +597,10 @@ public class LensSessionImpl extends HiveSessionImpl { public static class LensSessionPersistInfo implements Externalizable { /** The resources. */ - private List<ResourceEntry> resources = new ArrayList<ResourceEntry>(); + private List<ResourceEntry> resources = new ArrayList<>(); /** The config. */ - private Map<String, String> config = new HashMap<String, String>(); + private Map<String, String> config = new HashMap<>(); /** The session handle. */ private LensSessionHandle sessionHandle; http://git-wip-us.apache.org/repos/asf/lens/blob/df75a4b8/lens-server/src/main/java/org/apache/lens/server/session/SessionClassLoader.java ---------------------------------------------------------------------- diff --git a/lens-server/src/main/java/org/apache/lens/server/session/SessionClassLoader.java b/lens-server/src/main/java/org/apache/lens/server/session/SessionClassLoader.java new file mode 100644 index 0000000..f5e2068 --- /dev/null +++ b/lens-server/src/main/java/org/apache/lens/server/session/SessionClassLoader.java @@ -0,0 +1,55 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.lens.server.session; + +import java.io.IOException; +import java.net.URL; +import java.net.URLClassLoader; + +import lombok.EqualsAndHashCode; +import lombok.Getter; +import lombok.Setter; +import lombok.extern.slf4j.Slf4j; + +@Slf4j +@EqualsAndHashCode(callSuper = false) +public class SessionClassLoader extends URLClassLoader { + @Getter + @Setter + private boolean closed; + + public SessionClassLoader(URL[] urls) { + super(urls); + } + + public SessionClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + public void close() throws IOException { + setClosed(true); + super.close(); + } + + @Override + public void addURL(URL url) { + super.addURL(url); + } +} http://git-wip-us.apache.org/repos/asf/lens/blob/df75a4b8/lens-server/src/main/java/org/apache/lens/server/session/UncloseableClassLoader.java ---------------------------------------------------------------------- diff --git a/lens-server/src/main/java/org/apache/lens/server/session/UncloseableClassLoader.java b/lens-server/src/main/java/org/apache/lens/server/session/UncloseableClassLoader.java new file mode 100644 index 0000000..c350bcb --- /dev/null +++ b/lens-server/src/main/java/org/apache/lens/server/session/UncloseableClassLoader.java @@ -0,0 +1,41 @@ +/** + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.apache.lens.server.session; + +import java.io.IOException; +import java.net.URL; + +import lombok.extern.slf4j.Slf4j; + +@Slf4j +public class UncloseableClassLoader extends SessionClassLoader { + + public UncloseableClassLoader(URL[] urls) { + super(urls); + } + + public UncloseableClassLoader(URL[] urls, ClassLoader parent) { + super(urls, parent); + } + + @Override + public void close() throws IOException { + log.debug("Not closing db class loader"); + } +} http://git-wip-us.apache.org/repos/asf/lens/blob/df75a4b8/lens-server/src/test/java/org/apache/lens/server/session/TestSessionClassLoaders.java ---------------------------------------------------------------------- diff --git a/lens-server/src/test/java/org/apache/lens/server/session/TestSessionClassLoaders.java b/lens-server/src/test/java/org/apache/lens/server/session/TestSessionClassLoaders.java index eeb97f2..d66de4c 100644 --- a/lens-server/src/test/java/org/apache/lens/server/session/TestSessionClassLoaders.java +++ b/lens-server/src/test/java/org/apache/lens/server/session/TestSessionClassLoaders.java @@ -19,7 +19,10 @@ package org.apache.lens.server.session; +import static org.testng.Assert.*; + import java.io.File; +import java.net.URI; import java.net.URLClassLoader; import java.util.HashMap; @@ -27,13 +30,15 @@ import org.apache.lens.api.LensSessionHandle; import org.apache.lens.server.LensServerConf; import org.apache.lens.server.LensServerTestUtil; import org.apache.lens.server.api.LensConfConstants; +import org.apache.lens.server.api.error.LensException; import org.apache.lens.server.user.UserConfigLoaderFactory; import org.apache.hadoop.hive.conf.HiveConf; +import org.apache.hadoop.hive.ql.exec.UDFClassLoader; import org.apache.hadoop.hive.ql.metadata.Hive; import org.apache.hive.service.cli.CLIService; +import org.apache.hive.service.cli.HiveSQLException; -import org.testng.Assert; import org.testng.annotations.AfterClass; import org.testng.annotations.BeforeClass; import org.testng.annotations.Test; @@ -79,6 +84,7 @@ public class TestSessionClassLoaders { public void tearDown() throws Exception { Hive hive = Hive.get(conf); hive.dropDatabase(DB1, true, true); + sessionService.stop(); } @@ -99,7 +105,7 @@ public class TestSessionClassLoaders { Class clz = Class.forName(TestDatabaseResourceService.TEST_CLASS, true, Thread.currentThread().getContextClassLoader()); // Expected to fail - Assert.fail("Should not reach here as default db doesn't have jar loaded"); + fail("Should not reach here as default db doesn't have jar loaded"); } catch (ClassNotFoundException cnf) { // Pass } finally { @@ -112,7 +118,7 @@ public class TestSessionClassLoaders { sessionService.acquire(sessionHandle); Class clz = session.getCubeMetastoreClient().getConf().getClassByName(TestDatabaseResourceService.TEST_CLASS); // Expected to fail - Assert.fail("Should not reach here as default db doesn't have jar loaded"); + fail("Should not reach here as default db doesn't have jar loaded"); } catch (ClassNotFoundException cnf) { // Pass } finally { @@ -127,17 +133,17 @@ public class TestSessionClassLoaders { sessionService.acquire(sessionHandle); ClassLoader thClassLoader = Thread.currentThread().getContextClassLoader(); - Assert.assertTrue(thClassLoader == session.getClassLoader(DB1)); + assertTrue(thClassLoader == session.getClassLoader(DB1)); Class clz = Class.forName(TestDatabaseResourceService.TEST_CLASS, true, thClassLoader); - Assert.assertNotNull(clz); + assertNotNull(clz); // test cube metastore client's configuration's classloader clz = null; clz = session.getCubeMetastoreClient().getConf().getClassByName(TestDatabaseResourceService.TEST_CLASS); - Assert.assertNotNull(clz); + assertNotNull(clz); } catch (ClassNotFoundException cnf) { log.error(cnf.getMessage(), cnf); - Assert.fail("Should not have thrown class not found exception: " + cnf.getMessage()); + fail("Should not have thrown class not found exception: " + cnf.getMessage()); } finally { sessionService.release(sessionHandle); } @@ -169,7 +175,7 @@ public class TestSessionClassLoaders { sessionService.acquire(sessionHandle); ClassLoader dbClassLoader = session.getClassLoader("default"); - Assert.assertTrue(Thread.currentThread().getContextClassLoader() == dbClassLoader); + assertTrue(Thread.currentThread().getContextClassLoader() == dbClassLoader); // testClass2 should be loaded since test2.jar is added to the session Class testClass2 = dbClassLoader.loadClass("ClassLoaderTestClass2"); @@ -181,8 +187,8 @@ public class TestSessionClassLoaders { loadedDBClass = true; } catch (ClassNotFoundException cnf) { log.error(cnf.getMessage(), cnf); - Assert.assertTrue(loadedSessionClass); - Assert.assertFalse(loadedDBClass); + assertTrue(loadedSessionClass); + assertFalse(loadedDBClass); } finally { sessionService.release(sessionHandle); } @@ -204,8 +210,8 @@ public class TestSessionClassLoaders { loadedDBClass = true; } catch (ClassNotFoundException cnf) { log.error(cnf.getMessage(), cnf); - Assert.assertTrue(loadedSessionClass); - Assert.assertFalse(loadedDBClass); + assertTrue(loadedSessionClass); + assertFalse(loadedDBClass); } finally { sessionService.release(sessionHandle); } @@ -217,7 +223,7 @@ public class TestSessionClassLoaders { try { sessionService.acquire(sessionHandle); // testClass2 should be loaded since test2.jar is added to the session - URLClassLoader urlClassLoader = (URLClassLoader) Thread.currentThread().getContextClassLoader(); + URLClassLoader urlClassLoader = (SessionClassLoader) Thread.currentThread().getContextClassLoader(); Class testClass2 = Class.forName("ClassLoaderTestClass2", true, Thread.currentThread().getContextClassLoader()); // class inside 'test.jar' should also load since its added to DB1 loadedSessionClass = true; @@ -226,8 +232,8 @@ public class TestSessionClassLoaders { } finally { sessionService.release(sessionHandle); } - Assert.assertTrue(loadedSessionClass); - Assert.assertTrue(loadedDBClass); + assertTrue(loadedSessionClass); + assertTrue(loadedDBClass); log.info("@@@ TEST 2 - cube client"); loadedSessionClass = false; @@ -243,8 +249,8 @@ public class TestSessionClassLoaders { } finally { sessionService.release(sessionHandle); } - Assert.assertTrue(loadedSessionClass); - Assert.assertTrue(loadedDBClass); + assertTrue(loadedSessionClass); + assertTrue(loadedDBClass); // Switch back to default DB, again the test2.jar should be available, test.jar should not be available log.info("@@@ TEST 3"); @@ -260,8 +266,8 @@ public class TestSessionClassLoaders { Class clz = Class.forName("ClassLoaderTestClass", true, Thread.currentThread().getContextClassLoader()); loadedDBClass = true; } catch (ClassNotFoundException cnf) { - Assert.assertTrue(loadedSessionClass); - Assert.assertFalse(loadedDBClass); + assertTrue(loadedSessionClass); + assertFalse(loadedDBClass); } finally { sessionService.release(sessionHandle); } @@ -282,8 +288,8 @@ public class TestSessionClassLoaders { loadedDBClass = true; } catch (ClassNotFoundException cnf) { log.error(cnf.getMessage(), cnf); - Assert.assertTrue(loadedSessionClass); - Assert.assertFalse(loadedDBClass); + assertTrue(loadedSessionClass); + assertFalse(loadedDBClass); } finally { sessionService.release(sessionHandle); } @@ -291,4 +297,136 @@ public class TestSessionClassLoaders { sessionService.closeSession(sessionHandle); } + private void assertDBClassNotLoading(ClassLoader classLoader) { + try { + classLoader.loadClass("DatabaseJarSerde"); + fail("Shouldn't be able to load DatabaseJarSerde.class"); + } catch (ClassNotFoundException e) { + log.debug("no issues"); + } + } + + private void assertDBClassLoading(ClassLoader classLoader) throws ClassNotFoundException { + classLoader.loadClass("DatabaseJarSerde"); + } + + @Test + public void testClassloaderClose() throws LensException, HiveSQLException, ClassNotFoundException { + String parentResource = "org/apache/hadoop/conf/Configuration.class"; + String db1Resource = "DatabaseJarSerde.class"; + LensSessionHandle sessionHandle1 = sessionService.openSession("foo", "bar", new HashMap<String, String>()); + LensSessionImpl session1 = sessionService.getSession(sessionHandle1); + session1.setDbResService(sessionService.getDatabaseResourceService()); + session1.setCurrentDatabase(DB1); + LensSessionHandle sessionHandle2 = sessionService.openSession("foo", "bar", new HashMap<String, String>()); + LensSessionImpl session2 = sessionService.getSession(sessionHandle2); + session2.setDbResService(sessionService.getDatabaseResourceService()); + session2.setCurrentDatabase(DB1); + SessionClassLoader session1ClassLoader1 = (SessionClassLoader) session1.getClassLoader(); + SessionClassLoader classLoader2 = (SessionClassLoader) session2.getClassLoader(); + // classloader logically same, but instance different + assertEquals(session1ClassLoader1, classLoader2); + assertFalse(session1ClassLoader1 == classLoader2); + assertEquals(session1ClassLoader1.getParent(), classLoader2.getParent()); + assertDBClassLoading(session1ClassLoader1); + session1.setCurrentDatabase("default"); + // Default has no parent class loader, so + UDFClassLoader session1ClassLoader2 = (UDFClassLoader) session1.getClassLoader(); + assertNotEquals(session1ClassLoader2, session1ClassLoader1); // obviously, since even types are different + assertNotEquals(session1ClassLoader1.getParent(), session1ClassLoader2.getParent()); + assertFalse(session1ClassLoader1.isClosed()); + assertFalse(session1ClassLoader2.isClosed()); + assertDBClassNotLoading(session1ClassLoader2); + sessionService.closeSession(sessionHandle1); + // both classloaders got closed, but parent classloader not closed. + assertTrue(session1ClassLoader1.isClosed()); + assertTrue(session1ClassLoader2.isClosed()); + assertFalse(((UncloseableClassLoader) session1ClassLoader1.getParent()).isClosed()); + assertNotNull(session1ClassLoader2.getResource(parentResource)); + // session 1 classloader still able to load db1 jar resources + assertNotNull(session1ClassLoader1.getResource(db1Resource)); + // didn't affect classloaders of another session. + assertNotNull(classLoader2.getResource(parentResource)); + sessionService.closeSession(sessionHandle2); + assertTrue(classLoader2.isClosed()); + LensSessionHandle sessionHandle3 = sessionService.openSession("foo", "bar", new HashMap<String, String>()); + LensSessionImpl session3 = sessionService.getSession(sessionHandle3); + session3.setDbResService(sessionService.getDatabaseResourceService()); + session3.setCurrentDatabase("default"); + UDFClassLoader session3ClassLoader1 = (UDFClassLoader) session3.getClassLoader(); + assertFalse(session3ClassLoader1.isClosed()); + assertDBClassNotLoading(session3ClassLoader1); + session3.setCurrentDatabase(DB1); + SessionClassLoader session3ClassLoader2 = (SessionClassLoader) session3.getClassLoader(); + assertFalse(session3ClassLoader2.isClosed()); + assertDBClassLoading(session3ClassLoader2); + // session classloader is different in case of different sessions. + assertNotEquals(session3ClassLoader1, session1ClassLoader2); + // both are instances of UDFClassLoader, with same number of urls + assertEquals(session3ClassLoader1.getClass(), session1ClassLoader2.getClass()); + assertEquals(session1ClassLoader2.getURLs().length, session3ClassLoader1.getURLs().length); + // without adding any jars, classloaders for different sessions using same database have same + // parent and no extra urls added + assertEquals(session3ClassLoader2.getParent(), session1ClassLoader1.getParent()); + assertEquals(session3ClassLoader2.getURLs().length, session1ClassLoader1.getURLs().length); + + session3.setCurrentDatabase("dummy1"); + UDFClassLoader session3ClassLoader3 = (UDFClassLoader) session3.getClassLoader(); + session3.setCurrentDatabase("dummy2"); + UDFClassLoader session3ClassLoader4 = (UDFClassLoader) session3.getClassLoader(); + assertDBClassNotLoading(session3ClassLoader3); + assertDBClassNotLoading(session3ClassLoader4); + assertEquals(session3ClassLoader3, session3ClassLoader4); + sessionService.closeSession(sessionHandle3); + assertTrue(session3ClassLoader1.isClosed()); + assertTrue(session3ClassLoader2.isClosed()); + assertTrue(session3ClassLoader3.isClosed()); + assertTrue(session3ClassLoader4.isClosed()); + } + + private void assertExtraClassNotLoading(ClassLoader classLoader) { + try { + classLoader.loadClass("ClassLoaderTestClass2"); + // Class may be already loaded due to cache. + assertNull(classLoader.getResource("ClassLoaderTestClass2.class")); + } catch (ClassNotFoundException e) { + log.debug("no issues"); + } + } + + private void assertExtraClassLoading(ClassLoader classLoader) throws ClassNotFoundException { + classLoader.loadClass("ClassLoaderTestClass2"); + assertNotNull("ClassLoaderTestClass2.class"); + } + + @Test + public void testSessionClassLoaderCloseWhenExtraJars() throws Exception { + URI resource = new File("target/testjars/test2.jar").toURI(); + LensSessionHandle sessionHandle1 = sessionService.openSession("foo", "bar", new HashMap<String, String>()); + LensSessionImpl session1 = sessionService.getSession(sessionHandle1); + session1.setDbResService(sessionService.getDatabaseResourceService()); + session1.setCurrentDatabase(DB1); + sessionService.addResource(sessionHandle1, "jar", resource.toString()); + SessionClassLoader loader1 = (SessionClassLoader) session1.getClassLoader(); + assertExtraClassLoading(loader1); + assertExtraClassNotLoading(loader1.getParent()); + assertEquals(loader1.getURLs()[0].toURI(), resource); + // change db to a db which doesn't have db jars. and add resources there. + + session1.setCurrentDatabase("default"); + // Extra jar is still there. + assertExtraClassLoading(session1.getClassLoader()); + UDFClassLoader loader2 = (UDFClassLoader) session1.getClassLoader(); + + // Close and assert on all class loaders. + session1.setCurrentDatabase(DB1); + assertEquals(session1.getClassLoader(), loader1); + sessionService.closeSession(sessionHandle1); + assertTrue(loader1.isClosed()); + assertTrue(loader2.isClosed()); + assertExtraClassNotLoading(loader1); + assertExtraClassNotLoading(loader2); + assertFalse(((UncloseableClassLoader) loader1.getParent()).isClosed()); + assertExtraClassNotLoading(loader1.getParent()); + } }