Commit 0efbd40b authored by ager@chromium.org's avatar ager@chromium.org

Add support for forceful termination of JavaScript execution.

The termination is achieved by throwing an exception that is uncatchable by JavaScript exception handlers.
Review URL: http://codereview.chromium.org/174056

git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@2723 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
parent 8cc5048a
......@@ -2223,6 +2223,47 @@ class V8EXPORT V8 {
*/
static int GetLogLines(int from_pos, char* dest_buf, int max_size);
/**
* Retrieve the V8 thread id of the calling thread.
*
* The thread id for a thread should only be retrieved after the V8
* lock has been acquired with a Locker object with that thread.
*/
static int GetCurrentThreadId();
/**
* Forcefully terminate execution of a JavaScript thread. This can
* be used to terminate long-running scripts.
*
* TerminateExecution should only be called when then V8 lock has
* been acquired with a Locker object. Therefore, in order to be
* able to terminate long-running threads, preemption must be
* enabled to allow the user of TerminateExecution to acquire the
* lock.
*
* The termination is achieved by throwing an exception that is
* uncatchable by JavaScript exception handlers. Termination
* exceptions act as if they were caught by a C++ TryCatch exception
* handlers. If forceful termination is used, any C++ TryCatch
* exception handler that catches an exception should check if that
* exception is a termination exception and immediately return if
* that is the case. Returning immediately in that case will
* continue the propagation of the termination exception if needed.
*
* The thread id passed to TerminateExecution must have been
* obtained by calling GetCurrentThreadId on the thread in question.
*
* \param thread_id The thread id of the thread to terminate.
*/
static void TerminateExecution(int thread_id);
/**
* Forcefully terminate the current thread of JavaScript execution.
*
* This method can be used by any thread even if that thread has not
* acquired the V8 lock with a Locker object.
*/
static void TerminateExecution();
/**
* Releases any resources used by v8 and stops any utility threads
......@@ -2281,6 +2322,21 @@ class V8EXPORT TryCatch {
*/
bool HasCaught() const;
/**
* For certain types of exceptions, it makes no sense to continue
* execution.
*
* Currently, the only type of exception that can be caught by a
* TryCatch handler and for which it does not make sense to continue
* is termination exception. Such exceptions are thrown when the
* TerminateExecution methods are called to terminate a long-running
* script.
*
* If CanContinue returns false, the correct action is to perform
* any C++ cleanup needed and then return.
*/
bool CanContinue() const;
/**
* Returns the exception caught by this try/catch block. If no exception has
* been caught an empty handle is returned.
......@@ -2337,6 +2393,7 @@ class V8EXPORT TryCatch {
void* exception_;
void* message_;
bool is_verbose_;
bool can_continue_;
bool capture_message_;
void* js_handler_;
};
......
......@@ -75,7 +75,7 @@ namespace v8 {
i::V8::FatalProcessOutOfMemory(NULL); \
} \
bool call_depth_is_zero = thread_local.CallDepthIsZero(); \
i::Top::optional_reschedule_exception(call_depth_is_zero); \
i::Top::OptionalRescheduleException(call_depth_is_zero, false); \
return value; \
} \
} while (false)
......@@ -1208,6 +1208,11 @@ bool v8::TryCatch::HasCaught() const {
}
bool v8::TryCatch::CanContinue() const {
return can_continue_;
}
v8::Local<Value> v8::TryCatch::Exception() const {
if (HasCaught()) {
// Check for out of memory exception.
......@@ -3354,6 +3359,34 @@ int V8::GetLogLines(int from_pos, char* dest_buf, int max_size) {
return 0;
}
int V8::GetCurrentThreadId() {
API_ENTRY_CHECK("V8::GetCurrentThreadId()");
EnsureInitialized("V8::GetCurrentThreadId()");
return i::Top::thread_id();
}
void V8::TerminateExecution(int thread_id) {
if (!i::V8::IsRunning()) return;
API_ENTRY_CHECK("V8::GetCurrentThreadId()");
// If the thread_id identifies the current thread just terminate
// execution right away. Otherwise, ask the thread manager to
// terminate the thread with the given id if any.
if (thread_id == i::Top::thread_id()) {
i::StackGuard::TerminateExecution();
} else {
i::ThreadManager::TerminateExecution(thread_id);
}
}
void V8::TerminateExecution() {
if (!i::V8::IsRunning()) return;
i::StackGuard::TerminateExecution();
}
String::Utf8Value::Utf8Value(v8::Handle<v8::Value> obj) {
EnsureInitialized("v8::String::Utf8Value::Utf8Value()");
if (obj.IsEmpty()) {
......
......@@ -5701,7 +5701,8 @@ void CEntryStub::GenerateThrowTOS(MacroAssembler* masm) {
}
void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
UncatchableExceptionType type) {
// Adjust this code if not the case.
ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize);
......@@ -5725,20 +5726,22 @@ void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
// Set the top handler address to next handler past the current ENTRY handler.
ASSERT(StackHandlerConstants::kNextOffset == 0);
__ pop(r0);
__ str(r0, MemOperand(r3));
// Set external caught exception to false.
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ mov(r0, Operand(false));
__ mov(r2, Operand(external_caught));
__ str(r0, MemOperand(r2));
__ pop(r2);
__ str(r2, MemOperand(r3));
// Set pending exception and r0 to out of memory exception.
Failure* out_of_memory = Failure::OutOfMemoryException();
__ mov(r0, Operand(reinterpret_cast<int32_t>(out_of_memory)));
__ mov(r2, Operand(ExternalReference(Top::k_pending_exception_address)));
__ str(r0, MemOperand(r2));
if (type == OUT_OF_MEMORY) {
// Set external caught exception to false.
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ mov(r0, Operand(false));
__ mov(r2, Operand(external_caught));
__ str(r0, MemOperand(r2));
// Set pending exception and r0 to out of memory exception.
Failure* out_of_memory = Failure::OutOfMemoryException();
__ mov(r0, Operand(reinterpret_cast<int32_t>(out_of_memory)));
__ mov(r2, Operand(ExternalReference(Top::k_pending_exception_address)));
__ str(r0, MemOperand(r2));
}
// Stack layout at this point. See also StackHandlerConstants.
// sp -> state (ENTRY)
......@@ -5768,6 +5771,7 @@ void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
void CEntryStub::GenerateCore(MacroAssembler* masm,
Label* throw_normal_exception,
Label* throw_termination_exception,
Label* throw_out_of_memory_exception,
StackFrame::Type frame_type,
bool do_gc,
......@@ -5838,10 +5842,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
__ tst(r0, Operand(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize));
__ b(eq, &retry);
Label continue_exception;
// If the returned failure is EXCEPTION then promote Top::pending_exception().
__ cmp(r0, Operand(reinterpret_cast<int32_t>(Failure::Exception())));
__ b(ne, &continue_exception);
// Special handling of out of memory exceptions.
Failure* out_of_memory = Failure::OutOfMemoryException();
__ cmp(r0, Operand(reinterpret_cast<int32_t>(out_of_memory)));
__ b(eq, throw_out_of_memory_exception);
// Retrieve the pending exception and clear the variable.
__ mov(ip, Operand(ExternalReference::the_hole_value_location()));
......@@ -5850,11 +5854,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
__ ldr(r0, MemOperand(ip));
__ str(r3, MemOperand(ip));
__ bind(&continue_exception);
// Special handling of out of memory exception.
Failure* out_of_memory = Failure::OutOfMemoryException();
__ cmp(r0, Operand(reinterpret_cast<int32_t>(out_of_memory)));
__ b(eq, throw_out_of_memory_exception);
// Special handling of termination exceptions which are uncatchable
// by javascript code.
__ cmp(r0, Operand(Factory::termination_exception()));
__ b(eq, throw_termination_exception);
// Handle normal exception.
__ jmp(throw_normal_exception);
......@@ -5887,11 +5890,14 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// r5: pointer to builtin function (C callee-saved)
// r6: pointer to first argument (C callee-saved)
Label throw_out_of_memory_exception;
Label throw_normal_exception;
Label throw_termination_exception;
Label throw_out_of_memory_exception;
// Call into the runtime system.
GenerateCore(masm, &throw_normal_exception,
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
false,
......@@ -5900,6 +5906,7 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
......@@ -5910,14 +5917,17 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
__ mov(r0, Operand(reinterpret_cast<int32_t>(failure)));
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
true);
__ bind(&throw_out_of_memory_exception);
GenerateThrowOutOfMemory(masm);
// control flow for generated will not return.
GenerateThrowUncatchable(masm, OUT_OF_MEMORY);
__ bind(&throw_termination_exception);
GenerateThrowUncatchable(masm, TERMINATION);
__ bind(&throw_normal_exception);
GenerateThrowTOS(masm);
......
......@@ -70,6 +70,9 @@
// Mode to overwrite BinaryExpression values.
enum OverwriteMode { NO_OVERWRITE, OVERWRITE_LEFT, OVERWRITE_RIGHT };
// Types of uncatchable exceptions.
enum UncatchableExceptionType { OUT_OF_MEMORY, TERMINATION };
#if V8_TARGET_ARCH_IA32
#include "ia32/codegen-ia32.h"
......@@ -291,12 +294,14 @@ class CEntryStub : public CodeStub {
void GenerateBody(MacroAssembler* masm, bool is_debug_break);
void GenerateCore(MacroAssembler* masm,
Label* throw_normal_exception,
Label* throw_termination_exception,
Label* throw_out_of_memory_exception,
StackFrame::Type frame_type,
bool do_gc,
bool always_allocate_scope);
void GenerateThrowTOS(MacroAssembler* masm);
void GenerateThrowOutOfMemory(MacroAssembler* masm);
void GenerateThrowUncatchable(MacroAssembler* masm,
UncatchableExceptionType type);
private:
Major MajorKey() { return CEntry; }
......
......@@ -156,7 +156,8 @@ Handle<Object> Execution::TryCall(Handle<JSFunction> func,
ASSERT(catcher.HasCaught());
ASSERT(Top::has_pending_exception());
ASSERT(Top::external_caught_exception());
Top::optional_reschedule_exception(true);
bool is_bottom_call = HandleScopeImplementer::instance()->CallDepthIsZero();
Top::OptionalRescheduleException(is_bottom_call, true);
result = v8::Utils::OpenHandle(*catcher.Exception());
}
......@@ -328,6 +329,19 @@ void StackGuard::Preempt() {
}
bool StackGuard::IsTerminateExecution() {
ExecutionAccess access;
return thread_local_.interrupt_flags_ & TERMINATE;
}
void StackGuard::TerminateExecution() {
ExecutionAccess access;
thread_local_.interrupt_flags_ |= TERMINATE;
set_limits(kInterruptLimit, access);
}
#ifdef ENABLE_DEBUGGER_SUPPORT
bool StackGuard::IsDebugBreak() {
ExecutionAccess access;
......@@ -638,6 +652,10 @@ Object* Execution::HandleStackGuardInterrupt() {
}
#endif
if (StackGuard::IsPreempted()) RuntimePreempt();
if (StackGuard::IsTerminateExecution()) {
StackGuard::Continue(TERMINATE);
return Top::TerminateExecution();
}
if (StackGuard::IsInterrupted()) {
// interrupt
StackGuard::Continue(INTERRUPT);
......
......@@ -37,7 +37,8 @@ enum InterruptFlag {
INTERRUPT = 1 << 0,
DEBUGBREAK = 1 << 1,
DEBUGCOMMAND = 1 << 2,
PREEMPT = 1 << 3
PREEMPT = 1 << 3,
TERMINATE = 1 << 4
};
class Execution : public AllStatic {
......@@ -164,13 +165,15 @@ class StackGuard BASE_EMBEDDED {
static void Preempt();
static bool IsInterrupted();
static void Interrupt();
static void Continue(InterruptFlag after_what);
static bool IsTerminateExecution();
static void TerminateExecution();
#ifdef ENABLE_DEBUGGER_SUPPORT
static void DebugBreak();
static void DebugCommand();
static bool IsDebugBreak();
static void DebugBreak();
static bool IsDebugCommand();
static void DebugCommand();
#endif
static void Continue(InterruptFlag after_what);
private:
// You should hold the ExecutionAccess lock when calling this method.
......
......@@ -1412,6 +1412,9 @@ bool Heap::CreateInitialObjects() {
if (obj->IsFailure()) return false;
set_no_interceptor_result_sentinel(obj);
obj = CreateOddball(oddball_map(), "termination_exception", Smi::FromInt(-3));
if (obj->IsFailure()) return false;
set_termination_exception(obj);
// Allocate the empty string.
obj = AllocateRawAsciiString(0, TENURED);
......
......@@ -111,6 +111,7 @@ namespace internal {
V(Object, nan_value, NanValue) \
V(Object, undefined_value, UndefinedValue) \
V(Object, no_interceptor_result_sentinel, NoInterceptorResultSentinel) \
V(Object, termination_exception, TerminationException) \
V(Object, minus_zero_value, MinusZeroValue) \
V(Object, null_value, NullValue) \
V(Object, true_value, TrueValue) \
......
......@@ -7505,6 +7505,7 @@ void CEntryStub::GenerateThrowTOS(MacroAssembler* masm) {
void CEntryStub::GenerateCore(MacroAssembler* masm,
Label* throw_normal_exception,
Label* throw_termination_exception,
Label* throw_out_of_memory_exception,
StackFrame::Type frame_type,
bool do_gc,
......@@ -7568,10 +7569,9 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
__ test(eax, Immediate(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize));
__ j(zero, &retry, taken);
Label continue_exception;
// If the returned failure is EXCEPTION then promote Top::pending_exception().
__ cmp(eax, reinterpret_cast<int32_t>(Failure::Exception()));
__ j(not_equal, &continue_exception);
// Special handling of out of memory exceptions.
__ cmp(eax, reinterpret_cast<int32_t>(Failure::OutOfMemoryException()));
__ j(equal, throw_out_of_memory_exception);
// Retrieve the pending exception and clear the variable.
ExternalReference pending_exception_address(Top::k_pending_exception_address);
......@@ -7580,10 +7580,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
Operand::StaticVariable(ExternalReference::the_hole_value_location()));
__ mov(Operand::StaticVariable(pending_exception_address), edx);
__ bind(&continue_exception);
// Special handling of out of memory exception.
__ cmp(eax, reinterpret_cast<int32_t>(Failure::OutOfMemoryException()));
__ j(equal, throw_out_of_memory_exception);
// Special handling of termination exceptions which are uncatchable
// by javascript code.
__ cmp(eax, Factory::termination_exception());
__ j(equal, throw_termination_exception);
// Handle normal exception.
__ jmp(throw_normal_exception);
......@@ -7593,7 +7593,8 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
}
void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
UncatchableExceptionType type) {
// Adjust this code if not the case.
ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize);
......@@ -7618,17 +7619,19 @@ void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
ASSERT(StackHandlerConstants::kNextOffset == 0);
__ pop(Operand::StaticVariable(handler_address));
// Set external caught exception to false.
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ mov(eax, false);
__ mov(Operand::StaticVariable(external_caught), eax);
if (type == OUT_OF_MEMORY) {
// Set external caught exception to false.
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ mov(eax, false);
__ mov(Operand::StaticVariable(external_caught), eax);
// Set pending exception and eax to out of memory exception.
ExternalReference pending_exception(Top::k_pending_exception_address);
__ mov(eax, reinterpret_cast<int32_t>(Failure::OutOfMemoryException()));
__ mov(Operand::StaticVariable(pending_exception), eax);
// Set pending exception and eax to out of memory exception.
ExternalReference pending_exception(Top::k_pending_exception_address);
__ mov(eax, reinterpret_cast<int32_t>(Failure::OutOfMemoryException()));
__ mov(Operand::StaticVariable(pending_exception), eax);
}
// Clear the context pointer;
// Clear the context pointer.
__ xor_(esi, Operand(esi));
// Restore fp from handler and discard handler state.
......@@ -7667,11 +7670,14 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// edi: number of arguments including receiver (C callee-saved)
// esi: argv pointer (C callee-saved)
Label throw_out_of_memory_exception;
Label throw_normal_exception;
Label throw_termination_exception;
Label throw_out_of_memory_exception;
// Call into the runtime system.
GenerateCore(masm, &throw_normal_exception,
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
false,
......@@ -7680,6 +7686,7 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
......@@ -7690,14 +7697,17 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
__ mov(eax, Immediate(reinterpret_cast<int32_t>(failure)));
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
true);
__ bind(&throw_out_of_memory_exception);
GenerateThrowOutOfMemory(masm);
// control flow for generated will not return.
GenerateThrowUncatchable(masm, OUT_OF_MEMORY);
__ bind(&throw_termination_exception);
GenerateThrowUncatchable(masm, TERMINATION);
__ bind(&throw_normal_exception);
GenerateThrowTOS(masm);
......
......@@ -147,14 +147,12 @@ Handle<String> MessageHandler::GetMessage(Handle<Object> data) {
Handle<String> fmt_str = Factory::LookupAsciiSymbol("FormatMessage");
Handle<JSFunction> fun =
Handle<JSFunction>(
JSFunction::cast(
Top::builtins()->GetProperty(*fmt_str)));
JSFunction::cast(Top::builtins()->GetProperty(*fmt_str)));
Object** argv[1] = { data.location() };
bool caught_exception;
Handle<Object> result =
Execution::TryCall(fun, Top::builtins(), 1, argv,
&caught_exception);
Execution::TryCall(fun, Top::builtins(), 1, argv, &caught_exception);
if (caught_exception || !result->IsString()) {
return Factory::LookupAsciiSymbol("<error>");
......
......@@ -705,7 +705,8 @@ void Oddball::OddballVerify() {
} else {
ASSERT(number->IsSmi());
int value = Smi::cast(number)->value();
ASSERT(value == 0 || value == 1 || value == -1 || value == -2);
ASSERT(value == 0 || value == 1 || value == -1 ||
value == -2 || value == -3);
}
}
......
......@@ -788,6 +788,7 @@ Failure* Failure::Exception() {
return Construct(EXCEPTION);
}
Failure* Failure::OutOfMemoryException() {
return Construct(OUT_OF_MEMORY_EXCEPTION);
}
......
......@@ -98,6 +98,7 @@ void Top::InitializeThreadLocal() {
thread_local_.stack_is_cooked_ = false;
thread_local_.try_catch_handler_ = NULL;
thread_local_.context_ = NULL;
thread_local_.thread_id_ = ThreadManager::kInvalidId;
thread_local_.external_caught_exception_ = false;
thread_local_.failed_access_check_callback_ = NULL;
clear_pending_exception();
......@@ -598,6 +599,12 @@ Failure* Top::StackOverflow() {
}
Failure* Top::TerminateExecution() {
DoThrow(Heap::termination_exception(), NULL, NULL);
return Failure::Exception();
}
Failure* Top::Throw(Object* exception, MessageLocation* location) {
DoThrow(exception, location, NULL);
return Failure::Exception();
......@@ -694,7 +701,8 @@ void Top::ReportUncaughtException(Handle<Object> exception,
}
bool Top::ShouldReportException(bool* is_caught_externally) {
bool Top::ShouldReturnException(bool* is_caught_externally,
bool catchable_by_javascript) {
// Find the top-most try-catch handler.
StackHandler* handler =
StackHandler::FromAddress(Top::handler(Top::GetCurrentThread()));
......@@ -712,7 +720,8 @@ bool Top::ShouldReportException(bool* is_caught_externally) {
//
// See comments in RegisterTryCatchHandler for details.
*is_caught_externally = try_catch != NULL &&
(handler == NULL || handler == try_catch->js_handler_);
(handler == NULL || handler == try_catch->js_handler_ ||
!catchable_by_javascript);
if (*is_caught_externally) {
// Only report the exception if the external handler is verbose.
......@@ -735,12 +744,17 @@ void Top::DoThrow(Object* exception,
// Determine reporting and whether the exception is caught externally.
bool is_caught_externally = false;
bool is_out_of_memory = exception == Failure::OutOfMemoryException();
bool should_return_exception = ShouldReportException(&is_caught_externally);
bool report_exception = !is_out_of_memory && should_return_exception;
bool is_termination_exception = exception == Heap::termination_exception();
bool catchable_by_javascript = !is_termination_exception && !is_out_of_memory;
bool should_return_exception =
ShouldReturnException(&is_caught_externally, catchable_by_javascript);
bool report_exception = catchable_by_javascript && should_return_exception;
#ifdef ENABLE_DEBUGGER_SUPPORT
// Notify debugger of exception.
Debugger::OnException(exception_handle, report_exception);
if (catchable_by_javascript) {
Debugger::OnException(exception_handle, report_exception);
}
#endif
// Generate the message.
......@@ -791,14 +805,21 @@ void Top::ReportPendingMessages() {
// the global context. Note: We have to mark the global context here
// since the GenerateThrowOutOfMemory stub cannot make a RuntimeCall to
// set it.
bool external_caught = thread_local_.external_caught_exception_;
HandleScope scope;
if (thread_local_.pending_exception_ == Failure::OutOfMemoryException()) {
context()->mark_out_of_memory();
} else if (thread_local_.pending_exception_ ==
Heap::termination_exception()) {
if (external_caught) {
thread_local_.try_catch_handler_->can_continue_ = false;
thread_local_.try_catch_handler_->exception_ = Heap::null_value();
}
} else {
Handle<Object> exception(pending_exception());
bool external_caught = thread_local_.external_caught_exception_;
thread_local_.external_caught_exception_ = false;
if (external_caught) {
thread_local_.try_catch_handler_->can_continue_ = true;
thread_local_.try_catch_handler_->exception_ =
thread_local_.pending_exception_;
if (!thread_local_.pending_message_obj_->IsTheHole()) {
......@@ -834,16 +855,30 @@ void Top::TraceException(bool flag) {
}
bool Top::optional_reschedule_exception(bool is_bottom_call) {
bool Top::OptionalRescheduleException(bool is_bottom_call,
bool force_clear_catchable) {
// Allways reschedule out of memory exceptions.
if (!is_out_of_memory()) {
// Never reschedule the exception if this is the bottom call.
bool clear_exception = is_bottom_call;
bool is_termination_exception =
pending_exception() == Heap::termination_exception();
// Do not reschedule the exception if this is the bottom call or
// if we are asked to clear catchable exceptions. Termination
// exceptions are not catchable and are only cleared if this is
// the bottom call.
bool clear_exception = is_bottom_call ||
(force_clear_catchable && !is_termination_exception);
// If the exception is externally caught, clear it if there are no
// JavaScript frames on the way to the C++ frame that has the
// external handler.
if (thread_local_.external_caught_exception_) {
if (is_termination_exception) {
thread_local_.external_caught_exception_ = false;
if (is_bottom_call) {
clear_pending_exception();
return false;
}
} else if (thread_local_.external_caught_exception_) {
// If the exception is externally caught, clear it if there are no
// JavaScript frames on the way to the C++ frame that has the
// external handler.
ASSERT(thread_local_.try_catch_handler_ != NULL);
Address external_handler_address =
reinterpret_cast<Address>(thread_local_.try_catch_handler_);
......
......@@ -46,6 +46,7 @@ class ThreadLocalTop BASE_EMBEDDED {
// The context where the current execution method is created and for variable
// lookups.
Context* context_;
int thread_id_;
Object* pending_exception_;
bool has_pending_message_;
const char* pending_message_;
......@@ -118,6 +119,10 @@ class Top {
thread_local_.save_context_ = save;
}
// Access to current thread id.
static int thread_id() { return thread_local_.thread_id_; }
static void set_thread_id(int id) { thread_local_.thread_id_ = id; }
// Interface to pending exception.
static Object* pending_exception() {
ASSERT(has_pending_exception());
......@@ -152,7 +157,8 @@ class Top {
// exceptions. If an exception was thrown and not handled by an external
// handler the exception is scheduled to be rethrown when we return to running
// JavaScript code. If an exception is scheduled true is returned.
static bool optional_reschedule_exception(bool is_bottom_call);
static bool OptionalRescheduleException(bool is_bottom_call,
bool force_clear_catchable);
static bool* external_caught_exception_address() {
return &thread_local_.external_caught_exception_;
......@@ -246,7 +252,8 @@ class Top {
static void DoThrow(Object* exception,
MessageLocation* location,
const char* message);
static bool ShouldReportException(bool* is_caught_externally);
static bool ShouldReturnException(bool* is_caught_externally,
bool catchable_by_javascript);
static void ReportUncaughtException(Handle<Object> exception,
MessageLocation* location,
Handle<String> stack_trace);
......@@ -260,6 +267,7 @@ class Top {
// Out of resource exception helpers.
static Failure* StackOverflow();
static Failure* TerminateExecution();
// Administration
static void Initialize();
......
......@@ -151,6 +151,10 @@ bool ThreadManager::RestoreThread() {
from = RegExpStack::RestoreStack(from);
from = Bootstrapper::RestoreState(from);
Thread::SetThreadLocal(thread_state_key, NULL);
if (state->terminate_on_restore()) {
StackGuard::TerminateExecution();
state->set_terminate_on_restore(false);
}
state->set_id(kInvalidId);
state->Unlink();
state->LinkInto(ThreadState::FREE_LIST);
......@@ -188,6 +192,7 @@ ThreadState* ThreadState::in_use_anchor_ = new ThreadState();
ThreadState::ThreadState() : id_(ThreadManager::kInvalidId),
terminate_on_restore_(false),
next_(this), previous_(this) {
}
......@@ -317,7 +322,21 @@ int ThreadManager::CurrentId() {
void ThreadManager::AssignId() {
if (!Thread::HasThreadLocal(thread_id_key)) {
Thread::SetThreadLocalInt(thread_id_key, next_id_++);
ASSERT(Locker::IsLocked());
int thread_id = next_id_++;
Thread::SetThreadLocalInt(thread_id_key, thread_id);
Top::set_thread_id(thread_id);
}
}
void ThreadManager::TerminateExecution(int thread_id) {
for (ThreadState* state = ThreadState::FirstInUse();
state != NULL;
state = state->Next()) {
if (thread_id == state->id()) {
state->set_terminate_on_restore(true);
}
}
}
......
......@@ -50,6 +50,12 @@ class ThreadState {
void set_id(int id) { id_ = id; }
int id() { return id_; }
// Should the thread be terminated when it is restored?
bool terminate_on_restore() { return terminate_on_restore_; }
void set_terminate_on_restore(bool terminate_on_restore) {
terminate_on_restore_ = terminate_on_restore;
}
// Get data area for archiving a thread.
char* data() { return data_; }
private:
......@@ -58,6 +64,7 @@ class ThreadState {
void AllocateSpace();
int id_;
bool terminate_on_restore_;
char* data_;
ThreadState* next_;
ThreadState* previous_;
......@@ -88,6 +95,8 @@ class ThreadManager : public AllStatic {
static int CurrentId();
static void AssignId();
static void TerminateExecution(int thread_id);
static const int kInvalidId = -1;
private:
static void EagerlyArchiveThread();
......
......@@ -6747,6 +6747,7 @@ void CEntryStub::GenerateThrowTOS(MacroAssembler* masm) {
void CEntryStub::GenerateCore(MacroAssembler* masm,
Label* throw_normal_exception,
Label* throw_termination_exception,
Label* throw_out_of_memory_exception,
StackFrame::Type frame_type,
bool do_gc,
......@@ -6819,11 +6820,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
__ testl(rax, Immediate(((1 << kFailureTypeTagSize) - 1) << kFailureTagSize));
__ j(zero, &retry);
Label continue_exception;
// If the returned failure is EXCEPTION then promote Top::pending_exception().
__ movq(kScratchRegister, Failure::Exception(), RelocInfo::NONE);
// Special handling of out of memory exceptions.
__ movq(kScratchRegister, Failure::OutOfMemoryException(), RelocInfo::NONE);
__ cmpq(rax, kScratchRegister);
__ j(not_equal, &continue_exception);
__ j(equal, throw_out_of_memory_exception);
// Retrieve the pending exception and clear the variable.
ExternalReference pending_exception_address(Top::k_pending_exception_address);
......@@ -6833,11 +6833,10 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
__ movq(rdx, Operand(rdx, 0));
__ movq(Operand(kScratchRegister, 0), rdx);
__ bind(&continue_exception);
// Special handling of out of memory exception.
__ movq(kScratchRegister, Failure::OutOfMemoryException(), RelocInfo::NONE);
__ cmpq(rax, kScratchRegister);
__ j(equal, throw_out_of_memory_exception);
// Special handling of termination exceptions which are uncatchable
// by javascript code.
__ Cmp(rax, Factory::termination_exception());
__ j(equal, throw_termination_exception);
// Handle normal exception.
__ jmp(throw_normal_exception);
......@@ -6847,7 +6846,8 @@ void CEntryStub::GenerateCore(MacroAssembler* masm,
}
void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
UncatchableExceptionType type) {
// Fetch top stack handler.
ExternalReference handler_address(Top::k_handler_address);
__ movq(kScratchRegister, handler_address);
......@@ -6857,30 +6857,32 @@ void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
Label loop, done;
__ bind(&loop);
// Load the type of the current stack handler.
__ cmpq(Operand(rsp, StackHandlerConstants::kStateOffset),
Immediate(StackHandler::ENTRY));
const int kStateOffset = StackHandlerConstants::kStateOffset;
__ cmpq(Operand(rsp, kStateOffset), Immediate(StackHandler::ENTRY));
__ j(equal, &done);
// Fetch the next handler in the list.
ASSERT(StackHandlerConstants::kNextOffset == 0);
__ pop(rsp);
const int kNextOffset = StackHandlerConstants::kNextOffset;
__ movq(rsp, Operand(rsp, kNextOffset));
__ jmp(&loop);
__ bind(&done);
// Set the top handler address to next handler past the current ENTRY handler.
__ pop(rax);
__ store_rax(handler_address);
__ movq(kScratchRegister, handler_address);
__ pop(Operand(kScratchRegister, 0));
// Set external caught exception to false.
__ movq(rax, Immediate(false));
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ store_rax(external_caught);
if (type == OUT_OF_MEMORY) {
// Set external caught exception to false.
ExternalReference external_caught(Top::k_external_caught_exception_address);
__ movq(rax, Immediate(false));
__ store_rax(external_caught);
// Set pending exception and rax to out of memory exception.
__ movq(rax, Failure::OutOfMemoryException(), RelocInfo::NONE);
ExternalReference pending_exception(Top::k_pending_exception_address);
__ store_rax(pending_exception);
// Set pending exception and rax to out of memory exception.
ExternalReference pending_exception(Top::k_pending_exception_address);
__ movq(rax, Failure::OutOfMemoryException(), RelocInfo::NONE);
__ store_rax(pending_exception);
}
// Clear the context pointer;
// Clear the context pointer.
__ xor_(rsi, rsi);
// Restore registers from handler.
......@@ -6956,12 +6958,14 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// r14: number of arguments including receiver (C callee-saved).
// r15: argv pointer (C callee-saved).
Label throw_out_of_memory_exception;
Label throw_normal_exception;
Label throw_termination_exception;
Label throw_out_of_memory_exception;
// Call into the runtime system.
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
false,
......@@ -6970,6 +6974,7 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
......@@ -6980,14 +6985,17 @@ void CEntryStub::GenerateBody(MacroAssembler* masm, bool is_debug_break) {
__ movq(rax, failure, RelocInfo::NONE);
GenerateCore(masm,
&throw_normal_exception,
&throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
true);
__ bind(&throw_out_of_memory_exception);
GenerateThrowOutOfMemory(masm);
// control flow for generated will not return.
GenerateThrowUncatchable(masm, OUT_OF_MEMORY);
__ bind(&throw_termination_exception);
GenerateThrowUncatchable(masm, TERMINATION);
__ bind(&throw_normal_exception);
GenerateThrowTOS(masm);
......
......@@ -56,6 +56,7 @@ SOURCES = {
'test-spaces.cc',
'test-strings.cc',
'test-threads.cc',
'test-thread-termination.cc',
'test-utils.cc',
'test-version.cc'
],
......
// Copyright 2009 the V8 project authors. All rights reserved.
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions are
// met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Google Inc. nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
// A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
// OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
// SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
// LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
// DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
// THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
// (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
// OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
#include "v8.h"
#include "platform.h"
#include "cctest.h"
v8::internal::Semaphore* semaphore = NULL;
v8::Handle<v8::Value> Signal(const v8::Arguments& args) {
semaphore->Signal();
return v8::Undefined();
}
v8::Handle<v8::Value> TerminateCurrentThread(const v8::Arguments& args) {
v8::V8::TerminateExecution();
return v8::Undefined();
}
v8::Handle<v8::Value> Fail(const v8::Arguments& args) {
CHECK(false);
return v8::Undefined();
}
v8::Handle<v8::Value> Loop(const v8::Arguments& args) {
v8::Handle<v8::String> source =
v8::String::New("try { doloop(); fail(); } catch(e) { fail(); }");
v8::Script::Compile(source)->Run();
return v8::Undefined();
}
v8::Handle<v8::Value> DoLoop(const v8::Arguments& args) {
v8::TryCatch try_catch;
v8::Script::Compile(v8::String::New("function f() {"
" var term = true;"
" try {"
" while(true) {"
" if (term) terminate();"
" term = false;"
" }"
" fail();"
" } catch(e) {"
" fail();"
" }"
"}"
"f()"))->Run();
CHECK(try_catch.HasCaught());
CHECK(try_catch.Exception()->IsNull());
CHECK(try_catch.Message().IsEmpty());
CHECK(!try_catch.CanContinue());
return v8::Undefined();
}
v8::Handle<v8::ObjectTemplate> CreateGlobalTemplate(
v8::InvocationCallback terminate) {
v8::Handle<v8::ObjectTemplate> global = v8::ObjectTemplate::New();
global->Set(v8::String::New("terminate"),
v8::FunctionTemplate::New(terminate));
global->Set(v8::String::New("fail"), v8::FunctionTemplate::New(Fail));
global->Set(v8::String::New("loop"), v8::FunctionTemplate::New(Loop));
global->Set(v8::String::New("doloop"), v8::FunctionTemplate::New(DoLoop));
return global;
}
// Test that a single thread of JavaScript execution can terminate
// itself.
TEST(TerminateOnlyV8ThreadFromThreadItself) {
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> global =
CreateGlobalTemplate(TerminateCurrentThread);
v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
v8::Context::Scope context_scope(context);
// Run a loop that will be infinite if thread termination does not work.
v8::Handle<v8::String> source =
v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
v8::Script::Compile(source)->Run();
// Test that we can run the code again after thread termination.
v8::Script::Compile(source)->Run();
context.Dispose();
}
class TerminatorThread : public v8::internal::Thread {
void Run() {
semaphore->Wait();
v8::V8::TerminateExecution();
}
};
// Test that a single thread of JavaScript execution can be terminated
// from the side by another thread.
TEST(TerminateOnlyV8ThreadFromOtherThread) {
semaphore = v8::internal::OS::CreateSemaphore(0);
TerminatorThread thread;
thread.Start();
v8::HandleScope scope;
v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(Signal);
v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
v8::Context::Scope context_scope(context);
// Run a loop that will be infinite if thread termination does not work.
v8::Handle<v8::String> source =
v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
v8::Script::Compile(source)->Run();
thread.Join();
delete semaphore;
semaphore = NULL;
context.Dispose();
}
class LoopingThread : public v8::internal::Thread {
public:
void Run() {
v8::Locker locker;
v8::HandleScope scope;
v8_thread_id_ = v8::V8::GetCurrentThreadId();
v8::Handle<v8::ObjectTemplate> global = CreateGlobalTemplate(Signal);
v8::Persistent<v8::Context> context = v8::Context::New(NULL, global);
v8::Context::Scope context_scope(context);
// Run a loop that will be infinite if thread termination does not work.
v8::Handle<v8::String> source =
v8::String::New("try { loop(); fail(); } catch(e) { fail(); }");
v8::Script::Compile(source)->Run();
context.Dispose();
}
int GetV8ThreadId() { return v8_thread_id_; }
private:
int v8_thread_id_;
};
// Test that multiple threads using V8 can be terminated from another
// thread when using Lockers and preemption.
TEST(TerminateMultipleV8Threads) {
{
v8::Locker locker;
v8::V8::Initialize();
v8::Locker::StartPreemption(1);
semaphore = v8::internal::OS::CreateSemaphore(0);
}
LoopingThread thread1;
thread1.Start();
LoopingThread thread2;
thread2.Start();
// Wait until both threads have signaled the semaphore.
semaphore->Wait();
semaphore->Wait();
{
v8::Locker locker;
v8::V8::TerminateExecution(thread1.GetV8ThreadId());
v8::V8::TerminateExecution(thread2.GetV8ThreadId());
}
thread1.Join();
thread2.Join();
delete semaphore;
semaphore = NULL;
}
......@@ -50,5 +50,3 @@ TEST(Preemption) {
script->Run();
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment