8283044: Use asynchronous handshakes to deliver asynchronous exceptions

Reviewed-by: dcubed, dholmes, rehn
This commit is contained in:
Patricio Chilano Mateo 2022-04-04 14:00:26 +00:00
parent 9d200d6e7a
commit 4e20a03786
20 changed files with 772 additions and 262 deletions

View file

@ -549,7 +549,9 @@ JNI_END
static void jni_check_async_exceptions(JavaThread *thread) { static void jni_check_async_exceptions(JavaThread *thread) {
assert(thread == Thread::current(), "must be itself"); assert(thread == Thread::current(), "must be itself");
thread->check_and_handle_async_exceptions(); if (thread->has_async_exception_condition()) {
SafepointMechanism::process_if_requested_with_exit_check(thread, true /* check asyncs */);
}
} }
JNI_ENTRY_NO_PRESERVE(jthrowable, jni_ExceptionOccurred(JNIEnv *env)) JNI_ENTRY_NO_PRESERVE(jthrowable, jni_ExceptionOccurred(JNIEnv *env))

View file

@ -24,6 +24,8 @@
#include "precompiled.hpp" #include "precompiled.hpp"
#include "jvm_io.h" #include "jvm_io.h"
#include "classfile/javaClasses.hpp"
#include "classfile/vmSymbols.hpp"
#include "logging/log.hpp" #include "logging/log.hpp"
#include "logging/logStream.hpp" #include "logging/logStream.hpp"
#include "memory/resourceArea.hpp" #include "memory/resourceArea.hpp"
@ -78,6 +80,8 @@ class HandshakeOperation : public CHeapObj<mtThread> {
const char* name() { return _handshake_cl->name(); } const char* name() { return _handshake_cl->name(); }
bool is_async() { return _handshake_cl->is_async(); } bool is_async() { return _handshake_cl->is_async(); }
bool is_suspend() { return _handshake_cl->is_suspend(); } bool is_suspend() { return _handshake_cl->is_suspend(); }
bool is_async_exception() { return _handshake_cl->is_async_exception(); }
bool is_ThreadDeath() { return _handshake_cl->is_ThreadDeath(); }
}; };
class AsyncHandshakeOperation : public HandshakeOperation { class AsyncHandshakeOperation : public HandshakeOperation {
@ -313,7 +317,6 @@ void HandshakeOperation::do_handshake(JavaThread* thread) {
// Only actually execute the operation for non terminated threads. // Only actually execute the operation for non terminated threads.
if (!thread->is_terminated()) { if (!thread->is_terminated()) {
NoSafepointVerifier nsv;
_handshake_cl->do_thread(thread); _handshake_cl->do_thread(thread);
} }
@ -426,6 +429,7 @@ HandshakeState::HandshakeState(JavaThread* target) :
_queue(), _queue(),
_lock(Monitor::nosafepoint, "HandshakeState_lock"), _lock(Monitor::nosafepoint, "HandshakeState_lock"),
_active_handshaker(), _active_handshaker(),
_async_exceptions_blocked(false),
_suspended(false), _suspended(false),
_async_suspend_handshake(false) _async_suspend_handshake(false)
{ {
@ -443,39 +447,61 @@ bool HandshakeState::operation_pending(HandshakeOperation* op) {
return _queue.contains(mo); return _queue.contains(mo);
} }
static bool no_suspend_filter(HandshakeOperation* op) { // Filters
return !op->is_suspend(); static bool non_self_executable_filter(HandshakeOperation* op) {
return !op->is_async();
}
static bool no_async_exception_filter(HandshakeOperation* op) {
return !op->is_async_exception();
}
static bool async_exception_filter(HandshakeOperation* op) {
return op->is_async_exception();
}
static bool is_ThreadDeath_filter(HandshakeOperation* op) {
return op->is_ThreadDeath();
}
static bool no_suspend_no_async_exception_filter(HandshakeOperation* op) {
return !op->is_suspend() && !op->is_async_exception();
} }
HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend) { HandshakeOperation* HandshakeState::get_op_for_self(bool allow_suspend, bool check_async_exception) {
assert(_handshakee == Thread::current(), "Must be called by self"); assert(_handshakee == Thread::current(), "Must be called by self");
assert(_lock.owned_by_self(), "Lock must be held"); assert(_lock.owned_by_self(), "Lock must be held");
if (allow_suspend) { assert(allow_suspend || !check_async_exception, "invalid case");
if (!allow_suspend) {
return _queue.peek(no_suspend_no_async_exception_filter);
} else if (check_async_exception && !_async_exceptions_blocked) {
return _queue.peek(); return _queue.peek();
} else { } else {
return _queue.peek(no_suspend_filter); return _queue.peek(no_async_exception_filter);
} }
} }
static bool non_self_queue_filter(HandshakeOperation* op) { bool HandshakeState::has_operation(bool allow_suspend, bool check_async_exception) {
return !op->is_async(); MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
return get_op_for_self(allow_suspend, check_async_exception) != NULL;
}
bool HandshakeState::has_async_exception_operation(bool ThreadDeath_only) {
if (!has_operation()) return false;
MutexLocker ml(_lock.owned_by_self() ? NULL : &_lock, Mutex::_no_safepoint_check_flag);
if (!ThreadDeath_only) {
return _queue.peek(async_exception_filter) != NULL;
} else {
return _queue.peek(is_ThreadDeath_filter) != NULL;
}
} }
bool HandshakeState::have_non_self_executable_operation() { bool HandshakeState::have_non_self_executable_operation() {
assert(_handshakee != Thread::current(), "Must not be called by self"); assert(_handshakee != Thread::current(), "Must not be called by self");
assert(_lock.owned_by_self(), "Lock must be held"); assert(_lock.owned_by_self(), "Lock must be held");
return _queue.contains(non_self_queue_filter); return _queue.contains(non_self_executable_filter);
}
bool HandshakeState::has_a_non_suspend_operation() {
MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
return _queue.contains(no_suspend_filter);
} }
HandshakeOperation* HandshakeState::get_op() { HandshakeOperation* HandshakeState::get_op() {
assert(_handshakee != Thread::current(), "Must not be called by self"); assert(_handshakee != Thread::current(), "Must not be called by self");
assert(_lock.owned_by_self(), "Lock must be held"); assert(_lock.owned_by_self(), "Lock must be held");
return _queue.peek(non_self_queue_filter); return _queue.peek(non_self_executable_filter);
}; };
void HandshakeState::remove_op(HandshakeOperation* op) { void HandshakeState::remove_op(HandshakeOperation* op) {
@ -485,7 +511,7 @@ void HandshakeState::remove_op(HandshakeOperation* op) {
assert(ret == op, "Popped op must match requested op"); assert(ret == op, "Popped op must match requested op");
}; };
bool HandshakeState::process_by_self(bool allow_suspend) { bool HandshakeState::process_by_self(bool allow_suspend, bool check_async_exception) {
assert(Thread::current() == _handshakee, "should call from _handshakee"); assert(Thread::current() == _handshakee, "should call from _handshakee");
assert(!_handshakee->is_terminated(), "should not be a terminated thread"); assert(!_handshakee->is_terminated(), "should not be a terminated thread");
@ -493,15 +519,12 @@ bool HandshakeState::process_by_self(bool allow_suspend) {
// Threads shouldn't block if they are in the middle of printing, but... // Threads shouldn't block if they are in the middle of printing, but...
ttyLocker::break_tty_lock_for_safepoint(os::current_thread_id()); ttyLocker::break_tty_lock_for_safepoint(os::current_thread_id());
// Handshakes cannot safely safepoint.
// The exception to this rule is the asynchronous suspension handshake.
// It by-passes the NSV by manually doing the transition.
NoSafepointVerifier nsv;
while (has_operation()) { while (has_operation()) {
// Handshakes cannot safely safepoint. The exceptions to this rule are
// the asynchronous suspension and unsafe access error handshakes.
MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag); MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
HandshakeOperation* op = get_op_for_self(allow_suspend); HandshakeOperation* op = get_op_for_self(allow_suspend, check_async_exception);
if (op != NULL) { if (op != NULL) {
assert(op->_target == NULL || op->_target == Thread::current(), "Wrong thread"); assert(op->_target == NULL || op->_target == Thread::current(), "Wrong thread");
bool async = op->is_async(); bool async = op->is_async();
@ -517,8 +540,8 @@ bool HandshakeState::process_by_self(bool allow_suspend) {
// An asynchronous handshake may put the JavaThread in blocked state (safepoint safe). // An asynchronous handshake may put the JavaThread in blocked state (safepoint safe).
// The destructor ~PreserveExceptionMark touches the exception oop so it must not be executed, // The destructor ~PreserveExceptionMark touches the exception oop so it must not be executed,
// since a safepoint may be in-progress when returning from the async handshake. // since a safepoint may be in-progress when returning from the async handshake.
op->do_handshake(_handshakee); // acquire, op removed after
remove_op(op); remove_op(op);
op->do_handshake(_handshakee);
log_handshake_info(((AsyncHandshakeOperation*)op)->start_time(), op->name(), 1, 0, "asynchronous"); log_handshake_info(((AsyncHandshakeOperation*)op)->start_time(), op->name(), 1, 0, "asynchronous");
delete op; delete op;
return true; // Must check for safepoints return true; // Must check for safepoints
@ -730,3 +753,29 @@ bool HandshakeState::resume() {
_lock.notify(); _lock.notify();
return true; return true;
} }
void HandshakeState::handle_unsafe_access_error() {
if (is_suspended()) {
// A suspend handshake was added to the queue after the
// unsafe access error. Since the suspender has already
// considered this JT as suspended and assumes it won't go
// back to Java until resumed we cannot create the exception
// object yet. Add a new unsafe access error operation to
// the end of the queue and try again in the next attempt.
Handshake::execute(new UnsafeAccessErrorHandshake(), _handshakee);
log_info(handshake)("JavaThread " INTPTR_FORMAT " skipping unsafe access processing due to suspend.", p2i(_handshakee));
return;
}
// Release the handshake lock before constructing the oop to
// avoid deadlocks since that can block. This will allow the
// JavaThread to execute normally as if it was outside a handshake.
// We will reacquire the handshake lock at return from ~MutexUnlocker.
MutexUnlocker ml(&_lock, Mutex::_no_safepoint_check_flag);
// We may be at method entry which requires we save the do-not-unlock flag.
UnlockFlagSaver fs(_handshakee);
Handle h_exception = Exceptions::new_exception(_handshakee, vmSymbols::java_lang_InternalError(), "a fault occurred in an unsafe memory access operation");
if (h_exception()->is_a(vmClasses::InternalError_klass())) {
java_lang_InternalError::set_during_unsafe_access(h_exception());
}
_handshakee->handle_async_exception(h_exception());
}

View file

@ -33,9 +33,11 @@
#include "utilities/filterQueue.hpp" #include "utilities/filterQueue.hpp"
class HandshakeOperation; class HandshakeOperation;
class AsyncHandshakeOperation;
class JavaThread; class JavaThread;
class SuspendThreadHandshake; class SuspendThreadHandshake;
class ThreadSelfSuspensionHandshake; class ThreadSelfSuspensionHandshake;
class UnsafeAccessErrorHandshake;
class ThreadsListHandle; class ThreadsListHandle;
// A handshake closure is a callback that is executed for a JavaThread // A handshake closure is a callback that is executed for a JavaThread
@ -51,6 +53,8 @@ class HandshakeClosure : public ThreadClosure, public CHeapObj<mtThread> {
const char* name() const { return _name; } const char* name() const { return _name; }
virtual bool is_async() { return false; } virtual bool is_async() { return false; }
virtual bool is_suspend() { return false; } virtual bool is_suspend() { return false; }
virtual bool is_async_exception() { return false; }
virtual bool is_ThreadDeath() { return false; }
virtual void do_thread(Thread* thread) = 0; virtual void do_thread(Thread* thread) = 0;
}; };
@ -87,6 +91,7 @@ class JvmtiRawMonitor;
class HandshakeState { class HandshakeState {
friend ThreadSelfSuspensionHandshake; friend ThreadSelfSuspensionHandshake;
friend SuspendThreadHandshake; friend SuspendThreadHandshake;
friend UnsafeAccessErrorHandshake;
friend JavaThread; friend JavaThread;
// This a back reference to the JavaThread, // This a back reference to the JavaThread,
// the target for all operation in the queue. // the target for all operation in the queue.
@ -104,7 +109,7 @@ class HandshakeState {
bool can_process_handshake(); bool can_process_handshake();
bool have_non_self_executable_operation(); bool have_non_self_executable_operation();
HandshakeOperation* get_op_for_self(bool allow_suspend); HandshakeOperation* get_op_for_self(bool allow_suspend, bool check_async_exception);
HandshakeOperation* get_op(); HandshakeOperation* get_op();
void remove_op(HandshakeOperation* op); void remove_op(HandshakeOperation* op);
@ -124,17 +129,16 @@ class HandshakeState {
void add_operation(HandshakeOperation* op); void add_operation(HandshakeOperation* op);
bool has_operation() { bool has_operation() { return !_queue.is_empty(); }
return !_queue.is_empty(); bool has_operation(bool allow_suspend, bool check_async_exception);
} bool has_async_exception_operation(bool ThreadDeath_only);
bool has_a_non_suspend_operation();
bool operation_pending(HandshakeOperation* op); bool operation_pending(HandshakeOperation* op);
// If the method returns true we need to check for a possible safepoint. // If the method returns true we need to check for a possible safepoint.
// This is due to a suspension handshake which put the JavaThread in blocked // This is due to a suspension handshake which put the JavaThread in blocked
// state so a safepoint may be in-progress. // state so a safepoint may be in-progress.
bool process_by_self(bool allow_suspend); bool process_by_self(bool allow_suspend, bool check_async_exception);
enum ProcessResult { enum ProcessResult {
_no_operation = 0, _no_operation = 0,
@ -148,6 +152,14 @@ class HandshakeState {
Thread* active_handshaker() const { return Atomic::load(&_active_handshaker); } Thread* active_handshaker() const { return Atomic::load(&_active_handshaker); }
// Support for asynchronous exceptions
private:
bool _async_exceptions_blocked;
bool async_exceptions_blocked() { return _async_exceptions_blocked; }
void set_async_exceptions_blocked(bool b) { _async_exceptions_blocked = b; }
void handle_unsafe_access_error();
// Suspend/resume support // Suspend/resume support
private: private:
// This flag is true when the thread owning this // This flag is true when the thread owning this

View file

@ -210,7 +210,7 @@ class ThreadBlockInVMPreprocess : public ThreadStateTransition {
if (SafepointMechanism::should_process(_thread, _allow_suspend)) { if (SafepointMechanism::should_process(_thread, _allow_suspend)) {
_pr(_thread); _pr(_thread);
SafepointMechanism::process_if_requested(_thread, _allow_suspend); SafepointMechanism::process_if_requested(_thread, _allow_suspend, false /* check_async_exception */);
} }
} }
}; };

View file

@ -1897,7 +1897,10 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
// This is in keeping with the "no loitering in runtime" rule. // This is in keeping with the "no loitering in runtime" rule.
// We periodically check to see if there's a safepoint pending. // We periodically check to see if there's a safepoint pending.
if ((ctr & 0xFF) == 0) { if ((ctr & 0xFF) == 0) {
if (SafepointMechanism::should_process(current)) { // Can't call SafepointMechanism::should_process() since that
// might update the poll values and we could be in a thread_blocked
// state here which is not allowed so just check the poll.
if (SafepointMechanism::local_poll_armed(current)) {
goto Abort; // abrupt spin egress goto Abort; // abrupt spin egress
} }
SpinPause(); SpinPause();

View file

@ -963,33 +963,34 @@ void ThreadSafepointState::handle_polling_page_exception() {
set_at_poll_safepoint(true); set_at_poll_safepoint(true);
// Process pending operation // Process pending operation
// We never deliver an async exception at a polling point as the // We never deliver an async exception at a polling point as the
// compiler may not have an exception handler for it. The polling // compiler may not have an exception handler for it (polling at
// code will notice the pending async exception, deoptimize and // a return point is ok though). We will check for a pending async
// the exception will be delivered. (Polling at a return point // exception below and deoptimize if needed. We also cannot deoptimize
// is ok though). Sure is a lot of bother for a deprecated feature... // and still install the exception here because live registers needed
// during deoptimization are clobbered by the exception path. The
// exception will just be delivered once we get into the interpreter.
SafepointMechanism::process_if_requested_with_exit_check(self, false /* check asyncs */); SafepointMechanism::process_if_requested_with_exit_check(self, false /* check asyncs */);
set_at_poll_safepoint(false); set_at_poll_safepoint(false);
// If we have a pending async exception deoptimize the frame
// as otherwise we may never deliver it.
if (self->has_async_exception_condition()) { if (self->has_async_exception_condition()) {
Deoptimization::deoptimize_frame(self, caller_fr.id()); Deoptimization::deoptimize_frame(self, caller_fr.id());
log_info(exceptions)("deferred async exception at compiled safepoint");
} }
// If an exception has been installed we must check for a pending deoptimization // If an exception has been installed we must verify that the top frame wasn't deoptimized.
// Deoptimize frame if exception has been thrown.
if (self->has_pending_exception() ) { if (self->has_pending_exception() ) {
RegisterMap map(self, true, false); RegisterMap map(self, true, false);
frame caller_fr = stub_fr.sender(&map); frame caller_fr = stub_fr.sender(&map);
if (caller_fr.is_deoptimized_frame()) { if (caller_fr.is_deoptimized_frame()) {
// The exception patch will destroy registers that are still // The exception path will destroy registers that are still
// live and will be needed during deoptimization. Defer the // live and will be needed during deoptimization, so if we
// Async exception should have deferred the exception until the // have an exception now things are messed up. We only check
// next safepoint which will be detected when we get into // at this scope because for a poll return it is ok to deoptimize
// the interpreter so if we have an exception now things // while having a pending exception since the call we are returning
// are messed up. // from already collides with exception handling registers and
// so there is no issue (the exception handling path kills call
// result registers but this is ok since the exception kills
// the result anyway).
fatal("Exception installed and deoptimization is pending"); fatal("Exception installed and deoptimization is pending");
} }
} }

View file

@ -111,7 +111,7 @@ void SafepointMechanism::update_poll_values(JavaThread* thread) {
} }
} }
void SafepointMechanism::process(JavaThread *thread, bool allow_suspend) { void SafepointMechanism::process(JavaThread *thread, bool allow_suspend, bool check_async_exception) {
// Read global poll and has_handshake after local poll // Read global poll and has_handshake after local poll
OrderAccess::loadload(); OrderAccess::loadload();
@ -135,7 +135,7 @@ void SafepointMechanism::process(JavaThread *thread, bool allow_suspend) {
// 3) Before the handshake code is run // 3) Before the handshake code is run
StackWatermarkSet::on_safepoint(thread); StackWatermarkSet::on_safepoint(thread);
need_rechecking = thread->handshake_state()->has_operation() && thread->handshake_state()->process_by_self(allow_suspend); need_rechecking = thread->handshake_state()->has_operation() && thread->handshake_state()->process_by_self(allow_suspend, check_async_exception);
} while (need_rechecking); } while (need_rechecking);
update_poll_values(thread); update_poll_values(thread);

View file

@ -49,7 +49,7 @@ class SafepointMechanism : public AllStatic {
static inline bool global_poll(); static inline bool global_poll();
static void process(JavaThread *thread, bool allow_suspend); static void process(JavaThread *thread, bool allow_suspend, bool check_async_exception);
static void default_initialize(); static void default_initialize();
@ -80,8 +80,8 @@ class SafepointMechanism : public AllStatic {
static inline bool should_process(JavaThread* thread, bool allow_suspend = true); static inline bool should_process(JavaThread* thread, bool allow_suspend = true);
// Processes a pending requested operation. // Processes a pending requested operation.
static inline void process_if_requested(JavaThread* thread, bool allow_suspend = true); static inline void process_if_requested(JavaThread* thread, bool allow_suspend, bool check_async_exception);
static inline void process_if_requested_with_exit_check(JavaThread* thread, bool check_asyncs); static inline void process_if_requested_with_exit_check(JavaThread* thread, bool check_async_exception);
// Compute what the poll values should be and install them. // Compute what the poll values should be and install them.
static void update_poll_values(JavaThread* thread); static void update_poll_values(JavaThread* thread);

View file

@ -66,19 +66,17 @@ bool SafepointMechanism::global_poll() {
bool SafepointMechanism::should_process(JavaThread* thread, bool allow_suspend) { bool SafepointMechanism::should_process(JavaThread* thread, bool allow_suspend) {
if (!local_poll_armed(thread)) { if (!local_poll_armed(thread)) {
return false; return false;
} else if (allow_suspend) {
return true;
} }
// We are armed but we should ignore suspend operations.
if (global_poll() || // Safepoint if (global_poll() || // Safepoint
thread->handshake_state()->has_a_non_suspend_operation() || // Non-suspend handshake thread->handshake_state()->has_operation(allow_suspend, false /* check_async_exception */) || // Handshake
!StackWatermarkSet::processing_started(thread)) { // StackWatermark processing is not started !StackWatermarkSet::processing_started(thread)) { // StackWatermark processing is not started
return true; return true;
} }
// It has boiled down to two possibilities: // It has boiled down to two possibilities:
// 1: We have nothing to process, this just a disarm poll. // 1: We have nothing to process, this just a disarm poll.
// 2: We have a suspend handshake, which cannot be processed. // 2: We have a suspend or async exception handshake, which cannot be processed.
// We update the poll value in case of a disarm, to reduce false positives. // We update the poll value in case of a disarm, to reduce false positives.
update_poll_values(thread); update_poll_values(thread);
@ -88,19 +86,19 @@ bool SafepointMechanism::should_process(JavaThread* thread, bool allow_suspend)
return false; return false;
} }
void SafepointMechanism::process_if_requested(JavaThread* thread, bool allow_suspend) { void SafepointMechanism::process_if_requested(JavaThread* thread, bool allow_suspend, bool check_async_exception) {
// Check NoSafepointVerifier. This also clears unhandled oops if CheckUnhandledOops is used. // Check NoSafepointVerifier. This also clears unhandled oops if CheckUnhandledOops is used.
thread->check_possible_safepoint(); thread->check_possible_safepoint();
if (local_poll_armed(thread)) { if (local_poll_armed(thread)) {
process(thread, allow_suspend); process(thread, allow_suspend, check_async_exception);
} }
} }
void SafepointMechanism::process_if_requested_with_exit_check(JavaThread* thread, bool check_asyncs) { void SafepointMechanism::process_if_requested_with_exit_check(JavaThread* thread, bool check_async_exception) {
process_if_requested(thread); process_if_requested(thread, true /* allow_suspend */, check_async_exception);
if (thread->has_special_runtime_exit_condition()) { if (thread->has_special_runtime_exit_condition()) {
thread->handle_special_runtime_exit_condition(check_asyncs); thread->handle_special_runtime_exit_condition();
} }
} }

View file

@ -266,7 +266,7 @@ void NMethodSweeper::force_sweep() {
*/ */
void NMethodSweeper::handle_safepoint_request() { void NMethodSweeper::handle_safepoint_request() {
JavaThread* thread = JavaThread::current(); JavaThread* thread = JavaThread::current();
if (SafepointMechanism::should_process(thread)) { if (SafepointMechanism::local_poll_armed(thread)) {
if (PrintMethodFlushing && Verbose) { if (PrintMethodFlushing && Verbose) {
tty->print_cr("### Sweep at %d out of %d, yielding to safepoint", _seen, CodeCache::nmethod_count()); tty->print_cr("### Sweep at %d out of %d, yielding to safepoint", _seen, CodeCache::nmethod_count());
} }

View file

@ -1014,10 +1014,6 @@ JavaThread::JavaThread() :
_monitor_chunks(nullptr), _monitor_chunks(nullptr),
_suspend_flags(0), _suspend_flags(0),
_pending_async_exception(nullptr),
#ifdef ASSERT
_is_unsafe_access_error(false),
#endif
_thread_state(_thread_new), _thread_state(_thread_new),
_saved_exception_pc(nullptr), _saved_exception_pc(nullptr),
@ -1585,47 +1581,41 @@ void JavaThread::remove_monitor_chunk(MonitorChunk* chunk) {
} }
} }
void JavaThread::handle_special_runtime_exit_condition() {
if (is_obj_deopt_suspend()) {
frame_anchor()->make_walkable(this);
wait_for_object_deoptimization();
}
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(this);)
}
// Asynchronous exceptions support // Asynchronous exceptions support
// //
void JavaThread::check_and_handle_async_exceptions() { void JavaThread::handle_async_exception(oop java_throwable) {
if (has_last_Java_frame() && has_async_exception_condition()) { assert(java_throwable != NULL, "should have an _async_exception to throw");
// If we are at a polling page safepoint (not a poll return) assert(!is_at_poll_safepoint(), "should have never called this method");
// then we must defer async exception because live registers
// will be clobbered by the exception path. Poll return is
// ok because the call we are returning from already collides
// with exception handling registers and so there is no issue.
// (The exception handling path kills call result registers but
// this is ok since the exception kills the result anyway).
if (is_at_poll_safepoint()) { if (has_last_Java_frame()) {
// if the code we are returning to has deoptimized we must defer frame f = last_frame();
// the exception otherwise live registers get clobbered on the if (f.is_runtime_frame()) {
// exception path before deoptimization is able to retrieve them. // If the topmost frame is a runtime stub, then we are calling into
// // OptoRuntime from compiled code. Some runtime stubs (new, monitor_exit..)
RegisterMap map(this, false); // must deoptimize the caller before continuing, as the compiled exception
frame caller_fr = last_frame().sender(&map); // handler table may not be valid.
assert(caller_fr.is_compiled_frame(), "what?"); RegisterMap reg_map(this, false);
if (caller_fr.is_deoptimized_frame()) { frame compiled_frame = f.sender(&reg_map);
log_info(exceptions)("deferred async exception at compiled safepoint"); if (!StressCompiledExceptionHandlers && compiled_frame.can_be_deoptimized()) {
return; Deoptimization::deoptimize(this, compiled_frame);
} }
} }
} }
if (!clear_async_exception_condition()) { // Only overwrite an already pending exception if it is not a ThreadDeath.
if ((_suspend_flags & _async_delivery_disabled) != 0) {
log_info(exceptions)("Async exception delivery is disabled");
}
return;
}
if (_pending_async_exception != NULL) {
// Only overwrite an already pending exception if it is not a threadDeath.
if (!has_pending_exception() || !pending_exception()->is_a(vmClasses::ThreadDeath_klass())) { if (!has_pending_exception() || !pending_exception()->is_a(vmClasses::ThreadDeath_klass())) {
// We cannot call Exceptions::_throw(...) here because we cannot block // We cannot call Exceptions::_throw(...) here because we cannot block
set_pending_exception(_pending_async_exception, __FILE__, __LINE__); set_pending_exception(java_throwable, __FILE__, __LINE__);
LogTarget(Info, exceptions) lt; LogTarget(Info, exceptions) lt;
if (lt.is_enabled()) { if (lt.is_enabled()) {
@ -1636,108 +1626,61 @@ void JavaThread::check_and_handle_async_exceptions() {
frame f = last_frame(); frame f = last_frame();
ls.print(" (pc: " INTPTR_FORMAT " sp: " INTPTR_FORMAT " )", p2i(f.pc()), p2i(f.sp())); ls.print(" (pc: " INTPTR_FORMAT " sp: " INTPTR_FORMAT " )", p2i(f.pc()), p2i(f.sp()));
} }
ls.print_cr(" of type: %s", _pending_async_exception->klass()->external_name()); ls.print_cr(" of type: %s", java_throwable->klass()->external_name());
} }
} }
// Always null out the _pending_async_exception oop here since the async condition was
// already cleared above and thus considered handled.
_pending_async_exception = NULL;
} else {
assert(_is_unsafe_access_error, "must be");
DEBUG_ONLY(_is_unsafe_access_error = false);
// We may be at method entry which requires we save the do-not-unlock flag.
UnlockFlagSaver fs(this);
Exceptions::throw_unsafe_access_internal_error(this, __FILE__, __LINE__, "a fault occurred in an unsafe memory access operation");
// We might have blocked in a ThreadBlockInVM wrapper in the call above so make sure we process pending
// suspend requests and object reallocation operations if any since we might be going to Java after this.
SafepointMechanism::process_if_requested_with_exit_check(this, true /* check asyncs */);
}
} }
void JavaThread::handle_special_runtime_exit_condition(bool check_asyncs) { void JavaThread::install_async_exception(AsyncExceptionHandshake* aeh) {
// Do not throw asynchronous exceptions against the compiler thread.
if (is_obj_deopt_suspend()) { if (!can_call_java()) {
frame_anchor()->make_walkable(this); delete aeh;
wait_for_object_deoptimization(); return;
} }
// We might be here for reasons in addition to the self-suspend request // Don't install a new pending async exception if there is already
// so check for other async requests. // a pending ThreadDeath one. Just interrupt thread from potential
if (check_asyncs) { // wait()/sleep()/park() and return.
check_and_handle_async_exceptions(); if (has_async_exception_condition(true /* ThreadDeath_only */)) {
java_lang_Thread::set_interrupted(threadObj(), true);
this->interrupt();
delete aeh;
return;
} }
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(this);) oop exception = aeh->exception();
} Handshake::execute(aeh, this); // Install asynchronous handshake
class InstallAsyncExceptionClosure : public HandshakeClosure {
Handle _throwable; // The Throwable thrown at the target Thread
public:
InstallAsyncExceptionClosure(Handle throwable) : HandshakeClosure("InstallAsyncException"), _throwable(throwable) {}
void do_thread(Thread* thr) {
JavaThread* target = JavaThread::cast(thr);
// Note that this now allows multiple ThreadDeath exceptions to be
// thrown at a thread.
// The target thread has run and has not exited yet.
target->send_thread_stop(_throwable());
}
};
void JavaThread::send_async_exception(JavaThread* target, oop java_throwable) {
Handle throwable(Thread::current(), java_throwable);
InstallAsyncExceptionClosure vm_stop(throwable);
Handshake::execute(&vm_stop, target);
}
void JavaThread::send_thread_stop(oop java_throwable) {
ResourceMark rm; ResourceMark rm;
assert(is_handshake_safe_for(Thread::current()),
"should be self or handshakee");
// Do not throw asynchronous exceptions against the compiler thread
// (the compiler thread should not be a Java thread -- fix in 1.4.2)
if (!can_call_java()) return;
{
// Actually throw the Throwable against the target Thread - however
// only if there is no thread death exception installed already.
if (_pending_async_exception == NULL || !_pending_async_exception->is_a(vmClasses::ThreadDeath_klass())) {
// If the topmost frame is a runtime stub, then we are calling into
// OptoRuntime from compiled code. Some runtime stubs (new, monitor_exit..)
// must deoptimize the caller before continuing, as the compiled exception handler table
// may not be valid
if (has_last_Java_frame()) {
frame f = last_frame();
if (f.is_runtime_frame() || f.is_safepoint_blob_frame()) {
RegisterMap reg_map(this, false);
frame compiled_frame = f.sender(&reg_map);
if (!StressCompiledExceptionHandlers && compiled_frame.can_be_deoptimized()) {
Deoptimization::deoptimize(this, compiled_frame);
}
}
}
// Set async. pending exception in thread.
set_pending_async_exception(java_throwable);
if (log_is_enabled(Info, exceptions)) { if (log_is_enabled(Info, exceptions)) {
ResourceMark rm;
log_info(exceptions)("Pending Async. exception installed of type: %s", log_info(exceptions)("Pending Async. exception installed of type: %s",
InstanceKlass::cast(_pending_async_exception->klass())->external_name()); InstanceKlass::cast(exception->klass())->external_name());
} }
// for AbortVMOnException flag // for AbortVMOnException flag
Exceptions::debug_check_abort(_pending_async_exception->klass()->external_name()); Exceptions::debug_check_abort(exception->klass()->external_name());
}
}
// Interrupt thread so it will wake up from a potential wait()/sleep()/park() // Interrupt thread so it will wake up from a potential wait()/sleep()/park()
java_lang_Thread::set_interrupted(threadObj(), true); java_lang_Thread::set_interrupted(threadObj(), true);
this->interrupt(); this->interrupt();
} }
class InstallAsyncExceptionHandshake : public HandshakeClosure {
AsyncExceptionHandshake* _aeh;
public:
InstallAsyncExceptionHandshake(AsyncExceptionHandshake* aeh) :
HandshakeClosure("InstallAsyncException"), _aeh(aeh) {}
void do_thread(Thread* thr) {
JavaThread* target = JavaThread::cast(thr);
target->install_async_exception(_aeh);
}
};
void JavaThread::send_async_exception(JavaThread* target, oop java_throwable) {
OopHandle e(Universe::vm_global(), java_throwable);
InstallAsyncExceptionHandshake iaeh(new AsyncExceptionHandshake(e));
Handshake::execute(&iaeh, target);
}
// External suspension mechanism. // External suspension mechanism.
// //
@ -1966,7 +1909,6 @@ void JavaThread::oops_do_no_frames(OopClosure* f, CodeBlobClosure* cf) {
// around using this function // around using this function
f->do_oop((oop*) &_vm_result); f->do_oop((oop*) &_vm_result);
f->do_oop((oop*) &_exception_oop); f->do_oop((oop*) &_exception_oop);
f->do_oop((oop*) &_pending_async_exception);
#if INCLUDE_JVMCI #if INCLUDE_JVMCI
f->do_oop((oop*) &_jvmci_reserved_oop0); f->do_oop((oop*) &_jvmci_reserved_oop0);
#endif #endif

View file

@ -68,6 +68,7 @@ class OSThread;
class ThreadStatistics; class ThreadStatistics;
class ConcurrentLocksDump; class ConcurrentLocksDump;
class MonitorInfo; class MonitorInfo;
class AsyncExceptionHandshake;
class vframeArray; class vframeArray;
class vframe; class vframe;
@ -785,14 +786,11 @@ class JavaThread: public Thread {
enum SuspendFlags { enum SuspendFlags {
// NOTE: avoid using the sign-bit as cc generates different test code // NOTE: avoid using the sign-bit as cc generates different test code
// when the sign-bit is used, and sometimes incorrectly - see CR 6398077 // when the sign-bit is used, and sometimes incorrectly - see CR 6398077
_has_async_exception = 0x00000001U, // there is a pending async exception
_async_delivery_disabled = 0x00000002U, // async exception delivery is disabled
_trace_flag = 0x00000004U, // call tracing backend _trace_flag = 0x00000004U, // call tracing backend
_obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent _obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent
}; };
// various suspension related flags - atomically updated // various suspension related flags - atomically updated
// overloaded with async exceptions so that we do a single check when transitioning from native->Java
volatile uint32_t _suspend_flags; volatile uint32_t _suspend_flags;
inline void set_suspend_flag(SuspendFlags f); inline void set_suspend_flag(SuspendFlags f);
@ -806,24 +804,18 @@ class JavaThread: public Thread {
bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; } bool is_trace_suspend() { return (_suspend_flags & _trace_flag) != 0; }
bool is_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; } bool is_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; }
// Asynchronous exceptions support // Asynchronous exception support
private: private:
oop _pending_async_exception; friend class InstallAsyncExceptionHandshake;
#ifdef ASSERT friend class AsyncExceptionHandshake;
bool _is_unsafe_access_error; friend class HandshakeState;
#endif
inline bool clear_async_exception_condition(); void install_async_exception(AsyncExceptionHandshake* aec = NULL);
void handle_async_exception(oop java_throwable);
public: public:
bool has_async_exception_condition() { bool has_async_exception_condition(bool ThreadDeath_only = false);
return (_suspend_flags & _has_async_exception) != 0 &&
(_suspend_flags & _async_delivery_disabled) == 0;
}
inline void set_pending_async_exception(oop e);
inline void set_pending_unsafe_access_error(); inline void set_pending_unsafe_access_error();
static void send_async_exception(JavaThread* jt, oop java_throwable); static void send_async_exception(JavaThread* jt, oop java_throwable);
void send_thread_stop(oop throwable);
void check_and_handle_async_exceptions();
class NoAsyncExceptionDeliveryMark : public StackObj { class NoAsyncExceptionDeliveryMark : public StackObj {
friend JavaThread; friend JavaThread;
@ -1169,13 +1161,10 @@ class JavaThread: public Thread {
// current thread, i.e. reverts optimizations based on escape analysis. // current thread, i.e. reverts optimizations based on escape analysis.
void wait_for_object_deoptimization(); void wait_for_object_deoptimization();
// these next two are also used for self-suspension and async exception support // Support for object deoptimization and JFR suspension
void handle_special_runtime_exit_condition(bool check_asyncs = true); void handle_special_runtime_exit_condition();
// Return true if JavaThread has an asynchronous condition or
// if external suspension is requested.
bool has_special_runtime_exit_condition() { bool has_special_runtime_exit_condition() {
return (_suspend_flags & (_has_async_exception | _obj_deopt JFR_ONLY(| _trace_flag))) != 0; return (_suspend_flags & (_obj_deopt JFR_ONLY(| _trace_flag))) != 0;
} }
// Fast-locking support // Fast-locking support

View file

@ -28,7 +28,11 @@
#include "runtime/thread.hpp" #include "runtime/thread.hpp"
#include "classfile/vmClasses.hpp"
#include "gc/shared/tlab_globals.hpp" #include "gc/shared/tlab_globals.hpp"
#include "memory/universe.hpp"
#include "oops/instanceKlass.hpp"
#include "oops/oopHandle.inline.hpp"
#include "runtime/atomic.hpp" #include "runtime/atomic.hpp"
#include "runtime/nonJavaThread.hpp" #include "runtime/nonJavaThread.hpp"
#include "runtime/orderAccess.hpp" #include "runtime/orderAccess.hpp"
@ -122,31 +126,62 @@ inline void JavaThread::clear_obj_deopt_flag() {
clear_suspend_flag(_obj_deopt); clear_suspend_flag(_obj_deopt);
} }
inline bool JavaThread::clear_async_exception_condition() { class AsyncExceptionHandshake : public AsyncHandshakeClosure {
bool ret = has_async_exception_condition(); OopHandle _exception;
if (ret) { bool _is_ThreadDeath;
clear_suspend_flag(_has_async_exception); public:
AsyncExceptionHandshake(OopHandle& o, const char* name = "AsyncExceptionHandshake")
: AsyncHandshakeClosure(name), _exception(o) {
_is_ThreadDeath = exception()->is_a(vmClasses::ThreadDeath_klass());
} }
return ret;
}
inline void JavaThread::set_pending_async_exception(oop e) { ~AsyncExceptionHandshake() {
_pending_async_exception = e; assert(!_exception.is_empty(), "invariant");
set_suspend_flag(_has_async_exception); _exception.release(Universe::vm_global());
} }
void do_thread(Thread* thr) {
JavaThread* self = JavaThread::cast(thr);
assert(self == JavaThread::current(), "must be");
self->handle_async_exception(exception());
}
oop exception() {
assert(!_exception.is_empty(), "invariant");
return _exception.resolve();
}
bool is_async_exception() { return true; }
bool is_ThreadDeath() { return _is_ThreadDeath; }
};
class UnsafeAccessErrorHandshake : public AsyncHandshakeClosure {
public:
UnsafeAccessErrorHandshake() : AsyncHandshakeClosure("UnsafeAccessErrorHandshake") {}
void do_thread(Thread* thr) {
JavaThread* self = JavaThread::cast(thr);
assert(self == JavaThread::current(), "must be");
self->handshake_state()->handle_unsafe_access_error();
}
bool is_async_exception() { return true; }
};
inline void JavaThread::set_pending_unsafe_access_error() { inline void JavaThread::set_pending_unsafe_access_error() {
set_suspend_flag(_has_async_exception); if (!has_async_exception_condition()) {
DEBUG_ONLY(_is_unsafe_access_error = true); Handshake::execute(new UnsafeAccessErrorHandshake(), this);
}
} }
inline bool JavaThread::has_async_exception_condition(bool ThreadDeath_only) {
return handshake_state()->has_async_exception_operation(ThreadDeath_only);
}
inline JavaThread::NoAsyncExceptionDeliveryMark::NoAsyncExceptionDeliveryMark(JavaThread *t) : _target(t) { inline JavaThread::NoAsyncExceptionDeliveryMark::NoAsyncExceptionDeliveryMark(JavaThread *t) : _target(t) {
assert((_target->_suspend_flags & _async_delivery_disabled) == 0, "Nesting is not supported"); assert(!_target->handshake_state()->async_exceptions_blocked(), "Nesting is not supported");
_target->set_suspend_flag(_async_delivery_disabled); _target->handshake_state()->set_async_exceptions_blocked(true);
} }
inline JavaThread::NoAsyncExceptionDeliveryMark::~NoAsyncExceptionDeliveryMark() { inline JavaThread::NoAsyncExceptionDeliveryMark::~NoAsyncExceptionDeliveryMark() {
_target->clear_suspend_flag(_async_delivery_disabled); _target->handshake_state()->set_async_exceptions_blocked(false);
} }
inline JavaThreadState JavaThread::thread_state() const { inline JavaThreadState JavaThread::thread_state() const {

View file

@ -716,7 +716,6 @@
nonstatic_field(JavaThread, _current_pending_monitor_is_from_java, bool) \ nonstatic_field(JavaThread, _current_pending_monitor_is_from_java, bool) \
volatile_nonstatic_field(JavaThread, _current_waiting_monitor, ObjectMonitor*) \ volatile_nonstatic_field(JavaThread, _current_waiting_monitor, ObjectMonitor*) \
volatile_nonstatic_field(JavaThread, _suspend_flags, uint32_t) \ volatile_nonstatic_field(JavaThread, _suspend_flags, uint32_t) \
nonstatic_field(JavaThread, _pending_async_exception, oop) \
volatile_nonstatic_field(JavaThread, _exception_oop, oop) \ volatile_nonstatic_field(JavaThread, _exception_oop, oop) \
volatile_nonstatic_field(JavaThread, _exception_pc, address) \ volatile_nonstatic_field(JavaThread, _exception_pc, address) \
volatile_nonstatic_field(JavaThread, _is_method_handle_return, int) \ volatile_nonstatic_field(JavaThread, _is_method_handle_return, int) \
@ -2121,12 +2120,6 @@
declare_constant(JVM_CONSTANT_DynamicInError) \ declare_constant(JVM_CONSTANT_DynamicInError) \
declare_constant(JVM_CONSTANT_InternalMax) \ declare_constant(JVM_CONSTANT_InternalMax) \
\ \
/*****************************/ \
/* Thread::SuspendFlags enum */ \
/*****************************/ \
\
declare_constant(JavaThread::_has_async_exception) \
\
/*******************/ \ /*******************/ \
/* JavaThreadState */ \ /* JavaThreadState */ \
/*******************/ \ /*******************/ \

View file

@ -250,12 +250,6 @@ void Exceptions::throw_stack_overflow_exception(JavaThread* THREAD, const char*
_throw(THREAD, file, line, exception); _throw(THREAD, file, line, exception);
} }
void Exceptions::throw_unsafe_access_internal_error(JavaThread* thread, const char* file, int line, const char* message) {
Handle h_exception = new_exception(thread, vmSymbols::java_lang_InternalError(), message);
java_lang_InternalError::set_during_unsafe_access(h_exception());
_throw(thread, file, line, h_exception, message);
}
void Exceptions::fthrow(JavaThread* thread, const char* file, int line, Symbol* h_name, const char* format, ...) { void Exceptions::fthrow(JavaThread* thread, const char* file, int line, Symbol* h_name, const char* format, ...) {
const int max_msg_size = 1024; const int max_msg_size = 1024;
va_list ap; va_list ap;

View file

@ -173,8 +173,6 @@ class Exceptions {
static void throw_stack_overflow_exception(JavaThread* thread, const char* file, int line, const methodHandle& method); static void throw_stack_overflow_exception(JavaThread* thread, const char* file, int line, const methodHandle& method);
static void throw_unsafe_access_internal_error(JavaThread* thread, const char* file, int line, const char* message);
static void wrap_dynamic_exception(bool is_indy, JavaThread* thread); static void wrap_dynamic_exception(bool is_indy, JavaThread* thread);
// Exception counting for error files of interesting exceptions that may have // Exception counting for error files of interesting exceptions that may have

View file

@ -34,8 +34,6 @@ public class Thread extends VMObject {
private static long tlabFieldOffset; private static long tlabFieldOffset;
private static CIntegerField suspendFlagsField; private static CIntegerField suspendFlagsField;
// Thread::SuspendFlags enum constants
private static int HAS_ASYNC_EXCEPTION;
private static AddressField currentPendingMonitorField; private static AddressField currentPendingMonitorField;
private static AddressField currentWaitingMonitorField; private static AddressField currentWaitingMonitorField;
@ -55,7 +53,6 @@ public class Thread extends VMObject {
Type typeJavaThread = db.lookupType("JavaThread"); Type typeJavaThread = db.lookupType("JavaThread");
suspendFlagsField = typeJavaThread.getCIntegerField("_suspend_flags"); suspendFlagsField = typeJavaThread.getCIntegerField("_suspend_flags");
HAS_ASYNC_EXCEPTION = db.lookupIntConstant("JavaThread::_has_async_exception").intValue();
tlabFieldOffset = typeThread.getField("_tlab").getOffset(); tlabFieldOffset = typeThread.getField("_tlab").getOffset();
currentPendingMonitorField = typeJavaThread.getAddressField("_current_pending_monitor"); currentPendingMonitorField = typeJavaThread.getAddressField("_current_pending_monitor");
@ -71,10 +68,6 @@ public class Thread extends VMObject {
return (int) suspendFlagsField.getValue(addr); return (int) suspendFlagsField.getValue(addr);
} }
public boolean hasAsyncException() {
return (suspendFlags() & HAS_ASYNC_EXCEPTION) != 0;
}
public ThreadLocalAllocBuffer tlab() { public ThreadLocalAllocBuffer tlab() {
return new ThreadLocalAllocBuffer(addr.addOffsetTo(tlabFieldOffset)); return new ThreadLocalAllocBuffer(addr.addOffsetTo(tlabFieldOffset));
} }

View file

@ -0,0 +1,200 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8283044
* @summary Stress delivery of asynchronous exceptions while target is at monitorenter
* @build AsyncExceptionOnMonitorEnter
* @run main/othervm AsyncExceptionOnMonitorEnter 0
* @run main/othervm/native -agentlib:AsyncExceptionOnMonitorEnter AsyncExceptionOnMonitorEnter 1
*/
import java.util.concurrent.Semaphore;
public class AsyncExceptionOnMonitorEnter extends Thread {
private final static int DEF_TIME_MAX = 30; // default max # secs to test
private final static String PROG_NAME = "AsyncExceptionOnMonitorEnter";
private static int TEST_MODE = 0;
public static native int createRawMonitor();
public static native int enterRawMonitor();
public static native int exitRawMonitor();
public static native void destroyRawMonitor();
private static Object o1 = new Object();
private static boolean firstWorker = true;
private static Semaphore sem = new Semaphore(0);
@Override
public void run() {
if (TEST_MODE == 0) {
testWithJavaMonitor();
} else {
testWithJVMTIRawMonitor();
}
}
public void testWithJavaMonitor() {
try {
synchronized (o1) {
if (firstWorker) {
firstWorker = false;
sem.release();
}
Thread.sleep(1000);
}
} catch (ThreadDeath td) {
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
}
}
public void testWithJVMTIRawMonitor() {
boolean savedFirst = false;
try {
int retCode = enterRawMonitor();
if (retCode != 0 && firstWorker) {
throw new RuntimeException("error in JVMTI RawMonitorEnter: retCode=" + retCode);
}
if (firstWorker) {
firstWorker = false;
savedFirst = true;
sem.release();
}
Thread.sleep(1000);
retCode = exitRawMonitor();
if (retCode != 0 && savedFirst) {
throw new RuntimeException("error in JVMTI RawMonitorExit: retCode=" + retCode);
}
} catch (ThreadDeath td) {
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
}
}
public static void main(String[] args) {
int timeMax = DEF_TIME_MAX;
try {
if (args.length == 1) {
TEST_MODE = Integer.parseUnsignedInt(args[0]);
} else if (args.length == 2) {
TEST_MODE = Integer.parseUnsignedInt(args[0]);
timeMax = Integer.parseUnsignedInt(args[1]);
}
if (TEST_MODE != 0 && TEST_MODE != 1) {
System.err.println("'" + TEST_MODE + "': invalid mode");
usage();
}
} catch (NumberFormatException nfe) {
System.err.println("'" + args[0] + "': invalid value.");
usage();
}
System.out.println("About to execute for " + timeMax + " seconds.");
long count = 0;
long start_time = System.currentTimeMillis();
while (System.currentTimeMillis() < start_time + (timeMax * 1000)) {
count++;
if (TEST_MODE == 1) {
// Create JVMTI raw monitor that will be used
int retCode = createRawMonitor();
if (retCode != 0) {
throw new RuntimeException("error in JVMTI CreateRawMonitor: retCode=" + retCode);
}
}
AsyncExceptionOnMonitorEnter worker1 = new AsyncExceptionOnMonitorEnter();
AsyncExceptionOnMonitorEnter worker2 = new AsyncExceptionOnMonitorEnter();
try {
// Start firstWorker worker and wait until monitor is acquired
firstWorker = true;
worker1.start();
sem.acquire();
// Start second worker and allow some time for target to block on monitorenter
// before executing Thread.stop()
worker2.start();
Thread.sleep(300);
while (true) {
worker2.stop();
if (TEST_MODE != 1) {
// Don't stop() worker1 with JVMTI raw monitors since if the monitor is
// not released worker2 will deadlock on enter
worker1.stop();
}
if (!worker1.isAlive() && !worker2.isAlive()) {
// Done with Thread.stop() calls since
// threads are not alive.
break;
}
}
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
} catch (NoClassDefFoundError ncdfe) {
// Ignore because we're testing Thread.stop() which can
// cause it. Yes, a NoClassDefFoundError that happens
// in a worker thread can subsequently be seen in the
// main thread.
}
try {
worker1.join();
worker2.join();
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
}
if (TEST_MODE == 1) {
// Destroy JVMTI raw monitor used
destroyRawMonitor();
}
}
System.out.println("Executed " + count + " loops in " + timeMax +
" seconds.");
String cmd = System.getProperty("sun.java.command");
if (cmd != null && !cmd.startsWith("com.sun.javatest.regtest.agent.MainWrapper")) {
// Exit with success in a non-JavaTest environment:
System.exit(0);
}
}
public static void usage() {
System.err.println("Usage: " + PROG_NAME + " [mode [time_max]]");
System.err.println("where:");
System.err.println(" mode 0: Test with Java monitors (default); 1: Test with JVMTI raw monitors");
System.err.println(" time_max max looping time in seconds");
System.err.println(" (default is " + DEF_TIME_MAX +
" seconds)");
System.exit(1);
}
}

View file

@ -0,0 +1,216 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
/**
* @test
* @bug 8283044
* @summary Stress delivery of asynchronous exceptions.
* @library /test/lib /test/hotspot/jtreg
* @build AsyncExceptionTest
* @run driver jdk.test.lib.helpers.ClassFileInstaller sun.hotspot.WhiteBox
* @run main/othervm -Xbootclasspath/a:. -XX:+UnlockDiagnosticVMOptions -XX:+WhiteBoxAPI AsyncExceptionTest
*/
import compiler.testlibrary.CompilerUtils;
import compiler.whitebox.CompilerWhiteBoxTest;
import java.util.concurrent.CountDownLatch;
import sun.hotspot.WhiteBox;
public class AsyncExceptionTest extends Thread {
private final static int DEF_TIME_MAX = 30; // default max # secs to test
private final static String PROG_NAME = "AsyncExceptionTest";
public static final WhiteBox WB = WhiteBox.getWhiteBox();
public CountDownLatch exitSyncObj = new CountDownLatch(1);
public CountDownLatch startSyncObj = new CountDownLatch(1);
private boolean realRun;
private boolean firstEntry = true;
private boolean receivedThreadDeathinInternal1 = false;
private boolean receivedThreadDeathinInternal2 = false;
public void setDontInline(String method) {
java.lang.reflect.Method m;
try {
m = AsyncExceptionTest.class.getMethod(method);
} catch(NoSuchMethodException e) {
throw new RuntimeException("Unexpected: " + e);
}
WB.testSetDontInlineMethod(m, true);
}
public void checkCompLevel(String method) {
int highestLevel = CompilerUtils.getMaxCompilationLevel();
java.lang.reflect.Method m;
try {
m = AsyncExceptionTest.class.getMethod(method);
} catch(NoSuchMethodException e) {
throw new RuntimeException("Unexpected: " + e);
}
int compLevel = WB.getMethodCompilationLevel(m);
while (compLevel < (highestLevel - 1)) {
try {
Thread.sleep(200);
} catch (InterruptedException e) { /* ignored */ }
compLevel = WB.getMethodCompilationLevel(m);
}
}
@Override
public void run() {
try {
setDontInline("internalRun1");
setDontInline("internalRun2");
int callCount = CompilerWhiteBoxTest.THRESHOLD;
while (callCount-- > 0) {
receivedThreadDeathinInternal2 = false;
realRun = false;
internalRun1();
}
checkCompLevel("internalRun1");
checkCompLevel("internalRun2");
receivedThreadDeathinInternal2 = false;
realRun = true;
internalRun1();
} catch (ThreadDeath td) {
throw new RuntimeException("Catched ThreadDeath in run() instead of internalRun2() or internalRun1(). receivedThreadDeathinInternal1=" + receivedThreadDeathinInternal1 + "; receivedThreadDeathinInternal2=" + receivedThreadDeathinInternal2);
} catch (NoClassDefFoundError ncdfe) {
// ignore because we're testing Thread.stop() which can cause it
}
if (receivedThreadDeathinInternal2 == false && receivedThreadDeathinInternal1 == false) {
throw new RuntimeException("Didn't catched ThreadDeath in internalRun2() nor in internalRun1(). receivedThreadDeathinInternal1=" + receivedThreadDeathinInternal1 + "; receivedThreadDeathinInternal2=" + receivedThreadDeathinInternal2);
}
exitSyncObj.countDown();
}
public void internalRun1() {
long start_time = System.currentTimeMillis();
try {
while (!receivedThreadDeathinInternal2) {
internalRun2();
}
} catch (ThreadDeath e) {
receivedThreadDeathinInternal1 = true;
}
}
public void internalRun2() {
try {
Integer myLocalCount = 1;
Integer myLocalCount2 = 1;
if (realRun && firstEntry) {
// Tell main thread we have started.
startSyncObj.countDown();
firstEntry = false;
}
while(myLocalCount > 0) {
if (!realRun) {
receivedThreadDeathinInternal2 = true;
break;
}
myLocalCount2 = (myLocalCount % 3) / 2;
myLocalCount -= 1;
}
} catch (ThreadDeath e) {
receivedThreadDeathinInternal2 = true;
}
}
public static void main(String[] args) {
int timeMax = 0;
if (args.length == 0) {
timeMax = DEF_TIME_MAX;
} else {
try {
timeMax = Integer.parseUnsignedInt(args[0]);
} catch (NumberFormatException nfe) {
System.err.println("'" + args[0] + "': invalid timeMax value.");
usage();
}
}
System.out.println("About to execute for " + timeMax + " seconds.");
long count = 0;
long start_time = System.currentTimeMillis();
while (System.currentTimeMillis() < start_time + (timeMax * 1000)) {
count++;
AsyncExceptionTest thread = new AsyncExceptionTest();
thread.start();
try {
// Wait for the worker thread to get going.
thread.startSyncObj.await();
while (true) {
// Send async exception and wait until it is thrown
thread.stop();
thread.exitSyncObj.await();
Thread.sleep(100);
if (!thread.isAlive()) {
// Done with Thread.stop() calls since
// thread is not alive.
break;
}
}
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
} catch (NoClassDefFoundError ncdfe) {
// Ignore because we're testing Thread.stop() which can
// cause it. Yes, a NoClassDefFoundError that happens
// in a worker thread can subsequently be seen in the
// main thread.
}
try {
thread.join();
} catch (InterruptedException e) {
throw new Error("Unexpected: " + e);
}
}
System.out.println("Executed " + count + " loops in " + timeMax +
" seconds.");
String cmd = System.getProperty("sun.java.command");
if (cmd != null && !cmd.startsWith("com.sun.javatest.regtest.agent.MainWrapper")) {
// Exit with success in a non-JavaTest environment:
System.exit(0);
}
}
public static void usage() {
System.err.println("Usage: " + PROG_NAME + " [time_max]");
System.err.println("where:");
System.err.println(" time_max max looping time in seconds");
System.err.println(" (default is " + DEF_TIME_MAX +
" seconds)");
System.exit(1);
}
}

View file

@ -0,0 +1,85 @@
/*
* Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
#include <string.h>
#include "jvmti.h"
extern "C" {
static jvmtiEnv* jvmti = NULL;
static jrawMonitorID monitor;
JNIEXPORT jint JNICALL
Java_AsyncExceptionOnMonitorEnter_createRawMonitor(JNIEnv *jni, jclass cls) {
jvmtiError err;
char name[32];
sprintf(name, "MyRawMonitor");
err = jvmti->CreateRawMonitor(name, &monitor);
if (err != JVMTI_ERROR_NONE) {
printf("CreateRawMonitor unexpected error: (%d)\n", err);
}
return err;
}
JNIEXPORT jint JNICALL
Java_AsyncExceptionOnMonitorEnter_enterRawMonitor(JNIEnv *jni, jclass cls) {
jvmtiError err;
err = jvmti->RawMonitorEnter(monitor);
if (err != JVMTI_ERROR_NONE) {
printf("RawMonitorEnter unexpected error: (%d)\n", err);
}
return err;
}
JNIEXPORT jint JNICALL
Java_AsyncExceptionOnMonitorEnter_exitRawMonitor(JNIEnv *jni, jclass cls) {
jvmtiError err;
err = jvmti->RawMonitorExit(monitor);
if (err != JVMTI_ERROR_NONE) {
printf("RawMonitorExit unexpected error: (%d)\n", err);
}
return err;
}
JNIEXPORT void JNICALL
Java_AsyncExceptionOnMonitorEnter_destroyRawMonitor(JNIEnv *jni, jclass cls) {
jvmtiError err;
err = jvmti->DestroyRawMonitor(monitor);
// Almost always worker2 will be stopped before being able to release the
// JVMTI monitor so just ignore those errors.
if (err != JVMTI_ERROR_NONE && err != JVMTI_ERROR_NOT_MONITOR_OWNER) {
printf("DestroyRawMonitor unexpected error: (%d)\n", err);
}
}
JNIEXPORT jint JNICALL
Agent_OnLoad(JavaVM *jvm, char *options, void *reserved) {
// create JVMTI environment
if (jvm->GetEnv((void **) (&jvmti), JVMTI_VERSION) != JNI_OK) {
return JNI_ERR;
}
return JNI_OK;
}
}