8283044: Use asynchronous handshakes to deliver asynchronous exceptions
Reviewed-by: dcubed, dholmes, rehn
This commit is contained in:
parent
9d200d6e7a
commit
4e20a03786
@ -549,7 +549,9 @@ JNI_END
|
||||
|
||||
static void jni_check_async_exceptions(JavaThread *thread) {
|
||||
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))
|
||||
|
@ -24,6 +24,8 @@
|
||||
|
||||
#include "precompiled.hpp"
|
||||
#include "jvm_io.h"
|
||||
#include "classfile/javaClasses.hpp"
|
||||
#include "classfile/vmSymbols.hpp"
|
||||
#include "logging/log.hpp"
|
||||
#include "logging/logStream.hpp"
|
||||
#include "memory/resourceArea.hpp"
|
||||
@ -78,6 +80,8 @@ class HandshakeOperation : public CHeapObj<mtThread> {
|
||||
const char* name() { return _handshake_cl->name(); }
|
||||
bool is_async() { return _handshake_cl->is_async(); }
|
||||
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 {
|
||||
@ -313,7 +317,6 @@ void HandshakeOperation::do_handshake(JavaThread* thread) {
|
||||
|
||||
// Only actually execute the operation for non terminated threads.
|
||||
if (!thread->is_terminated()) {
|
||||
NoSafepointVerifier nsv;
|
||||
_handshake_cl->do_thread(thread);
|
||||
}
|
||||
|
||||
@ -426,6 +429,7 @@ HandshakeState::HandshakeState(JavaThread* target) :
|
||||
_queue(),
|
||||
_lock(Monitor::nosafepoint, "HandshakeState_lock"),
|
||||
_active_handshaker(),
|
||||
_async_exceptions_blocked(false),
|
||||
_suspended(false),
|
||||
_async_suspend_handshake(false)
|
||||
{
|
||||
@ -443,39 +447,61 @@ bool HandshakeState::operation_pending(HandshakeOperation* op) {
|
||||
return _queue.contains(mo);
|
||||
}
|
||||
|
||||
static bool no_suspend_filter(HandshakeOperation* op) {
|
||||
return !op->is_suspend();
|
||||
// Filters
|
||||
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(_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();
|
||||
} else {
|
||||
return _queue.peek(no_suspend_filter);
|
||||
return _queue.peek(no_async_exception_filter);
|
||||
}
|
||||
}
|
||||
|
||||
static bool non_self_queue_filter(HandshakeOperation* op) {
|
||||
return !op->is_async();
|
||||
bool HandshakeState::has_operation(bool allow_suspend, bool check_async_exception) {
|
||||
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() {
|
||||
assert(_handshakee != Thread::current(), "Must not be called by self");
|
||||
assert(_lock.owned_by_self(), "Lock must be held");
|
||||
return _queue.contains(non_self_queue_filter);
|
||||
}
|
||||
|
||||
bool HandshakeState::has_a_non_suspend_operation() {
|
||||
MutexLocker ml(&_lock, Mutex::_no_safepoint_check_flag);
|
||||
return _queue.contains(no_suspend_filter);
|
||||
return _queue.contains(non_self_executable_filter);
|
||||
}
|
||||
|
||||
HandshakeOperation* HandshakeState::get_op() {
|
||||
assert(_handshakee != Thread::current(), "Must not be called by self");
|
||||
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) {
|
||||
@ -485,7 +511,7 @@ void HandshakeState::remove_op(HandshakeOperation* 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(!_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...
|
||||
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()) {
|
||||
// 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);
|
||||
|
||||
HandshakeOperation* op = get_op_for_self(allow_suspend);
|
||||
HandshakeOperation* op = get_op_for_self(allow_suspend, check_async_exception);
|
||||
if (op != NULL) {
|
||||
assert(op->_target == NULL || op->_target == Thread::current(), "Wrong thread");
|
||||
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).
|
||||
// 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.
|
||||
op->do_handshake(_handshakee); // acquire, op removed after
|
||||
remove_op(op);
|
||||
op->do_handshake(_handshakee);
|
||||
log_handshake_info(((AsyncHandshakeOperation*)op)->start_time(), op->name(), 1, 0, "asynchronous");
|
||||
delete op;
|
||||
return true; // Must check for safepoints
|
||||
@ -730,3 +753,29 @@ bool HandshakeState::resume() {
|
||||
_lock.notify();
|
||||
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());
|
||||
}
|
||||
|
@ -33,9 +33,11 @@
|
||||
#include "utilities/filterQueue.hpp"
|
||||
|
||||
class HandshakeOperation;
|
||||
class AsyncHandshakeOperation;
|
||||
class JavaThread;
|
||||
class SuspendThreadHandshake;
|
||||
class ThreadSelfSuspensionHandshake;
|
||||
class UnsafeAccessErrorHandshake;
|
||||
class ThreadsListHandle;
|
||||
|
||||
// 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; }
|
||||
virtual bool is_async() { 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;
|
||||
};
|
||||
|
||||
@ -87,6 +91,7 @@ class JvmtiRawMonitor;
|
||||
class HandshakeState {
|
||||
friend ThreadSelfSuspensionHandshake;
|
||||
friend SuspendThreadHandshake;
|
||||
friend UnsafeAccessErrorHandshake;
|
||||
friend JavaThread;
|
||||
// This a back reference to the JavaThread,
|
||||
// the target for all operation in the queue.
|
||||
@ -104,7 +109,7 @@ class HandshakeState {
|
||||
bool can_process_handshake();
|
||||
|
||||
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();
|
||||
void remove_op(HandshakeOperation* op);
|
||||
|
||||
@ -124,17 +129,16 @@ class HandshakeState {
|
||||
|
||||
void add_operation(HandshakeOperation* op);
|
||||
|
||||
bool has_operation() {
|
||||
return !_queue.is_empty();
|
||||
}
|
||||
bool has_a_non_suspend_operation();
|
||||
bool has_operation() { return !_queue.is_empty(); }
|
||||
bool has_operation(bool allow_suspend, bool check_async_exception);
|
||||
bool has_async_exception_operation(bool ThreadDeath_only);
|
||||
|
||||
bool operation_pending(HandshakeOperation* op);
|
||||
|
||||
// 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
|
||||
// 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 {
|
||||
_no_operation = 0,
|
||||
@ -148,6 +152,14 @@ class HandshakeState {
|
||||
|
||||
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
|
||||
private:
|
||||
// This flag is true when the thread owning this
|
||||
|
@ -210,7 +210,7 @@ class ThreadBlockInVMPreprocess : public ThreadStateTransition {
|
||||
|
||||
if (SafepointMechanism::should_process(_thread, _allow_suspend)) {
|
||||
_pr(_thread);
|
||||
SafepointMechanism::process_if_requested(_thread, _allow_suspend);
|
||||
SafepointMechanism::process_if_requested(_thread, _allow_suspend, false /* check_async_exception */);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
@ -1897,7 +1897,10 @@ int ObjectMonitor::TrySpin(JavaThread* current) {
|
||||
// This is in keeping with the "no loitering in runtime" rule.
|
||||
// We periodically check to see if there's a safepoint pending.
|
||||
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
|
||||
}
|
||||
SpinPause();
|
||||
|
@ -963,33 +963,34 @@ void ThreadSafepointState::handle_polling_page_exception() {
|
||||
set_at_poll_safepoint(true);
|
||||
// Process pending operation
|
||||
// We never deliver an async exception at a polling point as the
|
||||
// compiler may not have an exception handler for it. The polling
|
||||
// code will notice the pending async exception, deoptimize and
|
||||
// the exception will be delivered. (Polling at a return point
|
||||
// is ok though). Sure is a lot of bother for a deprecated feature...
|
||||
// compiler may not have an exception handler for it (polling at
|
||||
// a return point is ok though). We will check for a pending async
|
||||
// exception below and deoptimize if needed. We also cannot deoptimize
|
||||
// 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 */);
|
||||
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()) {
|
||||
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
|
||||
// Deoptimize frame if exception has been thrown.
|
||||
|
||||
// If an exception has been installed we must verify that the top frame wasn't deoptimized.
|
||||
if (self->has_pending_exception() ) {
|
||||
RegisterMap map(self, true, false);
|
||||
frame caller_fr = stub_fr.sender(&map);
|
||||
if (caller_fr.is_deoptimized_frame()) {
|
||||
// The exception patch will destroy registers that are still
|
||||
// live and will be needed during deoptimization. Defer the
|
||||
// Async exception should have deferred the exception until the
|
||||
// next safepoint which will be detected when we get into
|
||||
// the interpreter so if we have an exception now things
|
||||
// are messed up.
|
||||
|
||||
// The exception path will destroy registers that are still
|
||||
// live and will be needed during deoptimization, so if we
|
||||
// have an exception now things are messed up. We only check
|
||||
// at this scope because for a poll return it is ok to deoptimize
|
||||
// while having a pending exception since 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).
|
||||
fatal("Exception installed and deoptimization is pending");
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
OrderAccess::loadload();
|
||||
|
||||
@ -135,7 +135,7 @@ void SafepointMechanism::process(JavaThread *thread, bool allow_suspend) {
|
||||
// 3) Before the handshake code is run
|
||||
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);
|
||||
|
||||
update_poll_values(thread);
|
||||
|
@ -49,7 +49,7 @@ class SafepointMechanism : public AllStatic {
|
||||
|
||||
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();
|
||||
|
||||
@ -80,8 +80,8 @@ class SafepointMechanism : public AllStatic {
|
||||
static inline bool should_process(JavaThread* thread, bool allow_suspend = true);
|
||||
|
||||
// Processes a pending requested operation.
|
||||
static inline void process_if_requested(JavaThread* thread, bool allow_suspend = true);
|
||||
static inline void process_if_requested_with_exit_check(JavaThread* thread, bool check_asyncs);
|
||||
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_async_exception);
|
||||
// Compute what the poll values should be and install them.
|
||||
static void update_poll_values(JavaThread* thread);
|
||||
|
||||
|
@ -66,19 +66,17 @@ bool SafepointMechanism::global_poll() {
|
||||
bool SafepointMechanism::should_process(JavaThread* thread, bool allow_suspend) {
|
||||
if (!local_poll_armed(thread)) {
|
||||
return false;
|
||||
} else if (allow_suspend) {
|
||||
return true;
|
||||
}
|
||||
// We are armed but we should ignore suspend operations.
|
||||
|
||||
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
|
||||
return true;
|
||||
}
|
||||
|
||||
// It has boiled down to two possibilities:
|
||||
// 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.
|
||||
update_poll_values(thread);
|
||||
|
||||
@ -88,19 +86,19 @@ bool SafepointMechanism::should_process(JavaThread* thread, bool allow_suspend)
|
||||
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.
|
||||
thread->check_possible_safepoint();
|
||||
|
||||
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) {
|
||||
process_if_requested(thread);
|
||||
void SafepointMechanism::process_if_requested_with_exit_check(JavaThread* thread, bool check_async_exception) {
|
||||
process_if_requested(thread, true /* allow_suspend */, check_async_exception);
|
||||
if (thread->has_special_runtime_exit_condition()) {
|
||||
thread->handle_special_runtime_exit_condition(check_asyncs);
|
||||
thread->handle_special_runtime_exit_condition();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -266,7 +266,7 @@ void NMethodSweeper::force_sweep() {
|
||||
*/
|
||||
void NMethodSweeper::handle_safepoint_request() {
|
||||
JavaThread* thread = JavaThread::current();
|
||||
if (SafepointMechanism::should_process(thread)) {
|
||||
if (SafepointMechanism::local_poll_armed(thread)) {
|
||||
if (PrintMethodFlushing && Verbose) {
|
||||
tty->print_cr("### Sweep at %d out of %d, yielding to safepoint", _seen, CodeCache::nmethod_count());
|
||||
}
|
||||
|
@ -1014,10 +1014,6 @@ JavaThread::JavaThread() :
|
||||
_monitor_chunks(nullptr),
|
||||
|
||||
_suspend_flags(0),
|
||||
_pending_async_exception(nullptr),
|
||||
#ifdef ASSERT
|
||||
_is_unsafe_access_error(false),
|
||||
#endif
|
||||
|
||||
_thread_state(_thread_new),
|
||||
_saved_exception_pc(nullptr),
|
||||
@ -1585,159 +1581,106 @@ void JavaThread::remove_monitor_chunk(MonitorChunk* chunk) {
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Asynchronous exceptions support
|
||||
//
|
||||
void JavaThread::check_and_handle_async_exceptions() {
|
||||
if (has_last_Java_frame() && has_async_exception_condition()) {
|
||||
// If we are at a polling page safepoint (not a poll return)
|
||||
// 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 the code we are returning to has deoptimized we must defer
|
||||
// the exception otherwise live registers get clobbered on the
|
||||
// exception path before deoptimization is able to retrieve them.
|
||||
//
|
||||
RegisterMap map(this, false);
|
||||
frame caller_fr = last_frame().sender(&map);
|
||||
assert(caller_fr.is_compiled_frame(), "what?");
|
||||
if (caller_fr.is_deoptimized_frame()) {
|
||||
log_info(exceptions)("deferred async exception at compiled safepoint");
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!clear_async_exception_condition()) {
|
||||
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())) {
|
||||
|
||||
// We cannot call Exceptions::_throw(...) here because we cannot block
|
||||
set_pending_exception(_pending_async_exception, __FILE__, __LINE__);
|
||||
|
||||
LogTarget(Info, exceptions) lt;
|
||||
if (lt.is_enabled()) {
|
||||
ResourceMark rm;
|
||||
LogStream ls(lt);
|
||||
ls.print("Async. exception installed at runtime exit (" INTPTR_FORMAT ")", p2i(this));
|
||||
if (has_last_Java_frame()) {
|
||||
frame f = last_frame();
|
||||
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());
|
||||
}
|
||||
}
|
||||
// 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::handle_special_runtime_exit_condition() {
|
||||
if (is_obj_deopt_suspend()) {
|
||||
frame_anchor()->make_walkable(this);
|
||||
wait_for_object_deoptimization();
|
||||
}
|
||||
|
||||
// We might be here for reasons in addition to the self-suspend request
|
||||
// so check for other async requests.
|
||||
if (check_asyncs) {
|
||||
check_and_handle_async_exceptions();
|
||||
}
|
||||
|
||||
JFR_ONLY(SUSPEND_THREAD_CONDITIONAL(this);)
|
||||
}
|
||||
|
||||
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());
|
||||
}
|
||||
};
|
||||
// Asynchronous exceptions support
|
||||
//
|
||||
void JavaThread::handle_async_exception(oop java_throwable) {
|
||||
assert(java_throwable != NULL, "should have an _async_exception to throw");
|
||||
assert(!is_at_poll_safepoint(), "should have never called this method");
|
||||
|
||||
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;
|
||||
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 (has_last_Java_frame()) {
|
||||
frame f = last_frame();
|
||||
if (f.is_runtime_frame()) {
|
||||
// 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(®_map);
|
||||
if (!StressCompiledExceptionHandlers && compiled_frame.can_be_deoptimized()) {
|
||||
Deoptimization::deoptimize(this, compiled_frame);
|
||||
}
|
||||
}
|
||||
// must deoptimize the caller before continuing, as the compiled exception
|
||||
// handler table may not be valid.
|
||||
RegisterMap reg_map(this, false);
|
||||
frame compiled_frame = f.sender(®_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)) {
|
||||
ResourceMark rm;
|
||||
log_info(exceptions)("Pending Async. exception installed of type: %s",
|
||||
InstanceKlass::cast(_pending_async_exception->klass())->external_name());
|
||||
}
|
||||
// for AbortVMOnException flag
|
||||
Exceptions::debug_check_abort(_pending_async_exception->klass()->external_name());
|
||||
}
|
||||
}
|
||||
|
||||
// Only overwrite an already pending exception if it is not a ThreadDeath.
|
||||
if (!has_pending_exception() || !pending_exception()->is_a(vmClasses::ThreadDeath_klass())) {
|
||||
|
||||
// We cannot call Exceptions::_throw(...) here because we cannot block
|
||||
set_pending_exception(java_throwable, __FILE__, __LINE__);
|
||||
|
||||
LogTarget(Info, exceptions) lt;
|
||||
if (lt.is_enabled()) {
|
||||
ResourceMark rm;
|
||||
LogStream ls(lt);
|
||||
ls.print("Async. exception installed at runtime exit (" INTPTR_FORMAT ")", p2i(this));
|
||||
if (has_last_Java_frame()) {
|
||||
frame f = last_frame();
|
||||
ls.print(" (pc: " INTPTR_FORMAT " sp: " INTPTR_FORMAT " )", p2i(f.pc()), p2i(f.sp()));
|
||||
}
|
||||
ls.print_cr(" of type: %s", java_throwable->klass()->external_name());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void JavaThread::install_async_exception(AsyncExceptionHandshake* aeh) {
|
||||
// Do not throw asynchronous exceptions against the compiler thread.
|
||||
if (!can_call_java()) {
|
||||
delete aeh;
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't install a new pending async exception if there is already
|
||||
// a pending ThreadDeath one. Just interrupt thread from potential
|
||||
// wait()/sleep()/park() and return.
|
||||
if (has_async_exception_condition(true /* ThreadDeath_only */)) {
|
||||
java_lang_Thread::set_interrupted(threadObj(), true);
|
||||
this->interrupt();
|
||||
delete aeh;
|
||||
return;
|
||||
}
|
||||
|
||||
oop exception = aeh->exception();
|
||||
Handshake::execute(aeh, this); // Install asynchronous handshake
|
||||
|
||||
ResourceMark rm;
|
||||
if (log_is_enabled(Info, exceptions)) {
|
||||
log_info(exceptions)("Pending Async. exception installed of type: %s",
|
||||
InstanceKlass::cast(exception->klass())->external_name());
|
||||
}
|
||||
// for AbortVMOnException flag
|
||||
Exceptions::debug_check_abort(exception->klass()->external_name());
|
||||
|
||||
// Interrupt thread so it will wake up from a potential wait()/sleep()/park()
|
||||
java_lang_Thread::set_interrupted(threadObj(), true);
|
||||
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.
|
||||
//
|
||||
@ -1966,7 +1909,6 @@ void JavaThread::oops_do_no_frames(OopClosure* f, CodeBlobClosure* cf) {
|
||||
// around using this function
|
||||
f->do_oop((oop*) &_vm_result);
|
||||
f->do_oop((oop*) &_exception_oop);
|
||||
f->do_oop((oop*) &_pending_async_exception);
|
||||
#if INCLUDE_JVMCI
|
||||
f->do_oop((oop*) &_jvmci_reserved_oop0);
|
||||
#endif
|
||||
|
@ -68,6 +68,7 @@ class OSThread;
|
||||
class ThreadStatistics;
|
||||
class ConcurrentLocksDump;
|
||||
class MonitorInfo;
|
||||
class AsyncExceptionHandshake;
|
||||
|
||||
class vframeArray;
|
||||
class vframe;
|
||||
@ -785,14 +786,11 @@ class JavaThread: public Thread {
|
||||
enum SuspendFlags {
|
||||
// NOTE: avoid using the sign-bit as cc generates different test code
|
||||
// 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
|
||||
_obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent
|
||||
_trace_flag = 0x00000004U, // call tracing backend
|
||||
_obj_deopt = 0x00000008U // suspend for object reallocation and relocking for JVMTI agent
|
||||
};
|
||||
|
||||
// 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;
|
||||
|
||||
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_obj_deopt_suspend() { return (_suspend_flags & _obj_deopt) != 0; }
|
||||
|
||||
// Asynchronous exceptions support
|
||||
// Asynchronous exception support
|
||||
private:
|
||||
oop _pending_async_exception;
|
||||
#ifdef ASSERT
|
||||
bool _is_unsafe_access_error;
|
||||
#endif
|
||||
friend class InstallAsyncExceptionHandshake;
|
||||
friend class AsyncExceptionHandshake;
|
||||
friend class HandshakeState;
|
||||
|
||||
inline bool clear_async_exception_condition();
|
||||
void install_async_exception(AsyncExceptionHandshake* aec = NULL);
|
||||
void handle_async_exception(oop java_throwable);
|
||||
public:
|
||||
bool has_async_exception_condition() {
|
||||
return (_suspend_flags & _has_async_exception) != 0 &&
|
||||
(_suspend_flags & _async_delivery_disabled) == 0;
|
||||
}
|
||||
inline void set_pending_async_exception(oop e);
|
||||
bool has_async_exception_condition(bool ThreadDeath_only = false);
|
||||
inline void set_pending_unsafe_access_error();
|
||||
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 {
|
||||
friend JavaThread;
|
||||
@ -1169,13 +1161,10 @@ class JavaThread: public Thread {
|
||||
// current thread, i.e. reverts optimizations based on escape analysis.
|
||||
void wait_for_object_deoptimization();
|
||||
|
||||
// these next two are also used for self-suspension and async exception support
|
||||
void handle_special_runtime_exit_condition(bool check_asyncs = true);
|
||||
|
||||
// Return true if JavaThread has an asynchronous condition or
|
||||
// if external suspension is requested.
|
||||
// Support for object deoptimization and JFR suspension
|
||||
void handle_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
|
||||
|
@ -28,7 +28,11 @@
|
||||
|
||||
#include "runtime/thread.hpp"
|
||||
|
||||
#include "classfile/vmClasses.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/nonJavaThread.hpp"
|
||||
#include "runtime/orderAccess.hpp"
|
||||
@ -122,31 +126,62 @@ inline void JavaThread::clear_obj_deopt_flag() {
|
||||
clear_suspend_flag(_obj_deopt);
|
||||
}
|
||||
|
||||
inline bool JavaThread::clear_async_exception_condition() {
|
||||
bool ret = has_async_exception_condition();
|
||||
if (ret) {
|
||||
clear_suspend_flag(_has_async_exception);
|
||||
class AsyncExceptionHandshake : public AsyncHandshakeClosure {
|
||||
OopHandle _exception;
|
||||
bool _is_ThreadDeath;
|
||||
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) {
|
||||
_pending_async_exception = e;
|
||||
set_suspend_flag(_has_async_exception);
|
||||
}
|
||||
~AsyncExceptionHandshake() {
|
||||
assert(!_exception.is_empty(), "invariant");
|
||||
_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() {
|
||||
set_suspend_flag(_has_async_exception);
|
||||
DEBUG_ONLY(_is_unsafe_access_error = true);
|
||||
if (!has_async_exception_condition()) {
|
||||
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) {
|
||||
assert((_target->_suspend_flags & _async_delivery_disabled) == 0, "Nesting is not supported");
|
||||
_target->set_suspend_flag(_async_delivery_disabled);
|
||||
assert(!_target->handshake_state()->async_exceptions_blocked(), "Nesting is not supported");
|
||||
_target->handshake_state()->set_async_exceptions_blocked(true);
|
||||
}
|
||||
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 {
|
||||
|
@ -716,7 +716,6 @@
|
||||
nonstatic_field(JavaThread, _current_pending_monitor_is_from_java, bool) \
|
||||
volatile_nonstatic_field(JavaThread, _current_waiting_monitor, ObjectMonitor*) \
|
||||
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_pc, address) \
|
||||
volatile_nonstatic_field(JavaThread, _is_method_handle_return, int) \
|
||||
@ -2121,12 +2120,6 @@
|
||||
declare_constant(JVM_CONSTANT_DynamicInError) \
|
||||
declare_constant(JVM_CONSTANT_InternalMax) \
|
||||
\
|
||||
/*****************************/ \
|
||||
/* Thread::SuspendFlags enum */ \
|
||||
/*****************************/ \
|
||||
\
|
||||
declare_constant(JavaThread::_has_async_exception) \
|
||||
\
|
||||
/*******************/ \
|
||||
/* JavaThreadState */ \
|
||||
/*******************/ \
|
||||
|
@ -250,12 +250,6 @@ void Exceptions::throw_stack_overflow_exception(JavaThread* THREAD, const char*
|
||||
_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, ...) {
|
||||
const int max_msg_size = 1024;
|
||||
va_list ap;
|
||||
|
@ -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_unsafe_access_internal_error(JavaThread* thread, const char* file, int line, const char* message);
|
||||
|
||||
static void wrap_dynamic_exception(bool is_indy, JavaThread* thread);
|
||||
|
||||
// Exception counting for error files of interesting exceptions that may have
|
||||
|
@ -34,8 +34,6 @@ public class Thread extends VMObject {
|
||||
private static long tlabFieldOffset;
|
||||
|
||||
private static CIntegerField suspendFlagsField;
|
||||
// Thread::SuspendFlags enum constants
|
||||
private static int HAS_ASYNC_EXCEPTION;
|
||||
|
||||
private static AddressField currentPendingMonitorField;
|
||||
private static AddressField currentWaitingMonitorField;
|
||||
@ -55,7 +53,6 @@ public class Thread extends VMObject {
|
||||
Type typeJavaThread = db.lookupType("JavaThread");
|
||||
|
||||
suspendFlagsField = typeJavaThread.getCIntegerField("_suspend_flags");
|
||||
HAS_ASYNC_EXCEPTION = db.lookupIntConstant("JavaThread::_has_async_exception").intValue();
|
||||
|
||||
tlabFieldOffset = typeThread.getField("_tlab").getOffset();
|
||||
currentPendingMonitorField = typeJavaThread.getAddressField("_current_pending_monitor");
|
||||
@ -71,10 +68,6 @@ public class Thread extends VMObject {
|
||||
return (int) suspendFlagsField.getValue(addr);
|
||||
}
|
||||
|
||||
public boolean hasAsyncException() {
|
||||
return (suspendFlags() & HAS_ASYNC_EXCEPTION) != 0;
|
||||
}
|
||||
|
||||
public ThreadLocalAllocBuffer tlab() {
|
||||
return new ThreadLocalAllocBuffer(addr.addOffsetTo(tlabFieldOffset));
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
216
test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java
Normal file
216
test/hotspot/jtreg/runtime/Thread/AsyncExceptionTest.java
Normal 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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user