Add test for thread-attach naming behavior
The interaction between pthread_setname_np and AttachCurrrentThread is
not always obvious. Add a test to codify the interaction. Basically
AttachCurrentThread will always override the current
pthread_setname_np thread name, including if AttachCurrentThread is
called without an explicit name, in which case the pthread-name will
be changed to the name selected by java.
Test: ./test.py --host
Bug: 168655382
Change-Id: If0319b733dc808a4cb892b6199030657c3e69f81
diff --git a/test/2037-thread-name-inherit/Android.bp b/test/2037-thread-name-inherit/Android.bp
new file mode 100644
index 0000000..8cda6b5
--- /dev/null
+++ b/test/2037-thread-name-inherit/Android.bp
@@ -0,0 +1,19 @@
+// Generated by `regen-test-files`. Do not edit manually.
+
+// Build rules for ART run-test `2037-thread-name-inherit`.
+
+// Test's Dex code.
+java_test {
+ name: "art-run-test-2037-thread-name-inherit",
+ defaults: ["art-run-test-defaults"],
+ srcs: ["src/**/*.java"],
+ data: [":art-run-test-2037-thread-name-inherit-expected"],
+}
+
+// Test's expected output.
+genrule {
+ name: "art-run-test-2037-thread-name-inherit-expected",
+ out: ["art-run-test-2037-thread-name-inherit-expected.txt"],
+ srcs: ["expected.txt"],
+ cmd: "cp -f $(in) $(out)",
+}
diff --git a/test/2037-thread-name-inherit/expected.txt b/test/2037-thread-name-inherit/expected.txt
new file mode 100644
index 0000000..6a5618e
--- /dev/null
+++ b/test/2037-thread-name-inherit/expected.txt
@@ -0,0 +1 @@
+JNI_OnLoad called
diff --git a/test/2037-thread-name-inherit/info.txt b/test/2037-thread-name-inherit/info.txt
new file mode 100644
index 0000000..f659e84
--- /dev/null
+++ b/test/2037-thread-name-inherit/info.txt
@@ -0,0 +1 @@
+Test that thread's names are correctly inherited.
diff --git a/test/2037-thread-name-inherit/src/Main.java b/test/2037-thread-name-inherit/src/Main.java
new file mode 100644
index 0000000..18049d7
--- /dev/null
+++ b/test/2037-thread-name-inherit/src/Main.java
@@ -0,0 +1,101 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+import java.util.function.*;
+
+public class Main {
+ public static final boolean IS_ART = System.getProperty("java.vm.name").equals("Dalvik");
+
+ public static final class Names {
+ public final String native_name;
+ public final String java_name;
+
+ public Names(String ntv, String java) {
+ this.native_name = ntv;
+ this.java_name = java;
+ }
+
+ public boolean equals(Object o) {
+ if (o instanceof Names) {
+ Names on = (Names) o;
+ return on.native_name.equals(native_name) && on.java_name.equals(java_name);
+ } else {
+ return false;
+ }
+ }
+
+ public String toString() {
+ return "Names{native: \"" + native_name + "\", java: \"" + java_name + "\"}";
+ }
+ }
+
+ public static void checkDefaultNames(Names res) {
+ if (IS_ART) {
+ if (!res.native_name.matches("Thread-[0-9]+")) {
+ throw new Error("Bad thread name! " + res);
+ }
+ } else {
+ if (!res.native_name.equals("native-thread")) {
+ throw new Error("Bad thread name! " + res);
+ }
+ }
+ if (!res.java_name.matches("Thread-[0-9]+")) {
+ throw new Error("Bad thread name! " + res);
+ }
+ }
+
+ public static void checkNames(Names res, Names art_exp, Names ri_exp) {
+ if (IS_ART) {
+ if (!res.equals(art_exp)) {
+ throw new Error("Not equal " + res + " != " + art_exp);
+ }
+ } else {
+ if (!res.equals(ri_exp)) {
+ throw new Error("Not equal " + res + " != " + ri_exp);
+ }
+ }
+ }
+
+ public static void main(String[] args) throws Exception {
+ System.loadLibrary(args[0]);
+ Names[] name = new Names[1];
+ BiConsumer<String, Thread> thdResult =
+ (String native_name, Thread jthread) -> {
+ name[0] = new Names(native_name, jthread.getName());
+ };
+
+ runThreadTest(thdResult);
+ checkDefaultNames(name[0]);
+
+ runThreadTestWithName(thdResult);
+ checkNames(
+ name[0],
+ new Names("java-native-thr", "java-native-thread"),
+ new Names("native-thread", "java-native-thread"));
+
+ runThreadTestSetJava(thdResult);
+ checkNames(
+ name[0],
+ new Names("native-thread-s", "native-thread-set-java"),
+ new Names("native-thread", "native-thread-set-java"));
+ }
+
+ public static native void runThreadTest(BiConsumer<String, Thread> results);
+
+ public static native void runThreadTestWithName(BiConsumer<String, Thread> results);
+
+ public static native void runThreadTestSetJava(BiConsumer<String, Thread> results);
+}
diff --git a/test/2037-thread-name-inherit/thread_name_inherit.cc b/test/2037-thread-name-inherit/thread_name_inherit.cc
new file mode 100644
index 0000000..1cca514
--- /dev/null
+++ b/test/2037-thread-name-inherit/thread_name_inherit.cc
@@ -0,0 +1,111 @@
+/*
+ * Copyright (C) 2020 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 <jni.h>
+
+#include <iostream>
+
+#include "android-base/logging.h"
+
+struct ThreadArgs {
+ JavaVM* jvm;
+ jobject consumer;
+ JavaVMAttachArgs* attach_args;
+ bool set_in_java;
+};
+
+// The main method of the test thread. The ThreadArgs controls what this does.
+void* ThreadMain(void* arg) {
+ ThreadArgs* args = reinterpret_cast<ThreadArgs*>(arg);
+ JNIEnv* env = nullptr;
+ pthread_t self = pthread_self();
+
+ int err = pthread_setname_np(self, "native-thread");
+ CHECK_EQ(err, 0);
+
+ args->jvm->AttachCurrentThread(&env, args->attach_args);
+
+ jclass thread_class = env->FindClass("java/lang/Thread");
+ jclass consumer_class = env->FindClass("java/util/function/BiConsumer");
+ jmethodID current_thread_method =
+ env->GetStaticMethodID(thread_class, "currentThread", "()Ljava/lang/Thread;");
+ jmethodID accept_method =
+ env->GetMethodID(consumer_class, "accept", "(Ljava/lang/Object;Ljava/lang/Object;)V");
+ jobject current_thread = env->CallStaticObjectMethod(thread_class, current_thread_method);
+ if (args->set_in_java) {
+ jmethodID set_name_method = env->GetMethodID(thread_class, "setName", "(Ljava/lang/String;)V");
+ jobject str_name = env->NewStringUTF("native-thread-set-java");
+ env->CallVoidMethod(current_thread, set_name_method, str_name);
+ }
+
+ char name_chars[1024];
+ err = pthread_getname_np(self, name_chars, sizeof(name_chars));
+ CHECK_EQ(err, 0);
+ jobject str_name = env->NewStringUTF(name_chars);
+
+ env->CallVoidMethod(args->consumer, accept_method, str_name, current_thread);
+
+ args->jvm->DetachCurrentThread();
+
+ return nullptr;
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTestWithName(JNIEnv* env,
+ jclass /*clazz*/,
+ jobject consumer) {
+ jobject global_consumer = env->NewGlobalRef(consumer);
+ JavaVMAttachArgs args;
+ args.group = nullptr;
+ args.name = "java-native-thread";
+ args.version = JNI_VERSION_1_6;
+ ThreadArgs ta {
+ .jvm = nullptr, .consumer = global_consumer, .attach_args = &args, .set_in_java = false
+ };
+ env->GetJavaVM(&ta.jvm);
+ pthread_t child;
+ pthread_create(&child, nullptr, ThreadMain, &ta);
+ void* ret;
+ pthread_join(child, &ret);
+ env->DeleteGlobalRef(ta.consumer);
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTest(JNIEnv* env, jclass /*clazz*/, jobject consumer) {
+ jobject global_consumer = env->NewGlobalRef(consumer);
+ ThreadArgs ta {
+ .jvm = nullptr, .consumer = global_consumer, .attach_args = nullptr, .set_in_java = false
+ };
+ env->GetJavaVM(&ta.jvm);
+ pthread_t child;
+ pthread_create(&child, nullptr, ThreadMain, &ta);
+ void* ret;
+ pthread_join(child, &ret);
+ env->DeleteGlobalRef(ta.consumer);
+}
+
+extern "C" JNIEXPORT void Java_Main_runThreadTestSetJava(JNIEnv* env,
+ jclass /*clazz*/,
+ jobject consumer) {
+ jobject global_consumer = env->NewGlobalRef(consumer);
+ ThreadArgs ta {
+ .jvm = nullptr, .consumer = global_consumer, .attach_args = nullptr, .set_in_java = true
+ };
+ env->GetJavaVM(&ta.jvm);
+ pthread_t child;
+ pthread_create(&child, nullptr, ThreadMain, &ta);
+ void* ret;
+ pthread_join(child, &ret);
+ env->DeleteGlobalRef(ta.consumer);
+}
diff --git a/test/Android.bp b/test/Android.bp
index d31de87..514b54d 100644
--- a/test/Android.bp
+++ b/test/Android.bp
@@ -644,6 +644,7 @@
"2031-zygote-compiled-frame-deopt/native-wait.cc",
"2033-shutdown-mechanics/native_shutdown.cc",
"2036-jni-filechannel/jni_filechannel.cc",
+ "2037-thread-name-inherit/thread_name_inherit.cc",
"common/runtime_state.cc",
"common/stack_inspect.cc",
],