*/
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
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.
*
void* exception_;
void* message_;
bool is_verbose_;
+ bool can_continue_;
bool capture_message_;
void* js_handler_;
};
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)
}
+bool v8::TryCatch::CanContinue() const {
+ return can_continue_;
+}
+
+
v8::Local<Value> v8::TryCatch::Exception() const {
if (HasCaught()) {
// Check for out of memory exception.
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()) {
}
-void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
+void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
+ UncatchableExceptionType type) {
// Adjust this code if not the case.
ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize);
// 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)
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,
__ 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()));
__ 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);
// 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,
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
+ &throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
__ 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);
// 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"
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; }
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());
}
}
+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;
}
#endif
if (StackGuard::IsPreempted()) RuntimePreempt();
+ if (StackGuard::IsTerminateExecution()) {
+ StackGuard::Continue(TERMINATE);
+ return Top::TerminateExecution();
+ }
if (StackGuard::IsInterrupted()) {
// interrupt
StackGuard::Continue(INTERRUPT);
INTERRUPT = 1 << 0,
DEBUGBREAK = 1 << 1,
DEBUGCOMMAND = 1 << 2,
- PREEMPT = 1 << 3
+ PREEMPT = 1 << 3,
+ TERMINATE = 1 << 4
};
class Execution : public AllStatic {
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.
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);
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) \
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,
__ 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);
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);
}
-void CEntryStub::GenerateThrowOutOfMemory(MacroAssembler* masm) {
+void CEntryStub::GenerateThrowUncatchable(MacroAssembler* masm,
+ UncatchableExceptionType type) {
// Adjust this code if not the case.
ASSERT(StackHandlerConstants::kSize == 4 * kPointerSize);
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.
// 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,
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
+ &throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
__ 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);
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>");
} 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);
}
}
return Construct(EXCEPTION);
}
+
Failure* Failure::OutOfMemoryException() {
return Construct(OUT_OF_MEMORY_EXCEPTION);
}
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();
}
+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();
}
-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()));
//
// 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.
// 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.
// 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()) {
}
-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_);
// 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_;
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());
// 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_;
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);
// Out of resource exception helpers.
static Failure* StackOverflow();
+ static Failure* TerminateExecution();
// Administration
static void Initialize();
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);
ThreadState::ThreadState() : id_(ThreadManager::kInvalidId),
+ terminate_on_restore_(false),
next_(this), previous_(this) {
}
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);
+ }
}
}
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:
void AllocateSpace();
int id_;
+ bool terminate_on_restore_;
char* data_;
ThreadState* next_;
ThreadState* previous_;
static int CurrentId();
static void AssignId();
+ static void TerminateExecution(int thread_id);
+
static const int kInvalidId = -1;
private:
static void EagerlyArchiveThread();
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,
__ 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);
__ 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);
}
-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);
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.
// 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,
// Do space-specific GC and retry runtime call.
GenerateCore(masm,
&throw_normal_exception,
+ &throw_termination_exception,
&throw_out_of_memory_exception,
frame_type,
true,
__ 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);
'test-spaces.cc',
'test-strings.cc',
'test-threads.cc',
+ 'test-thread-termination.cc',
'test-utils.cc',
'test-version.cc'
],
--- /dev/null
+// 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;
+}