diff options
-rw-r--r-- | runtime/asm_support.h | 4 | ||||
-rw-r--r-- | runtime/entrypoints_order_test.cc | 3 | ||||
-rw-r--r-- | runtime/thread.cc | 95 | ||||
-rw-r--r-- | runtime/thread.h | 18 |
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. |