HADOOP-7156. Workaround for unsafe implementations of getpwuid_r. Contributed by Todd Lipcon.

git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1080723 13f79535-47bb-0310-9956-ffa450edef68
This commit is contained in:
Todd Lipcon 2011-03-11 18:44:25 +00:00
parent 527bac7e22
commit 96da4be11c
5 changed files with 126 additions and 2 deletions

View File

@ -535,6 +535,8 @@ Release 0.22.0 - Unreleased
HADOOP-7145. Configuration.getLocalPath should trim whitespace from HADOOP-7145. Configuration.getLocalPath should trim whitespace from
the provided directories. (todd) the provided directories. (todd)
HADOOP-7156. Workaround for unsafe implementations of getpwuid_r (todd)
Release 0.21.1 - Unreleased Release 0.21.1 - Unreleased
IMPROVEMENTS IMPROVEMENTS

View File

@ -108,6 +108,21 @@
</description> </description>
</property> </property>
<property>
<name>hadoop.work.around.non.threadsafe.getpwuid</name>
<value>false</value>
<description>Some operating systems or authentication modules are known to
have broken implementations of getpwuid_r and getpwgid_r, such that these
calls are not thread-safe. Symptoms of this problem include JVM crashes
with a stack trace inside these functions. If your system exhibits this
issue, enable this configuration parameter to include a lock around the
calls as a workaround.
An incomplete list of some systems known to have this issue is available
at http://wiki.apache.org/hadoop/KnownBrokenPwuidImplementations
</description>
</property>
<!--- logging properties --> <!--- logging properties -->
<property> <property>

View File

@ -20,6 +20,7 @@ package org.apache.hadoop.io.nativeio;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.IOException; import java.io.IOException;
import org.apache.hadoop.conf.Configuration;
import org.apache.hadoop.util.NativeCodeLoader; import org.apache.hadoop.util.NativeCodeLoader;
import org.apache.commons.logging.Log; import org.apache.commons.logging.Log;
@ -48,10 +49,20 @@ public class NativeIO {
private static final Log LOG = LogFactory.getLog(NativeIO.class); private static final Log LOG = LogFactory.getLog(NativeIO.class);
private static boolean nativeLoaded = false; private static boolean nativeLoaded = false;
private static boolean workaroundNonThreadSafePasswdCalls = false;
static final String WORKAROUND_NON_THREADSAFE_CALLS_KEY =
"hadoop.workaround.non.threadsafe.getpwuid";
static final boolean WORKAROUND_NON_THREADSAFE_CALLS_DEFAULT = false;
static { static {
if (NativeCodeLoader.isNativeCodeLoaded()) { if (NativeCodeLoader.isNativeCodeLoaded()) {
try { try {
Configuration conf = new Configuration();
workaroundNonThreadSafePasswdCalls = conf.getBoolean(
WORKAROUND_NON_THREADSAFE_CALLS_KEY,
WORKAROUND_NON_THREADSAFE_CALLS_DEFAULT);
initNative(); initNative();
nativeLoaded = true; nativeLoaded = true;
} catch (Throwable t) { } catch (Throwable t) {

View File

@ -44,18 +44,50 @@ static jmethodID stat_ctor;
static jclass nioe_clazz; static jclass nioe_clazz;
static jmethodID nioe_ctor; static jmethodID nioe_ctor;
// the monitor used for working around non-threadsafe implementations
// of getpwuid_r, observed on platforms including RHEL 6.0.
// Please see HADOOP-7156 for details.
static jobject pw_lock_object;
// Internal functions // Internal functions
static void throw_ioe(JNIEnv* env, int errnum); static void throw_ioe(JNIEnv* env, int errnum);
static ssize_t get_pw_buflen(); static ssize_t get_pw_buflen();
/**
* Returns non-zero if the user has specified that the system
* has non-threadsafe implementations of getpwuid_r or getgrgid_r.
**/
static int workaround_non_threadsafe_calls(JNIEnv *env, jclass clazz) {
jfieldID needs_workaround_field = (*env)->GetStaticFieldID(env, clazz,
"workaroundNonThreadSafePasswdCalls", "Z");
PASS_EXCEPTIONS_RET(env, 0);
assert(needs_workaround_field);
static void stat_init(JNIEnv *env) { jboolean result = (*env)->GetStaticBooleanField(
env, clazz, needs_workaround_field);
return result;
}
static void stat_init(JNIEnv *env, jclass nativeio_class) {
// Init Stat // Init Stat
jclass clazz = (*env)->FindClass(env, "org/apache/hadoop/io/nativeio/NativeIO$Stat"); jclass clazz = (*env)->FindClass(env, "org/apache/hadoop/io/nativeio/NativeIO$Stat");
PASS_EXCEPTIONS(env); PASS_EXCEPTIONS(env);
stat_clazz = (*env)->NewGlobalRef(env, clazz); stat_clazz = (*env)->NewGlobalRef(env, clazz);
stat_ctor = (*env)->GetMethodID(env, stat_clazz, "<init>", stat_ctor = (*env)->GetMethodID(env, stat_clazz, "<init>",
"(Ljava/lang/String;Ljava/lang/String;I)V"); "(Ljava/lang/String;Ljava/lang/String;I)V");
jclass obj_class = (*env)->FindClass(env, "java/lang/Object");
assert(obj_class != NULL);
jmethodID obj_ctor = (*env)->GetMethodID(env, obj_class,
"<init>", "()V");
assert(obj_ctor != NULL);
if (workaround_non_threadsafe_calls(env, nativeio_class)) {
pw_lock_object = (*env)->NewObject(env, obj_class, obj_ctor);
PASS_EXCEPTIONS(env);
pw_lock_object = (*env)->NewGlobalRef(env, pw_lock_object);
PASS_EXCEPTIONS(env);
}
} }
static void stat_deinit(JNIEnv *env) { static void stat_deinit(JNIEnv *env) {
@ -63,6 +95,10 @@ static void stat_deinit(JNIEnv *env) {
(*env)->DeleteGlobalRef(env, stat_clazz); (*env)->DeleteGlobalRef(env, stat_clazz);
stat_clazz = NULL; stat_clazz = NULL;
} }
if (pw_lock_object != NULL) {
(*env)->DeleteGlobalRef(env, pw_lock_object);
pw_lock_object = NULL;
}
} }
static void nioe_init(JNIEnv *env) { static void nioe_init(JNIEnv *env) {
@ -95,7 +131,7 @@ JNIEXPORT void JNICALL
Java_org_apache_hadoop_io_nativeio_NativeIO_initNative( Java_org_apache_hadoop_io_nativeio_NativeIO_initNative(
JNIEnv *env, jclass clazz) { JNIEnv *env, jclass clazz) {
stat_init(env); stat_init(env, clazz);
PASS_EXCEPTIONS_GOTO(env, error); PASS_EXCEPTIONS_GOTO(env, error);
nioe_init(env); nioe_init(env);
PASS_EXCEPTIONS_GOTO(env, error); PASS_EXCEPTIONS_GOTO(env, error);
@ -122,6 +158,7 @@ Java_org_apache_hadoop_io_nativeio_NativeIO_fstat(
{ {
jobject ret = NULL; jobject ret = NULL;
char *pw_buf = NULL; char *pw_buf = NULL;
int pw_lock_locked = 0;
int fd = fd_get(env, fd_object); int fd = fd_get(env, fd_object);
PASS_EXCEPTIONS_GOTO(env, cleanup); PASS_EXCEPTIONS_GOTO(env, cleanup);
@ -139,6 +176,13 @@ Java_org_apache_hadoop_io_nativeio_NativeIO_fstat(
goto cleanup; goto cleanup;
} }
if (pw_lock_object != NULL) {
if ((*env)->MonitorEnter(env, pw_lock_object) != JNI_OK) {
goto cleanup;
}
pw_lock_locked = 1;
}
// Grab username // Grab username
struct passwd pwd, *pwdp; struct passwd pwd, *pwdp;
while ((rc = getpwuid_r(s.st_uid, &pwd, pw_buf, pw_buflen, &pwdp)) != 0) { while ((rc = getpwuid_r(s.st_uid, &pwd, pw_buf, pw_buflen, &pwdp)) != 0) {
@ -183,6 +227,9 @@ Java_org_apache_hadoop_io_nativeio_NativeIO_fstat(
cleanup: cleanup:
if (pw_buf != NULL) free(pw_buf); if (pw_buf != NULL) free(pw_buf);
if (pw_lock_locked) {
(*env)->MonitorExit(env, pw_lock_object);
}
return ret; return ret;
} }

View File

@ -21,6 +21,9 @@ import java.io.File;
import java.io.FileDescriptor; import java.io.FileDescriptor;
import java.io.FileOutputStream; import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.util.concurrent.atomic.AtomicReference;
import java.util.ArrayList;
import java.util.List;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import static org.junit.Assume.*; import static org.junit.Assume.*;
@ -67,6 +70,52 @@ public class TestNativeIO {
NativeIO.Stat.S_IFREG, stat.getMode() & NativeIO.Stat.S_IFMT); NativeIO.Stat.S_IFREG, stat.getMode() & NativeIO.Stat.S_IFMT);
} }
/**
* Test for races in fstat usage
*
* NOTE: this test is likely to fail on RHEL 6.0 which has a non-threadsafe
* implementation of getpwuid_r.
*/
@Test
public void testMultiThreadedFstat() throws Exception {
final FileOutputStream fos = new FileOutputStream(
new File(TEST_DIR, "testfstat"));
final AtomicReference<Throwable> thrown =
new AtomicReference<Throwable>();
List<Thread> statters = new ArrayList<Thread>();
for (int i = 0; i < 10; i++) {
Thread statter = new Thread() {
public void run() {
long et = System.currentTimeMillis() + 5000;
while (System.currentTimeMillis() < et) {
try {
NativeIO.Stat stat = NativeIO.fstat(fos.getFD());
assertEquals(System.getProperty("user.name"), stat.getOwner());
assertNotNull(stat.getGroup());
assertTrue(!"".equals(stat.getGroup()));
assertEquals("Stat mode field should indicate a regular file",
NativeIO.Stat.S_IFREG, stat.getMode() & NativeIO.Stat.S_IFMT);
} catch (Throwable t) {
thrown.set(t);
}
}
}
};
statters.add(statter);
statter.start();
}
for (Thread t : statters) {
t.join();
}
fos.close();
if (thrown.get() != null) {
throw new RuntimeException(thrown.get());
}
}
@Test @Test
public void testFstatClosedFd() throws Exception { public void testFstatClosedFd() throws Exception {
FileOutputStream fos = new FileOutputStream( FileOutputStream fos = new FileOutputStream(