Skip to content

Commit abf276c

Browse files
committed
HADOOP-10893. isolated classloader on the client side. Contributed by Sangjin Lee
git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1619604 13f79535-47bb-0310-9956-ffa450edef68
1 parent 0a9f2c5 commit abf276c

File tree

20 files changed

+584
-217
lines changed

20 files changed

+584
-217
lines changed

hadoop-common-project/hadoop-common/CHANGES.txt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -383,6 +383,9 @@ Release 2.6.0 - UNRELEASED
383383

384384
HADOOP-10433. Key Management Server based on KeyProvider API. (tucu)
385385

386+
HADOOP-10893. isolated classloader on the client side (Sangjin Lee via
387+
jlowe)
388+
386389
IMPROVEMENTS
387390

388391
HADOOP-10808. Remove unused native code for munlock. (cnauroth)

hadoop-common-project/hadoop-common/dev-support/findbugsExcludeFile.xml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,11 @@
108108
<Method name="driver" />
109109
<Bug pattern="DM_EXIT" />
110110
</Match>
111+
<Match>
112+
<Class name="org.apache.hadoop.util.RunJar" />
113+
<Method name="run" />
114+
<Bug pattern="DM_EXIT" />
115+
</Match>
111116
<!--
112117
We need to cast objects between old and new api objects
113118
-->

hadoop-common-project/hadoop-common/src/main/bin/hadoop-config.cmd

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -282,10 +282,12 @@ if not "%HADOOP_MAPRED_HOME%\%MAPRED_DIR%" == "%HADOOP_YARN_HOME%\%YARN_DIR%" (
282282
@rem
283283

284284
if defined HADOOP_CLASSPATH (
285-
if defined HADOOP_USER_CLASSPATH_FIRST (
286-
set CLASSPATH=%HADOOP_CLASSPATH%;%CLASSPATH%;
287-
) else (
288-
set CLASSPATH=%CLASSPATH%;%HADOOP_CLASSPATH%;
285+
if not defined HADOOP_USE_CLIENT_CLASSLOADER (
286+
if defined HADOOP_USER_CLASSPATH_FIRST (
287+
set CLASSPATH=%HADOOP_CLASSPATH%;%CLASSPATH%;
288+
) else (
289+
set CLASSPATH=%CLASSPATH%;%HADOOP_CLASSPATH%;
290+
)
289291
)
290292
)
291293

hadoop-common-project/hadoop-common/src/main/bin/hadoop-functions.sh

Lines changed: 12 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -450,7 +450,8 @@ function hadoop_add_to_classpath_mapred
450450
function hadoop_add_to_classpath_userpath
451451
{
452452
# Add the user-specified HADOOP_CLASSPATH to the
453-
# official CLASSPATH env var.
453+
# official CLASSPATH env var if HADOOP_USE_CLIENT_CLASSLOADER
454+
# is not set.
454455
# Add it first or last depending on if user has
455456
# set env-var HADOOP_USER_CLASSPATH_FIRST
456457
# we'll also dedupe it, because we're cool like that.
@@ -469,14 +470,16 @@ function hadoop_add_to_classpath_userpath
469470
done
470471
let j=c-1
471472

472-
if [[ -z "${HADOOP_USER_CLASSPATH_FIRST}" ]]; then
473-
for ((i=j; i>=0; i--)); do
474-
hadoop_add_classpath "${array[$i]}" before
475-
done
476-
else
477-
for ((i=0; i<=j; i++)); do
478-
hadoop_add_classpath "${array[$i]}" after
479-
done
473+
if [[ -z "${HADOOP_USE_CLIENT_CLASSLOADER}" ]]; then
474+
if [[ -z "${HADOOP_USER_CLASSPATH_FIRST}" ]]; then
475+
for ((i=j; i>=0; i--)); do
476+
hadoop_add_classpath "${array[$i]}" before
477+
done
478+
else
479+
for ((i=0; i<=j; i++)); do
480+
hadoop_add_classpath "${array[$i]}" after
481+
done
482+
fi
480483
fi
481484
fi
482485
}

hadoop-common-project/hadoop-common/src/main/bin/hadoop.cmd

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,26 @@
2828
@rem classpath. Can be defined, for example,
2929
@rem by doing
3030
@rem export HADOOP_USER_CLASSPATH_FIRST=true
31+
@rem
32+
@rem HADOOP_USE_CLIENT_CLASSLOADER When defined, HADOOP_CLASSPATH and the
33+
@rem jar as the hadoop jar argument are
34+
@rem handled by a separate isolated client
35+
@rem classloader. If it is set,
36+
@rem HADOOP_USER_CLASSPATH_FIRST is
37+
@rem ignored. Can be defined by doing
38+
@rem export HADOOP_USE_CLIENT_CLASSLOADER=true
39+
@rem
40+
@rem HADOOP_CLIENT_CLASSLOADER_SYSTEM_CLASSES
41+
@rem When defined, it overrides the default
42+
@rem definition of system classes for the
43+
@rem client classloader when
44+
@rem HADOOP_USE_CLIENT_CLASSLOADER is
45+
@rem enabled. Names ending in '.' (period)
46+
@rem are treated as package names, and names
47+
@rem starting with a '-' are treated as
48+
@rem negative matches. For example,
49+
@rem export HADOOP_CLIENT_CLASSLOADER_SYSTEM_CLASSES="-org.apache.hadoop.UserClass,java.,javax.,org.apache.hadoop."
50+
3151
@rem
3252
@rem HADOOP_HEAPSIZE The maximum amount of heap to use, in MB.
3353
@rem Default is 1000.

hadoop-common-project/hadoop-common/src/main/conf/hadoop-env.sh

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,17 @@ esac
111111
# Should HADOOP_USER_CLASSPATH be first in the official CLASSPATH?
112112
# export HADOOP_USER_CLASSPATH_FIRST="yes"
113113

114+
# If HADOOP_USE_CLIENT_CLASSLOADER is set, HADOOP_CLASSPATH along with the main
115+
# jar are handled by a separate isolated client classloader. If it is set,
116+
# HADOOP_USER_CLASSPATH_FIRST is ignored. Can be defined by doing
117+
# export HADOOP_USE_CLIENT_CLASSLOADER=true
118+
119+
# HADOOP_CLIENT_CLASSLOADER_SYSTEM_CLASSES overrides the default definition of
120+
# system classes for the client classloader when HADOOP_USE_CLIENT_CLASSLOADER
121+
# is enabled. Names ending in '.' (period) are treated as package names, and
122+
# names starting with a '-' are treated as negative matches. For example,
123+
# export HADOOP_CLIENT_CLASSLOADER_SYSTEM_CLASSES="-org.apache.hadoop.UserClass,java.,javax.,org.apache.hadoop."
124+
114125
###
115126
# Options for remote shell connectivity
116127
###
Lines changed: 219 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,219 @@
1+
/**
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing, software
13+
* distributed under the License is distributed on an "AS IS" BASIS,
14+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
* See the License for the specific language governing permissions and
16+
* limitations under the License.
17+
*/
18+
19+
package org.apache.hadoop.util;
20+
21+
import java.io.File;
22+
import java.io.FilenameFilter;
23+
import java.net.MalformedURLException;
24+
import java.net.URL;
25+
import java.net.URLClassLoader;
26+
import java.util.ArrayList;
27+
import java.util.Arrays;
28+
import java.util.List;
29+
30+
import org.apache.commons.logging.Log;
31+
import org.apache.commons.logging.LogFactory;
32+
import org.apache.hadoop.classification.InterfaceAudience.Public;
33+
import org.apache.hadoop.classification.InterfaceStability.Unstable;
34+
35+
/**
36+
* A {@link URLClassLoader} for application isolation. Classes from the
37+
* application JARs are loaded in preference to the parent loader.
38+
*/
39+
@Public
40+
@Unstable
41+
public class ApplicationClassLoader extends URLClassLoader {
42+
/**
43+
* Default value of the system classes if the user did not override them.
44+
* JDK classes, hadoop classes and resources, and some select third-party
45+
* classes are considered system classes, and are not loaded by the
46+
* application classloader.
47+
*/
48+
public static final String DEFAULT_SYSTEM_CLASSES =
49+
"java.," +
50+
"javax.," +
51+
"org.w3c.dom.," +
52+
"org.xml.sax.," +
53+
"org.apache.commons.logging.," +
54+
"org.apache.log4j.," +
55+
"org.apache.hadoop.," +
56+
"core-default.xml," +
57+
"hdfs-default.xml," +
58+
"mapred-default.xml," +
59+
"yarn-default.xml";
60+
61+
private static final Log LOG =
62+
LogFactory.getLog(ApplicationClassLoader.class.getName());
63+
64+
private static final FilenameFilter JAR_FILENAME_FILTER =
65+
new FilenameFilter() {
66+
@Override
67+
public boolean accept(File dir, String name) {
68+
return name.endsWith(".jar") || name.endsWith(".JAR");
69+
}
70+
};
71+
72+
private final ClassLoader parent;
73+
private final List<String> systemClasses;
74+
75+
public ApplicationClassLoader(URL[] urls, ClassLoader parent,
76+
List<String> systemClasses) {
77+
super(urls, parent);
78+
if (LOG.isDebugEnabled()) {
79+
LOG.debug("urls: " + Arrays.toString(urls));
80+
LOG.debug("system classes: " + systemClasses);
81+
}
82+
this.parent = parent;
83+
if (parent == null) {
84+
throw new IllegalArgumentException("No parent classloader!");
85+
}
86+
// if the caller-specified system classes are null or empty, use the default
87+
this.systemClasses = (systemClasses == null || systemClasses.isEmpty()) ?
88+
Arrays.asList(StringUtils.getTrimmedStrings(DEFAULT_SYSTEM_CLASSES)) :
89+
systemClasses;
90+
LOG.info("system classes: " + this.systemClasses);
91+
}
92+
93+
public ApplicationClassLoader(String classpath, ClassLoader parent,
94+
List<String> systemClasses) throws MalformedURLException {
95+
this(constructUrlsFromClasspath(classpath), parent, systemClasses);
96+
}
97+
98+
static URL[] constructUrlsFromClasspath(String classpath)
99+
throws MalformedURLException {
100+
List<URL> urls = new ArrayList<URL>();
101+
for (String element : classpath.split(File.pathSeparator)) {
102+
if (element.endsWith("/*")) {
103+
String dir = element.substring(0, element.length() - 1);
104+
File[] files = new File(dir).listFiles(JAR_FILENAME_FILTER);
105+
if (files != null) {
106+
for (File file : files) {
107+
urls.add(file.toURI().toURL());
108+
}
109+
}
110+
} else {
111+
File file = new File(element);
112+
if (file.exists()) {
113+
urls.add(new File(element).toURI().toURL());
114+
}
115+
}
116+
}
117+
return urls.toArray(new URL[urls.size()]);
118+
}
119+
120+
@Override
121+
public URL getResource(String name) {
122+
URL url = null;
123+
124+
if (!isSystemClass(name, systemClasses)) {
125+
url= findResource(name);
126+
if (url == null && name.startsWith("/")) {
127+
if (LOG.isDebugEnabled()) {
128+
LOG.debug("Remove leading / off " + name);
129+
}
130+
url= findResource(name.substring(1));
131+
}
132+
}
133+
134+
if (url == null) {
135+
url= parent.getResource(name);
136+
}
137+
138+
if (url != null) {
139+
if (LOG.isDebugEnabled()) {
140+
LOG.debug("getResource("+name+")=" + url);
141+
}
142+
}
143+
144+
return url;
145+
}
146+
147+
@Override
148+
public Class<?> loadClass(String name) throws ClassNotFoundException {
149+
return this.loadClass(name, false);
150+
}
151+
152+
@Override
153+
protected synchronized Class<?> loadClass(String name, boolean resolve)
154+
throws ClassNotFoundException {
155+
156+
if (LOG.isDebugEnabled()) {
157+
LOG.debug("Loading class: " + name);
158+
}
159+
160+
Class<?> c = findLoadedClass(name);
161+
ClassNotFoundException ex = null;
162+
163+
if (c == null && !isSystemClass(name, systemClasses)) {
164+
// Try to load class from this classloader's URLs. Note that this is like
165+
// the servlet spec, not the usual Java 2 behaviour where we ask the
166+
// parent to attempt to load first.
167+
try {
168+
c = findClass(name);
169+
if (LOG.isDebugEnabled() && c != null) {
170+
LOG.debug("Loaded class: " + name + " ");
171+
}
172+
} catch (ClassNotFoundException e) {
173+
if (LOG.isDebugEnabled()) {
174+
LOG.debug(e);
175+
}
176+
ex = e;
177+
}
178+
}
179+
180+
if (c == null) { // try parent
181+
c = parent.loadClass(name);
182+
if (LOG.isDebugEnabled() && c != null) {
183+
LOG.debug("Loaded class from parent: " + name + " ");
184+
}
185+
}
186+
187+
if (c == null) {
188+
throw ex != null ? ex : new ClassNotFoundException(name);
189+
}
190+
191+
if (resolve) {
192+
resolveClass(c);
193+
}
194+
195+
return c;
196+
}
197+
198+
public static boolean isSystemClass(String name, List<String> systemClasses) {
199+
if (systemClasses != null) {
200+
String canonicalName = name.replace('/', '.');
201+
while (canonicalName.startsWith(".")) {
202+
canonicalName=canonicalName.substring(1);
203+
}
204+
for (String c : systemClasses) {
205+
boolean result = true;
206+
if (c.startsWith("-")) {
207+
c = c.substring(1);
208+
result = false;
209+
}
210+
if (c.endsWith(".") && canonicalName.startsWith(c)) {
211+
return result;
212+
} else if (canonicalName.equals(c)) {
213+
return result;
214+
}
215+
}
216+
}
217+
return false;
218+
}
219+
}

0 commit comments

Comments
 (0)