Thread: Don't set native names twice for attached threads.
ART already does this when a threads are attached. There's no need
to do it another time.
Also add a test that documents the behaviour wrt. to thread naming
for attached threads.
bug: 27748318
Change-Id: I9075f0434b0168280662a1f8a4176f23724748a7
diff --git a/NativeCode.mk b/NativeCode.mk
index 7ab8e4f..be02ff1 100644
--- a/NativeCode.mk
+++ b/NativeCode.mk
@@ -94,6 +94,7 @@
core_test_files := \
luni/src/test/native/dalvik_system_JniTest.cpp \
luni/src/test/native/libcore_java_io_FileTest.cpp \
+ luni/src/test/native/libcore_java_lang_ThreadTest.cpp \
luni/src/test/native/libcore_java_nio_BufferTest.cpp \
luni/src/test/native/libcore_util_NativeAllocationRegistryTest.cpp \
diff --git a/luni/src/test/java/libcore/java/lang/ThreadTest.java b/luni/src/test/java/libcore/java/lang/ThreadTest.java
index 8545a20..10ca9a7 100644
--- a/luni/src/test/java/libcore/java/lang/ThreadTest.java
+++ b/luni/src/test/java/libcore/java/lang/ThreadTest.java
@@ -22,6 +22,9 @@
import libcore.java.lang.ref.FinalizationTester;
public final class ThreadTest extends TestCase {
+ static {
+ System.loadLibrary("javacoretests");
+ }
/**
* getContextClassLoader returned a non-application class loader.
@@ -167,6 +170,19 @@
assertTrue("Must have traces for all threads", visibleTraces.get() > 1);
}
+ // http://b/27748318
+ public void testNativeThreadNames() throws Exception {
+ String testResult = nativeTestNativeThreadNames();
+ // Not using assertNull here because this results in a better error message.
+ if (testResult != null) {
+ fail(testResult);
+ }
+ }
+
+ // This method returns {@code null} if all tests pass, or a non-null String containing
+ // failure details if an error occured.
+ private static native String nativeTestNativeThreadNames();
+
private Thread newThread(final AtomicInteger finalizedThreadsCount, final int size) {
return new Thread() {
long[] memoryPressure = new long[size];
diff --git a/luni/src/test/native/libcore_java_lang_ThreadTest.cpp b/luni/src/test/native/libcore_java_lang_ThreadTest.cpp
new file mode 100644
index 0000000..3e52d997
--- /dev/null
+++ b/luni/src/test/native/libcore_java_lang_ThreadTest.cpp
@@ -0,0 +1,134 @@
+/*
+ * Copyright (C) 2015 The Android Open Source Project
+ *
+ * Licensed 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.
+ */
+
+#include <memory>
+#include <string>
+
+#include <pthread.h>
+#include <sys/prctl.h>
+
+#include <jni.h>
+#include "JNIHelp.h"
+
+static JavaVM* javaVm = nullptr;
+
+static void* TestThreadNaming(void* arg) {
+ const bool attach_with_name = (reinterpret_cast<uint64_t>(arg) == 1);
+ const std::string native_thread_name = "foozball";
+ pthread_setname_np(pthread_self(), native_thread_name.c_str());
+
+ JNIEnv* env;
+ JavaVMAttachArgs args;
+ args.version = JNI_VERSION_1_6;
+ args.group = nullptr;
+ if (attach_with_name) {
+ args.name = native_thread_name.c_str();
+ }
+
+ if (javaVm->AttachCurrentThread(&env, &args) != JNI_OK) {
+ return new std::string("Attach failed");
+ }
+
+ std::string* exception_message = nullptr;
+ std::unique_ptr<char[]> thread_name(new char[32]);
+ if (prctl(PR_GET_NAME, reinterpret_cast<unsigned long>(thread_name.get()), 0L, 0L, 0L) == 0) {
+ // If a thread is attached with a name, the native thread name must be set to
+ // the supplied name. In this test, the name we attach with == the
+ // native_thread_name.
+ if (attach_with_name && (thread_name.get() != native_thread_name)) {
+ exception_message = new std::string("expected_thread_name != thread_name: ");
+ exception_message->append("expected :");
+ exception_message->append(native_thread_name);
+ exception_message->append(" was :");
+ exception_message->append(thread_name.get());
+ }
+
+ // On the other hand, if the thread isn't attached with a name - the
+ // runtime assigns a name according to the usual thread naming scheme.
+ if (!attach_with_name && strncmp(thread_name.get(), "Thread", 6)) {
+ exception_message = new std::string("unexpected thread name : ");
+ exception_message->append(thread_name.get());
+ }
+ } else {
+ exception_message = new std::string("prctl(PR_GET_NAME) failed :");
+ exception_message->append(strerror(errno));
+ }
+
+
+ if (javaVm->DetachCurrentThread() != JNI_OK) {
+ exception_message = new std::string("Detach failed");
+ }
+
+ return exception_message;
+}
+
+extern "C" jstring Java_libcore_java_lang_ThreadTest_nativeTestNativeThreadNames(
+ JNIEnv* env, jobject /* object */) {
+ std::string result;
+
+ // TEST 1: Test that a thread attaching with a specified name (in the
+ // JavaVMAttachArgs) does not have its name changed.
+ pthread_t attacher;
+ if (pthread_create(&attacher, nullptr, TestThreadNaming,
+ reinterpret_cast<void*>(static_cast<uint64_t>(0))) != 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Attach failed");
+ }
+
+ std::string* result_test1;
+ if (pthread_join(attacher, reinterpret_cast<void**>(&result_test1)) != 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Join failed");
+ }
+
+ if (result_test1 != nullptr) {
+ result.append("test 1: ");
+ result.append(*result_test1);
+ }
+
+ // TEST 2: Test that a thread attaching without a specified name (in the
+ // JavaVMAttachArgs) has its native name changed as per the standard naming
+ // convention.
+ pthread_t attacher2;
+ if (pthread_create(&attacher2, nullptr, TestThreadNaming,
+ reinterpret_cast<void*>(static_cast<uint64_t>(1))) != 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Attach failed");
+ }
+
+ std::string* result_test2;
+ if (pthread_join(attacher2, reinterpret_cast<void**>(&result_test2)) != 0) {
+ jniThrowException(env, "java/lang/IllegalStateException", "Join failed");
+ }
+
+ if (result_test2 != nullptr) {
+ result.append("test 2: ");
+ result.append(*result_test2);
+ }
+
+ // Return test results.
+ jstring resultJString = nullptr;
+ if (result.size() > 0) {
+ resultJString = env->NewStringUTF(result.c_str());
+ }
+
+ delete result_test1;
+ delete result_test2;
+
+ return resultJString;
+}
+
+extern "C" int JNI_OnLoad(JavaVM* vm, void*) {
+ javaVm = vm;
+ return JNI_VERSION_1_6;
+}
diff --git a/ojluni/src/main/java/java/lang/Thread.java b/ojluni/src/main/java/java/lang/Thread.java
index c14022b..1436dd4 100755
--- a/ojluni/src/main/java/java/lang/Thread.java
+++ b/ojluni/src/main/java/java/lang/Thread.java
@@ -395,53 +395,6 @@
* zero to indicate that this parameter is to be ignored.
*/
private void init(ThreadGroup g, Runnable target, String name, long stackSize) {
- // Android changed : Reimplemented.
- /*
- if (name == null) {
- throw new NullPointerException("name cannot be null");
- }
-
- Thread parent = currentThread();
- SecurityManager security = System.getSecurityManager();
- if (g == null) {
- // Determine if it's an applet or not
-
- // If there is a security manager, ask the security manager what to do.
- if (security != null) {
- g = security.getThreadGroup();
- }
-
- // If the security doesn't have a strong opinion of the matter
- // use the parent thread group.
- if (g == null) {
- g = parent.getThreadGroup();
- }
- }
-
- // checkAccess regardless of whether or not threadgroup is explicitly passed in.
- g.checkAccess();
-
- // Do we have the required permissions?
- if (security != null) {
- if (isCCLOverridden(getClass())) {
- security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
- }
- }
- if (g == null) {
- g = parent.getThreadGroup();
- }
- g.checkAccess();
- this.daemon = parent.isDaemon();
- this.priority = parent.getPriority();
- this.name = name.toCharArray();
- if (security == null || isCCLOverridden(parent.getClass()))
- this.contextClassLoader = parent.getContextClassLoader();
- else
- this.contextClassLoader = parent.contextClassLoader;
- tid = nextThreadID();
- */
- // ----- END android -----
-
Thread parent = currentThread();
if (g == null) {
g = parent.getThreadGroup();
@@ -574,7 +527,12 @@
if (name == null) {
name = "Thread-" + nextThreadNum();
}
- setName(name);
+
+ // NOTE: Resist the temptation to call setName() here. This constructor is only called
+ // by the runtime to construct peers for threads that have attached via JNI and it's
+ // undesirable to clobber their natively set name.
+ this.name = name;
+
this.priority = priority;
this.daemon = daemon;
init2(currentThread());