mirror of
https://github.com/openjdk/jdk.git
synced 2025-09-17 09:34:38 +02:00
8283044: Use asynchronous handshakes to deliver asynchronous exceptions
Reviewed-by: dcubed, dholmes, rehn
This commit is contained in:
parent
9d200d6e7a
commit
4e20a03786
20 changed files with 772 additions and 262 deletions
|
@ -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))
|
||||||
|
|
|
@ -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());
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 */);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -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();
|
||||||
|
|
|
@ -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");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -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);
|
||||||
|
|
|
@ -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);
|
||||||
|
|
||||||
|
|
|
@ -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();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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());
|
||||||
}
|
}
|
||||||
|
|
|
@ -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(®_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(®_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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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 */ \
|
||||||
/*******************/ \
|
/*******************/ \
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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));
|
||||||
}
|
}
|
||||||
|
|
|
@ -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…
Add table
Add a link
Reference in a new issue