From a1a430d2ebb3fcad1c816a57214bb057e5ad1560 Mon Sep 17 00:00:00 2001
From: Evgeny Mandrikov
Date: Thu, 2 Jun 2011 12:32:21 +0400
Subject: Fix violations
---
.../src/main/java/org/sonar/batch/components/PastSnapshotFinder.java | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
(limited to 'sonar-batch/src/main')
diff --git a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
index 4962be2b8e6..dd59bb2009d 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/components/PastSnapshotFinder.java
@@ -63,8 +63,8 @@ public class PastSnapshotFinder implements BatchExtension {
case 1: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_1; break;
case 2: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_2; break;
case 3: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_3; break;
- case 4: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4; break;
- case 5: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5; break;// NOSONAR false-positive: constant 5 is the same than 4 (empty string)
+ case 4: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_4; break; // NOSONAR false-positive: constant 4 is the same than 5 (empty string)
+ case 5: defaultValue = CoreProperties.TIMEMACHINE_DEFAULT_PERIOD_5; break; // NOSONAR false-positive: constant 5 is the same than 4 (empty string)
}
return conf.getString(CoreProperties.TIMEMACHINE_PERIOD_PREFIX + index, defaultValue);
}
--
cgit v1.2.3
From 2233993388ae63625d926099903a9b697a062409 Mon Sep 17 00:00:00 2001
From: Evgeny Mandrikov
Date: Tue, 7 Jun 2011 02:16:39 +0400
Subject: SONAR-2495 Correctly deregister JDBC Driver to prevent memory leaks
DriverDatabaseConnector should register only one instance of
DriverProxy in DriverManager and also should perform deregistration.
But this is not enough to prevent memory leaks, so class loader for
JDBC Driver should perform additional efforts for deregistration.
---
.../batch/bootstrap/BootstrapClassLoader.java | 53 ---------
.../org/sonar/batch/bootstrap/BootstrapModule.java | 4 +-
.../sonar/batch/bootstrap/JdbcDriverHolder.java | 132 +++++++++++++++++++++
.../sonar/batch/bootstrap/JdbcLeakPrevention.java | 64 ++++++++++
.../batch/bootstrap/BootstrapClassLoaderTest.java | 2 +-
.../sonar/jpa/session/DriverDatabaseConnector.java | 59 ++++++---
6 files changed, 244 insertions(+), 70 deletions(-)
delete mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java
create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
create mode 100644 sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java
(limited to 'sonar-batch/src/main')
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java
deleted file mode 100644
index ca00f9a0691..00000000000
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapClassLoader.java
+++ /dev/null
@@ -1,53 +0,0 @@
-/*
- * Sonar, open source software quality management tool.
- * Copyright (C) 2008-2011 SonarSource
- * mailto:contact AT sonarsource DOT com
- *
- * Sonar is free software; you can redistribute it and/or
- * modify it under the terms of the GNU Lesser General Public
- * License as published by the Free Software Foundation; either
- * version 3 of the License, or (at your option) any later version.
- *
- * Sonar is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
- * Lesser General Public License for more details.
- *
- * You should have received a copy of the GNU Lesser General Public
- * License along with Sonar; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
- */
-package org.sonar.batch.bootstrap;
-
-import org.sonar.api.utils.SonarException;
-
-import java.io.File;
-import java.net.MalformedURLException;
-import java.net.URL;
-import java.net.URLClassLoader;
-
-/**
- * ClassLoader extended with the JDBC Driver hosted on the server-side.
- */
-public class BootstrapClassLoader {
-
- private URLClassLoader classLoader;
-
- public BootstrapClassLoader(ArtifactDownloader extensionDownloader) {
- this(extensionDownloader.downloadJdbcDriver());
- }
-
- BootstrapClassLoader(File jdbcDriver) {
- try {
- ClassLoader parentClassLoader = BootstrapClassLoader.class.getClassLoader();
- classLoader = URLClassLoader.newInstance(new URL[]{jdbcDriver.toURI().toURL()}, parentClassLoader);
-
- } catch (MalformedURLException e) {
- throw new SonarException("Fail to get URL of : " + jdbcDriver.getAbsolutePath(), e);
- }
- }
-
- public URLClassLoader getClassLoader() {
- return classLoader;
- }
-}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
index fda540156a8..3127e7a3e50 100644
--- a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/BootstrapModule.java
@@ -56,9 +56,9 @@ public class BootstrapModule extends Module {
addComponent(TempDirectories.class);// registered here because used by BootstrapClassLoader
addComponent(HttpDownloader.class);// registered here because used by BootstrapClassLoader
addComponent(ArtifactDownloader.class);// registered here because used by BootstrapClassLoader
- addComponent(BootstrapClassLoader.class);
+ addComponent(JdbcDriverHolder.class);
- URLClassLoader bootstrapClassLoader = getComponent(BootstrapClassLoader.class).getClassLoader();
+ URLClassLoader bootstrapClassLoader = getComponent(JdbcDriverHolder.class).getClassLoader();
// set as the current context classloader for hibernate, else it does not find the JDBC driver.
Thread.currentThread().setContextClassLoader(bootstrapClassLoader);
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
new file mode 100644
index 00000000000..427ac20e3ab
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcDriverHolder.java
@@ -0,0 +1,132 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.batch.bootstrap;
+
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.sonar.api.utils.SonarException;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.MalformedURLException;
+import java.net.URL;
+import java.net.URLClassLoader;
+import java.util.List;
+
+/**
+ * Contains and provides class loader extended with the JDBC Driver hosted on the server-side.
+ */
+public class JdbcDriverHolder {
+
+ private static Logger LOG = LoggerFactory.getLogger(JdbcDriverHolder.class);
+ private JdbcDriverClassLoader classLoader;
+
+ public JdbcDriverHolder(ArtifactDownloader extensionDownloader) {
+ this(extensionDownloader.downloadJdbcDriver());
+ }
+
+ JdbcDriverHolder(File jdbcDriver) {
+ try {
+ ClassLoader parentClassLoader = JdbcDriverHolder.class.getClassLoader();
+ classLoader = new JdbcDriverClassLoader(jdbcDriver.toURI().toURL(), parentClassLoader);
+
+ } catch (MalformedURLException e) {
+ throw new SonarException("Fail to get URL of : " + jdbcDriver.getAbsolutePath(), e);
+ }
+ }
+
+ public URLClassLoader getClassLoader() {
+ return classLoader;
+ }
+
+ /**
+ * This method automatically invoked by PicoContainer and deregisters JDBC drivers, which were forgotten.
+ *
+ * Dynamically loaded JDBC drivers can not be simply used and this is a well known problem of {@link java.sql.DriverManager},
+ * so workaround is to use proxy.
+ * However DriverManager also contains memory leak, thus not only proxy, but also original driver must be deregistered,
+ * otherwise our class loader would be kept in memory.
+ *
+ *
+ * This operation contains unnecessary complexity because:
+ *
+ * - DriverManager checks the class loader of the calling class. Thus we can't simply ask it about deregistration.
+ * - We can't use reflection against DriverManager, since it would create a dependency on DriverManager implementation,
+ * which can be changed (like it was done - compare Java 1.5 and 1.6).
+ * - So we use companion - {@link JdbcLeakPrevention}. But we can't just create an instance,
+ * since it will be loaded by parent class loader and again will not pass DriverManager's check.
+ * So, we load the bytes via our parent class loader, but define the class with this class loader
+ * thus JdbcLeakPrevention looks like our class to the DriverManager.
+ *
+ *
+ */
+ public void stop() {
+ classLoader.clearReferencesJdbc();
+ classLoader = null;
+ }
+
+ private static class JdbcDriverClassLoader extends URLClassLoader {
+
+ public JdbcDriverClassLoader(URL jdbcDriver, ClassLoader parent) {
+ super(new URL[] { jdbcDriver }, parent);
+ }
+
+ public void clearReferencesJdbc() {
+ InputStream is = getResourceAsStream("org/sonar/batch/bootstrap/JdbcLeakPrevention.class");
+ byte[] classBytes = new byte[2048];
+ int offset = 0;
+ try {
+ int read = is.read(classBytes, offset, classBytes.length - offset);
+ while (read > -1) {
+ offset += read;
+ if (offset == classBytes.length) {
+ // Buffer full - double size
+ byte[] tmp = new byte[classBytes.length * 2];
+ System.arraycopy(classBytes, 0, tmp, 0, classBytes.length);
+ classBytes = tmp;
+ }
+ read = is.read(classBytes, offset, classBytes.length - offset);
+ }
+
+ Class> lpClass = defineClass("org.sonar.batch.bootstrap.JdbcLeakPrevention", classBytes, 0, offset, this.getClass().getProtectionDomain());
+ Object obj = lpClass.newInstance();
+
+ @SuppressWarnings("unchecked")
+ List driverNames = (List) obj.getClass().getMethod("clearJdbcDriverRegistrations").invoke(obj);
+
+ for (String name : driverNames) {
+ LOG.debug("To prevent a memory leak, the JDBC Driver [{}] has been forcibly deregistered", name);
+ }
+ } catch (Exception e) {
+ LOG.warn("JDBC driver deregistration failed", e);
+ } finally {
+ if (is != null) {
+ try {
+ is.close();
+ } catch (IOException ioe) {
+ LOG.warn(ioe.getMessage(), ioe);
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java
new file mode 100644
index 00000000000..6da6514d45e
--- /dev/null
+++ b/sonar-batch/src/main/java/org/sonar/batch/bootstrap/JdbcLeakPrevention.java
@@ -0,0 +1,64 @@
+/*
+ * Sonar, open source software quality management tool.
+ * Copyright (C) 2008-2011 SonarSource
+ * mailto:contact AT sonarsource DOT com
+ *
+ * Sonar is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License as published by the Free Software Foundation; either
+ * version 3 of the License, or (at your option) any later version.
+ *
+ * Sonar is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
+ * Lesser General Public License for more details.
+ *
+ * You should have received a copy of the GNU Lesser General Public
+ * License along with Sonar; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02
+ */
+package org.sonar.batch.bootstrap;
+
+import java.sql.Driver;
+import java.sql.DriverManager;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Enumeration;
+import java.util.HashSet;
+import java.util.List;
+
+/**
+ * Companion of {@link JdbcDriverHolder} and allows it to deregister JDBC drivers.
+ *
+ * Some hacks are involved in the loading of the class - see {@link JdbcDriverHolder#stop()},
+ * so this class can refer to classes only from java.* package and must not be referred from other classes.
+ * Placement and naming of this class and methods are very important, since it loaded and invoked via reflection.
+ *
+ */
+public class JdbcLeakPrevention {
+
+ /**
+ * @return list of names of deregistered drivers
+ */
+ public List clearJdbcDriverRegistrations() throws SQLException {
+ List driverNames = new ArrayList();
+ HashSet originalDrivers = new HashSet();
+ Enumeration drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ originalDrivers.add(drivers.nextElement());
+ }
+ drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ if (driver.getClass().getClassLoader() != this.getClass().getClassLoader()) {
+ continue;
+ }
+ if (originalDrivers.contains(driver)) {
+ driverNames.add(driver.getClass().getCanonicalName());
+ }
+ DriverManager.deregisterDriver(driver);
+ }
+ return driverNames;
+ }
+
+}
diff --git a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java
index b28a4c33431..dfee29db761 100644
--- a/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java
+++ b/sonar-batch/src/test/java/org/sonar/batch/bootstrap/BootstrapClassLoaderTest.java
@@ -37,7 +37,7 @@ public class BootstrapClassLoaderTest {
assertNull(getClass().getClassLoader().getResource("foo/foo.txt"));
URL url = getClass().getResource("/org/sonar/batch/bootstrap/BootstrapClassLoaderTest/foo.jar");
- BootstrapClassLoader classloader = new BootstrapClassLoader(new File(url.toURI()));
+ JdbcDriverHolder classloader = new JdbcDriverHolder(new File(url.toURI()));
assertNotNull(classloader.getClassLoader());
assertNotNull(classloader.getClassLoader().getResource("foo/foo.txt"));
diff --git a/sonar-core/src/main/java/org/sonar/jpa/session/DriverDatabaseConnector.java b/sonar-core/src/main/java/org/sonar/jpa/session/DriverDatabaseConnector.java
index 0b11068040f..00e91fd8f38 100644
--- a/sonar-core/src/main/java/org/sonar/jpa/session/DriverDatabaseConnector.java
+++ b/sonar-core/src/main/java/org/sonar/jpa/session/DriverDatabaseConnector.java
@@ -27,11 +27,13 @@ import java.sql.Connection;
import java.sql.Driver;
import java.sql.DriverManager;
import java.sql.SQLException;
+import java.util.Enumeration;
import java.util.Properties;
public class DriverDatabaseConnector extends AbstractDatabaseConnector {
private ClassLoader classloader;
+ private boolean driverProxyRegistered = false;
public DriverDatabaseConnector(Configuration configuration) {
super(configuration, true);
@@ -74,23 +76,52 @@ public class DriverDatabaseConnector extends AbstractDatabaseConnector {
}
public Connection getConnection() throws SQLException {
- try {
- /*
- * The sonar batch downloads the JDBC driver in a separated classloader.
- * This is a well-know problem of java.sql.DriverManager. The workaround
- * is to use a proxy.
- * See http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location
- */
- Driver driver = (Driver) classloader.loadClass(getDriver()).newInstance();
- DriverManager.registerDriver(new DriverProxy(driver));
-
- } catch (Exception e) {
- SQLException ex = new SQLException("SQL driver not found " + getDriver());
- throw (SQLException) ex.initCause(e);
+ /*
+ * The Sonar batch downloads the JDBC driver in a separated class loader.
+ * This is a well-know problem of java.sql.DriverManager. The workaround
+ * is to use a proxy.
+ * See http://stackoverflow.com/questions/288828/how-to-use-a-jdbc-driver-from-an-arbitrary-location
+ */
+ if (!driverProxyRegistered) {
+ driverProxyRegistered = true;
+ try {
+ Driver driver = (Driver) classloader.loadClass(getDriver()).newInstance();
+ DriverManager.registerDriver(new DriverProxy(driver));
+ } catch (Exception e) {
+ SQLException ex = new SQLException("SQL driver not found " + getDriver());
+ throw (SQLException) ex.initCause(e);
+ }
}
return DriverManager.getConnection(getUrl(), getUsername(), getPassword());
}
+ @Override
+ public void stop() {
+ super.stop();
+
+ deregisterDriverProxy();
+ }
+
+ /**
+ * Due to memory leak in DriverManager we also should deregister original driver,
+ * but we can't do it here, because DriverManager checks the class loader of the calling class.
+ * So actually we might have a memory leak, but it supposed to be handled by Sonar batch.
+ */
+ private void deregisterDriverProxy() {
+ Enumeration drivers = DriverManager.getDrivers();
+ while (drivers.hasMoreElements()) {
+ Driver driver = drivers.nextElement();
+ if (driver instanceof DriverProxy) {
+ try {
+ DriverManager.deregisterDriver(driver);
+ LOG.debug("JDBC Driver [{}] deregistered", driver);
+ } catch (SQLException e) {
+ LOG.warn("JDBC driver deregistration failed", e);
+ }
+ }
+ }
+ }
+
@Override
public void setupEntityManagerFactory(Properties factoryProps) {
factoryProps.put("hibernate.connection.url", getUrl());
@@ -163,4 +194,4 @@ final class DriverProxy implements Driver {
org.sonar.jpa.session.DriverProxy other = (org.sonar.jpa.session.DriverProxy) obj;
return this.target.equals(other.target);
}
-}
\ No newline at end of file
+}
--
cgit v1.2.3