Add support for forceful termination of JavaScript execution.
authorager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 19 Aug 2009 15:14:11 +0000 (15:14 +0000)
committerager@chromium.org <ager@chromium.org@ce2b1a6d-e550-0410-aec6-3dcde31c8c00>
Wed, 19 Aug 2009 15:14:11 +0000 (15:14 +0000)
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

20 files changed:
include/v8.h
src/api.cc
src/arm/codegen-arm.cc
src/codegen.h
src/execution.cc
src/execution.h
src/heap.cc
src/heap.h
src/ia32/codegen-ia32.cc
src/messages.cc
src/objects-debug.cc
src/objects-inl.h
src/top.cc
src/top.h
src/v8threads.cc
src/v8threads.h
src/x64/codegen-x64.cc
test/cctest/SConscript
test/cctest/test-thread-termination.cc [new file with mode: 0644]
test/cctest/test-threads.cc

index a0cc4ea..9608bf2 100644 (file)
@@ -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
@@ -2282,6 +2323,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_;
 };
index 88cf2bc..10475f3 100644 (file)
@@ -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()) {
index 40b0ac8..71ffaa2 100644 (file)
@@ -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);
index 243d87c..d6967b7 100644 (file)
@@ -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; }
index 9080f5e..0ad55bd 100644 (file)
@@ -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);
index fba696e..456cbe7 100644 (file)
@@ -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.
index 7d6e442..c7e7e67 100644 (file)
@@ -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);
index ec1e21a..ceec85c 100644 (file)
@@ -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)                                             \
index b8b3db6..bf1f81b 100644 (file)
@@ -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);
index a3fffcb..e16b1b2 100644 (file)
@@ -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>");
index e97b207..f713171 100644 (file)
@@ -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);
   }
 }
 
index 7b750b6..91aae2f 100644 (file)
@@ -788,6 +788,7 @@ Failure* Failure::Exception() {
   return Construct(EXCEPTION);
 }
 
+
 Failure* Failure::OutOfMemoryException() {
   return Construct(OUT_OF_MEMORY_EXCEPTION);
 }
index 96d4a01..550703a 100644 (file)
@@ -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_);
index 25242f7..d4d73c2 100644 (file)
--- a/src/top.h
+++ b/src/top.h
@@ -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();
index c5fc9fa..8e0a8be 100644 (file)
@@ -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);
+    }
   }
 }
 
index 83f69f0..3f81f57 100644 (file)
@@ -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();
index 7fe6ebd..462b960 100644 (file)
@@ -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);
index 112ecd6..fc4e01a 100644 (file)
@@ -56,6 +56,7 @@ SOURCES = {
     'test-spaces.cc',
     'test-strings.cc',
     'test-threads.cc',
+    'test-thread-termination.cc',
     'test-utils.cc',
     'test-version.cc'
   ],
diff --git a/test/cctest/test-thread-termination.cc b/test/cctest/test-thread-termination.cc
new file mode 100644 (file)
index 0000000..323be1d
--- /dev/null
@@ -0,0 +1,195 @@
+// 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;
+}
index c0d55f2..f70c4e8 100644 (file)
@@ -50,5 +50,3 @@ TEST(Preemption) {
 
   script->Run();
 }
-
-