summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndreas Gampe <agampe@google.com>2015-06-01 22:29:51 -0700
committerAndreas Gampe <agampe@google.com>2015-06-03 12:55:57 -0700
commit520abbd0edcf333f07164539620ce65258c72383 (patch)
tree4a795bdaefc0e8cdff4c2432afa3e5d4de5f779d
parent44905ce1c97613a5cb44046049843fe1029a64cf (diff)
downloadart-520abbd0edcf333f07164539620ce65258c72383.zip
art-520abbd0edcf333f07164539620ce65258c72383.tar.gz
art-520abbd0edcf333f07164539620ce65258c72383.tar.bz2
ART: Refactor Thread::Init
This refactor allows the parent thread to allocate the JNIEnvExt for the child (with a fallback in place in Init). This allows to throw an OOME in CreateNativeThread instead of aborting in the child. Bug: 21291279 Change-Id: Iccc1a5c202999f5bfacec706d9833e53135ba2fa
-rw-r--r--runtime/asm_support.h4
-rw-r--r--runtime/entrypoints_order_test.cc3
-rw-r--r--runtime/thread.cc95
-rw-r--r--runtime/thread.h18
4 files changed, 83 insertions, 37 deletions
diff --git a/runtime/asm_support.h b/runtime/asm_support.h
index d7efe1c..10ed0f4 100644
--- a/runtime/asm_support.h
+++ b/runtime/asm_support.h
@@ -104,11 +104,11 @@ ADD_TEST_EQ(THREAD_TOP_QUICK_FRAME_OFFSET,
art::Thread::TopOfManagedStackOffset<__SIZEOF_POINTER__>().Int32Value())
// Offset of field Thread::tlsPtr_.managed_stack.top_quick_frame_.
-#define THREAD_SELF_OFFSET (THREAD_CARD_TABLE_OFFSET + (8 * __SIZEOF_POINTER__))
+#define THREAD_SELF_OFFSET (THREAD_CARD_TABLE_OFFSET + (9 * __SIZEOF_POINTER__))
ADD_TEST_EQ(THREAD_SELF_OFFSET,
art::Thread::SelfOffset<__SIZEOF_POINTER__>().Int32Value())
-#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 146 * __SIZEOF_POINTER__)
+#define THREAD_LOCAL_POS_OFFSET (THREAD_CARD_TABLE_OFFSET + 147 * __SIZEOF_POINTER__)
ADD_TEST_EQ(THREAD_LOCAL_POS_OFFSET,
art::Thread::ThreadLocalPosOffset<__SIZEOF_POINTER__>().Int32Value())
#define THREAD_LOCAL_END_OFFSET (THREAD_LOCAL_POS_OFFSET + __SIZEOF_POINTER__)
diff --git a/runtime/entrypoints_order_test.cc b/runtime/entrypoints_order_test.cc
index 482f656..963dd02 100644
--- a/runtime/entrypoints_order_test.cc
+++ b/runtime/entrypoints_order_test.cc
@@ -88,7 +88,8 @@ class EntrypointsOrderTest : public CommonRuntimeTest {
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, stack_end, managed_stack, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, managed_stack, suspend_trigger, sizeof(ManagedStack));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, suspend_trigger, jni_env, sizeof(void*));
- EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, jni_env, self, sizeof(void*));
+ EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, jni_env, tmp_jni_env, sizeof(void*));
+ EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, tmp_jni_env, self, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, self, opeer, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, opeer, jpeer, sizeof(void*));
EXPECT_OFFSET_DIFFP(Thread, tlsPtr_, jpeer, stack_begin, sizeof(void*));
diff --git a/runtime/thread.cc b/runtime/thread.cc
index f37960f..845345a 100644
--- a/runtime/thread.cc
+++ b/runtime/thread.cc
@@ -196,7 +196,12 @@ void* Thread::CreateCallback(void* arg) {
// Check that if we got here we cannot be shutting down (as shutdown should never have started
// while threads are being born).
CHECK(!runtime->IsShuttingDownLocked());
- CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM()));
+ // Note: given that the JNIEnv is created in the parent thread, the only failure point here is
+ // a mess in InitStackHwm. We do not have a reasonable way to recover from that, so abort
+ // the runtime in such a case. In case this ever changes, we need to make sure here to
+ // delete the tmp_jni_env, as we own it at this point.
+ CHECK(self->Init(runtime->GetThreadList(), runtime->GetJavaVM(), self->tlsPtr_.tmp_jni_env));
+ self->tlsPtr_.tmp_jni_env = nullptr;
Runtime::Current()->EndThreadBirth();
}
{
@@ -358,37 +363,59 @@ void Thread::CreateNativeThread(JNIEnv* env, jobject java_peer, size_t stack_siz
env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer,
reinterpret_cast<jlong>(child_thread));
- pthread_t new_pthread;
- pthread_attr_t attr;
- CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
- CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED), "PTHREAD_CREATE_DETACHED");
- CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
- int pthread_create_result = pthread_create(&new_pthread, &attr, Thread::CreateCallback, child_thread);
- CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
-
- if (pthread_create_result != 0) {
- // pthread_create(3) failed, so clean up.
- {
- MutexLock mu(self, *Locks::runtime_shutdown_lock_);
- runtime->EndThreadBirth();
- }
- // Manually delete the global reference since Thread::Init will not have been run.
- env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
- child_thread->tlsPtr_.jpeer = nullptr;
- delete child_thread;
- child_thread = nullptr;
- // TODO: remove from thread group?
- env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
- {
- std::string msg(StringPrintf("pthread_create (%s stack) failed: %s",
- PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
- ScopedObjectAccess soa(env);
- soa.Self()->ThrowOutOfMemoryError(msg.c_str());
+ // Try to allocate a JNIEnvExt for the thread. We do this here as we might be out of memory and
+ // do not have a good way to report this on the child's side.
+ std::unique_ptr<JNIEnvExt> child_jni_env_ext(
+ JNIEnvExt::Create(child_thread, Runtime::Current()->GetJavaVM()));
+
+ int pthread_create_result = 0;
+ if (child_jni_env_ext.get() != nullptr) {
+ pthread_t new_pthread;
+ pthread_attr_t attr;
+ child_thread->tlsPtr_.tmp_jni_env = child_jni_env_ext.get();
+ CHECK_PTHREAD_CALL(pthread_attr_init, (&attr), "new thread");
+ CHECK_PTHREAD_CALL(pthread_attr_setdetachstate, (&attr, PTHREAD_CREATE_DETACHED),
+ "PTHREAD_CREATE_DETACHED");
+ CHECK_PTHREAD_CALL(pthread_attr_setstacksize, (&attr, stack_size), stack_size);
+ pthread_create_result = pthread_create(&new_pthread,
+ &attr,
+ Thread::CreateCallback,
+ child_thread);
+ CHECK_PTHREAD_CALL(pthread_attr_destroy, (&attr), "new thread");
+
+ if (pthread_create_result == 0) {
+ // pthread_create started the new thread. The child is now responsible for managing the
+ // JNIEnvExt we created.
+ // Note: we can't check for tmp_jni_env == nullptr, as that would require synchronization
+ // between the threads.
+ child_jni_env_ext.release();
+ return;
}
}
+
+ // Either JNIEnvExt::Create or pthread_create(3) failed, so clean up.
+ {
+ MutexLock mu(self, *Locks::runtime_shutdown_lock_);
+ runtime->EndThreadBirth();
+ }
+ // Manually delete the global reference since Thread::Init will not have been run.
+ env->DeleteGlobalRef(child_thread->tlsPtr_.jpeer);
+ child_thread->tlsPtr_.jpeer = nullptr;
+ delete child_thread;
+ child_thread = nullptr;
+ // TODO: remove from thread group?
+ env->SetLongField(java_peer, WellKnownClasses::java_lang_Thread_nativePeer, 0);
+ {
+ std::string msg(child_jni_env_ext.get() == nullptr ?
+ "Could not allocate JNI Env" :
+ StringPrintf("pthread_create (%s stack) failed: %s",
+ PrettySize(stack_size).c_str(), strerror(pthread_create_result)));
+ ScopedObjectAccess soa(env);
+ soa.Self()->ThrowOutOfMemoryError(msg.c_str());
+ }
}
-bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm) {
+bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm, JNIEnvExt* jni_env_ext) {
// This function does all the initialization that must be run by the native thread it applies to.
// (When we create a new thread from managed code, we allocate the Thread* in Thread::Create so
// we can handshake with the corresponding native thread when it's ready.) Check this native
@@ -415,9 +442,15 @@ bool Thread::Init(ThreadList* thread_list, JavaVMExt* java_vm) {
tls32_.thin_lock_thread_id = thread_list->AllocThreadId(this);
- tlsPtr_.jni_env = JNIEnvExt::Create(this, java_vm);
- if (tlsPtr_.jni_env == nullptr) {
- return false;
+ if (jni_env_ext != nullptr) {
+ DCHECK_EQ(jni_env_ext->vm, java_vm);
+ DCHECK_EQ(jni_env_ext->self, this);
+ tlsPtr_.jni_env = jni_env_ext;
+ } else {
+ tlsPtr_.jni_env = JNIEnvExt::Create(this, java_vm);
+ if (tlsPtr_.jni_env == nullptr) {
+ return false;
+ }
}
thread_list->Register(this);
diff --git a/runtime/thread.h b/runtime/thread.h
index 8c2e215..3f0d0a5 100644
--- a/runtime/thread.h
+++ b/runtime/thread.h
@@ -975,7 +975,15 @@ class Thread {
SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
void RemoveFromThreadGroup(ScopedObjectAccess& soa) SHARED_LOCKS_REQUIRED(Locks::mutator_lock_);
- bool Init(ThreadList*, JavaVMExt*) EXCLUSIVE_LOCKS_REQUIRED(Locks::runtime_shutdown_lock_);
+ // Initialize a thread.
+ //
+ // The third parameter is not mandatory. If given, the thread will use this JNIEnvExt. In case
+ // Init succeeds, this means the thread takes ownership of it. If Init fails, it is the caller's
+ // responsibility to destroy the given JNIEnvExt. If the parameter is null, Init will try to
+ // create a JNIEnvExt on its own (and potentially fail at that stage, indicated by a return value
+ // of false).
+ bool Init(ThreadList*, JavaVMExt*, JNIEnvExt* jni_env_ext = nullptr)
+ EXCLUSIVE_LOCKS_REQUIRED(Locks::runtime_shutdown_lock_);
void InitCardTable();
void InitCpu();
void CleanupCpu();
@@ -1111,8 +1119,8 @@ class Thread {
struct PACKED(4) tls_ptr_sized_values {
tls_ptr_sized_values() : card_table(nullptr), exception(nullptr), stack_end(nullptr),
- managed_stack(), suspend_trigger(nullptr), jni_env(nullptr), self(nullptr), opeer(nullptr),
- jpeer(nullptr), stack_begin(nullptr), stack_size(0),
+ managed_stack(), suspend_trigger(nullptr), jni_env(nullptr), tmp_jni_env(nullptr),
+ self(nullptr), opeer(nullptr), jpeer(nullptr), stack_begin(nullptr), stack_size(0),
stack_trace_sample(nullptr), wait_next(nullptr), monitor_enter_object(nullptr),
top_handle_scope(nullptr), class_loader_override(nullptr), long_jump_context(nullptr),
instrumentation_stack(nullptr), debug_invoke_req(nullptr), single_step_control(nullptr),
@@ -1144,6 +1152,10 @@ class Thread {
// Every thread may have an associated JNI environment
JNIEnvExt* jni_env;
+ // Temporary storage to transfer a pre-allocated JNIEnvExt from the creating thread to the
+ // created thread.
+ JNIEnvExt* tmp_jni_env;
+
// Initialized to "this". On certain architectures (such as x86) reading off of Thread::Current
// is easy but getting the address of Thread::Current is hard. This field can be read off of
// Thread::Current to give the address.