blob: bf6b2b81ae175cde2e7c65b29bbeec0eed492af6 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one or more
* contributor license agreements. See the NOTICE file distributed with
* this work for additional information regarding copyright ownership.
* The ASF licenses this file to You 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.
*/
/**
* @author Intel, Evgueni Brevnov
*/
#define LOG_DOMAIN "vm.core.shutdown"
#include "cxxlog.h"
#include <stdlib.h>
#include <apr_thread_mutex.h>
#include "open/hythread.h"
#include "open/gc.h"
#include "jthread.h"
#include "jni.h"
#include "jni_direct.h"
#include "environment.h"
#include "classloader.h"
#include "compile.h"
#include "nogc.h"
#include "jni_utils.h"
#include "vm_stats.h"
#include "thread_dump.h"
#include "interpreter.h"
#include "finalize.h"
#include "signals.h"
#define PROCESS_EXCEPTION(messageId, message) \
{ \
LECHO(messageId, message << "Internal error: "); \
\
if (jni_env->ExceptionCheck()== JNI_TRUE) \
{ \
jni_env->ExceptionDescribe(); \
jni_env->ExceptionClear(); \
} \
\
return JNI_ERR; \
} \
static jobject java_lang_ThreadGroup_lock_field;
/**
* Calls java.lang.System.execShutdownSequence() method.
*
* @param jni_env JNI environment of the current thread
*/
static jint exec_shutdown_sequence(JNIEnv * jni_env) {
jclass system_class;
jmethodID shutdown_method;
assert(hythread_is_suspend_enabled());
BEGIN_RAISE_AREA;
system_class = jni_env->FindClass("java/lang/System");
if (jni_env->ExceptionCheck() == JNI_TRUE || system_class == NULL) {
// This is debug message only. May appear when VM is already in shutdown stage.
PROCESS_EXCEPTION(38, "{0}can't find java.lang.System class.");
}
shutdown_method = jni_env->GetStaticMethodID(system_class, "execShutdownSequence", "()V");
if (jni_env->ExceptionCheck() == JNI_TRUE || shutdown_method == NULL) {
PROCESS_EXCEPTION(39, "{0}can't find java.lang.System.execShutdownSequence() method.");
}
jni_env->CallStaticVoidMethod(system_class, shutdown_method);
if (jni_env->ExceptionCheck() == JNI_TRUE) {
PROCESS_EXCEPTION(40, "{0}java.lang.System.execShutdownSequence() method completed with an exception.");
}
END_RAISE_AREA;
return JNI_OK;
}
static void vm_shutdown_callback() {
hythread_suspend_enable();
set_unwindable(false);
vm_thread_t vm_thread = jthread_self_vm_thread();
assert(vm_thread);
jobject java_thread = vm_thread->java_thread;
assert(java_thread);
IDATA UNUSED status = jthread_detach(java_thread);
assert(status == TM_ERROR_NONE);
status = jthread_monitor_release(java_lang_ThreadGroup_lock_field);
assert(status == TM_ERROR_NONE);
hythread_exit(NULL);
}
static void vm_thread_cancel(vm_thread_t thread) {
assert(thread);
// grab hythread global lock
hythread_global_lock();
IDATA UNUSED status = jthread_vm_detach(thread);
assert(status == TM_ERROR_NONE);
hythread_cancel((hythread_t)thread);
// release hythread global lock
hythread_global_unlock();
}
/**
* Stops running java threads by throwing an exception
* up to the first native frame.
*
* @param[in] vm_env a global VM environment
*/
static void vm_shutdown_stop_java_threads(Global_Env * vm_env) {
hythread_t self;
hythread_t * running_threads;
hythread_t native_thread;
hythread_iterator_t it;
VM_thread *vm_thread;
self = hythread_self();
// Get java.lang.ThreadGroup.lock field
JNIEnv *jni_env = jthread_self_vm_thread()->jni_env;
Class* klass = vm_env->java_lang_ThreadGroup_Class;
jobject class_handle = struct_Class_to_jclass(klass);
jfieldID lock_field_id = jni_env->GetStaticFieldID(class_handle, "lock",
"Ljava/lang/ThreadGroup$ThreadGroupLock;");
assert(lock_field_id);
java_lang_ThreadGroup_lock_field =
jni_env->GetStaticObjectField(class_handle, lock_field_id);
assert(java_lang_ThreadGroup_lock_field);
// Collect running java threads.
// Set callbacks to let threads exit
TRACE2("shutdown", "stopping threads, self " << self);
it = hythread_iterator_create(NULL);
running_threads = (hythread_t *)apr_palloc(vm_env->mem_pool,
hythread_iterator_size(it) * sizeof(hythread_t));
int size = 0;
while(native_thread = hythread_iterator_next(&it)) {
vm_thread = jthread_get_vm_thread(native_thread);
if (native_thread != self && vm_thread != NULL) {
hythread_set_safepoint_callback(native_thread,
vm_shutdown_callback);
running_threads[size] = native_thread;
++size;
}
}
hythread_iterator_release(&it);
TRACE2("shutdown", "waiting threads");
for (int i = 0; i < size; i++) {
hythread_sleep(100);
}
TRACE2("shutdown", "cancelling threads");
// Grab java.lang.ThreadGroup.lock
IDATA status = jthread_monitor_enter(java_lang_ThreadGroup_lock_field);
assert(status == TM_ERROR_NONE);
// forcedly kill remaining threads
// There is a small chance that some of these threads are not in the point
// safe for killing, e.g. in malloc()
it = hythread_iterator_create(NULL);
while(native_thread = hythread_iterator_next(&it)) {
vm_thread = jthread_get_vm_thread(native_thread);
// we should not cancel self and
// non-java threads (i.e. vm_thread == NULL)
if (native_thread != self && vm_thread != NULL) {
vm_thread_cancel(vm_thread);
TRACE2("shutdown", "cancelling " << native_thread);
STD_FREE(vm_thread);
}
}
hythread_iterator_release(&it);
// release java.lang.ThreadGroup.lock
status = jthread_monitor_exit(java_lang_ThreadGroup_lock_field);
assert(status == TM_ERROR_NONE);
TRACE2("shutdown", "shutting down threads complete");
}
/**
* A native analogue of <code>java.lang.System.execShutdownSequence</code>.
*/
void exec_native_shutdown_sequence() {
// print out gathered data
#ifdef VM_STATS
VM_Statistics::get_vm_stats().print();
#endif
}
/**
* Waits until all non-daemon threads finish their execution,
* initiates VM shutdown sequence and stops running threads if any.
*
* @param[in] java_vm JVM that should be destroyed
* @param[in] java_thread current java thread
*/
jint vm_destroy(JavaVM_Internal * java_vm, jthread java_thread)
{
IDATA status;
JNIEnv * jni_env;
jobject uncaught_exception;
assert(hythread_is_suspend_enabled());
jni_env = jthread_get_JNI_env(java_thread);
// Wait until all non-daemon threads finish their execution.
status = jthread_wait_for_all_nondaemon_threads();
if (status != TM_ERROR_NONE) {
TRACE("Failed to wait for all non-daemon threads completion.");
return JNI_ERR;
}
// Remember thread's uncaught exception if any.
uncaught_exception = jni_env->ExceptionOccurred();
jni_env->ExceptionClear();
// Execute pending shutdown hooks & finalizers
status = exec_shutdown_sequence(jni_env);
exec_native_shutdown_sequence();
if (status != JNI_OK) return (jint)status;
if(get_native_finalizer_thread_flag())
wait_native_fin_threads_detached();
if(get_native_ref_enqueue_thread_flag())
wait_native_ref_thread_detached();
// Raise uncaught exception to current thread.
// It will be properly processed in jthread_java_detach().
if (uncaught_exception) {
exn_raise_object(uncaught_exception);
}
// Send VM_Death event and switch to DEAD phase.
// This should be the last event sent by VM.
// This event should be sent before Agent_OnUnload called.
jvmti_send_vm_death_event();
// prepare thread manager to shutdown
hythread_shutdowning();
// Call Agent_OnUnload() for agents and unload agents.
// Gregory -
// We cannot call this function after vm_shutdown_stop_java_threads!!!
// In this case agent's thread won't be shutdown properly, and the
// code of agent's thread will run in unmapped address space
// of unloaded agent's library. This is bad and will almost certainly crash.
java_vm->vm_env->TI->Shutdown(java_vm);
// Stop all (except current) java threads
// before destroying VM-wide data.
vm_shutdown_stop_java_threads(java_vm->vm_env);
// TODO: ups we don't stop native threads as well :-((
// We are lucky! Currently, there are no such threads.
// Detach current main thread.
status = jthread_detach(java_thread);
// check detach status
if (status != TM_ERROR_NONE)
return JNI_ERR;
// Shutdown signals
vm_shutdown_signals();
// Block thread creation.
// TODO: investigate how to achieve that with ThreadManager
// Starting this moment any exception occurred in java thread will cause
// entire java stack unwinding to the most recent native frame.
// JNI is not available as well.
assert(java_vm->vm_env->vm_state == Global_Env::VM_RUNNING);
java_vm->vm_env->vm_state = Global_Env::VM_SHUTDOWNING;
TRACE2("shutdown", "vm_destroy complete");
return JNI_OK;
}
static hylatch_t shutdown_end;
static IDATA vm_interrupt_process(void * data) {
IDATA status = hylatch_wait(shutdown_end);
assert(status == TM_ERROR_NONE);
status = hylatch_destroy(shutdown_end);
assert(status == TM_ERROR_NONE);
STD_FREE(data);
// Return 130 to be compatible with RI.
exit(130);
}
/**
* Initiates VM shutdown sequence.
*/
static IDATA vm_interrupt_entry_point(void * data) {
JavaVM * java_vm = (JavaVM *)data;
JavaVMAttachArgs vm_args = {JNI_VERSION_1_2, const_cast<char*>("InterruptionHandler"), NULL};
JNIEnv * jni_env;
jint status = AttachCurrentThread(java_vm, (void **)&jni_env, &vm_args);
if (status == JNI_OK) {
exec_shutdown_sequence(jni_env);
exec_native_shutdown_sequence();
DetachCurrentThread(java_vm);
}
IDATA hy_status = hylatch_count_down(shutdown_end);
assert(hy_status == TM_ERROR_NONE);
return status;
}
/**
* Release allocated resourses.
*/
static IDATA vm_dump_process(void * data) {
IDATA status = hylatch_wait(shutdown_end);
assert(status == TM_ERROR_NONE);
status = hylatch_destroy(shutdown_end);
assert(status == TM_ERROR_NONE);
STD_FREE(data);
return TM_ERROR_NONE;
}
/**
* Dumps all java stacks.
*/
static IDATA vm_dump_entry_point(void * data) {
JavaVM * java_vm = (JavaVM *)data;
JavaVMAttachArgs vm_args = {JNI_VERSION_1_2, const_cast<char*>("DumpHandler"), NULL};
JNIEnv * jni_env;
jint status = AttachCurrentThread(java_vm, (void **)&jni_env, &vm_args);
if (status == JNI_OK) {
// TODO: specify particular VM to notify.
jvmti_notify_data_dump_request();
st_print_all(stdout);
DetachCurrentThread(java_vm);
}
IDATA hy_status = hylatch_count_down(shutdown_end);
assert(hy_status == TM_ERROR_NONE);
return status;
}
/**
* Current process received an interruption signal (Ctrl+C pressed).
* Shutdown all running VMs and terminate the process.
*/
void vm_interrupt_handler() {
int nVMs;
IDATA status = JNI_GetCreatedJavaVMs(NULL, 0, &nVMs);
assert(nVMs <= 1);
if (status != JNI_OK) {
return;
}
JavaVM ** vmBuf = (JavaVM **) STD_MALLOC(nVMs * sizeof(JavaVM *)
+ (nVMs + 1) * sizeof(vm_thread_t));
assert(vmBuf);
status = JNI_GetCreatedJavaVMs(vmBuf, nVMs, &nVMs);
assert(nVMs <= 1);
if (status != JNI_OK) {
STD_FREE(vmBuf);
return;
}
vm_thread_t *threadBuf = (vm_thread_t*)((char*)vmBuf + (nVMs * sizeof(JavaVM *)));
status = hylatch_create(&shutdown_end, nVMs);
assert(status == TM_ERROR_NONE);
// Create a new thread for each VM to avoid scalability and deadlock problems.
for (int i = 0; i < nVMs; i++) {
threadBuf[i] = jthread_allocate_thread();
assert(threadBuf[i]);
status = hythread_create_ex((hythread_t)threadBuf[i], NULL, 0, 0, NULL,
vm_interrupt_entry_point, (void *)vmBuf[i]);
assert(status == TM_ERROR_NONE);
}
// spawn a new thread which will terminate the process.
status = hythread_create(NULL, 0, 0, 0,
vm_interrupt_process, (void *)vmBuf);
assert(status == TM_ERROR_NONE);
// set a NULL terminator
threadBuf[nVMs] = NULL;
}
/**
* Current process received an SIGQUIT signal (Linux) or Ctrl+Break (Windows).
* Prints java stack traces for each VM running in the current process.
*/
void vm_dump_handler() {
int nVMs;
jint status = JNI_GetCreatedJavaVMs(NULL, 0, &nVMs);
assert(nVMs <= 1);
if (status != JNI_OK)
return;
JavaVM** vmBuf = (JavaVM **) STD_MALLOC(nVMs * sizeof(JavaVM *)
+ (nVMs + 1) * sizeof(vm_thread_t));
assert(vmBuf);
status = JNI_GetCreatedJavaVMs(vmBuf, nVMs, &nVMs);
assert(nVMs <= 1);
if (status != JNI_OK) {
STD_FREE(vmBuf);
return;
}
vm_thread_t *threadBuf = (vm_thread_t*)((char*)vmBuf + (nVMs * sizeof(JavaVM *)));
status = hylatch_create(&shutdown_end, nVMs);
assert(status == TM_ERROR_NONE);
// Create a new thread for each VM to avoid scalability and deadlock problems.
IDATA UNUSED hy_status;
for (int i = 0; i < nVMs; i++) {
threadBuf[i] = jthread_allocate_thread();
assert(threadBuf[i]);
hy_status = hythread_create_ex((hythread_t)threadBuf[i],
NULL, 0, 0, NULL, vm_dump_entry_point, (void *)vmBuf[i]);
assert(hy_status == TM_ERROR_NONE);
}
// set buf end marker
threadBuf[nVMs] = NULL;
// spawn a new thread which will release resources.
hy_status = hythread_create(NULL, 0, 0, 0,
vm_dump_process, (void *)vmBuf);
assert(hy_status == TM_ERROR_NONE);
}