Revision: 2723 Author: [email protected] Date: Wed Aug 19 08:14:11 2009 Log: 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 http://code.google.com/p/v8/source/detail?r=2723 Added: /branches/bleeding_edge/test/cctest/test-thread-termination.cc Modified: /branches/bleeding_edge/include/v8.h /branches/bleeding_edge/src/api.cc /branches/bleeding_edge/src/arm/codegen-arm.cc /branches/bleeding_edge/src/codegen.h /branches/bleeding_edge/src/execution.cc /branches/bleeding_edge/src/execution.h /branches/bleeding_edge/src/heap.cc /branches/bleeding_edge/src/heap.h /branches/bleeding_edge/src/ia32/codegen-ia32.cc /branches/bleeding_edge/src/messages.cc /branches/bleeding_edge/src/objects-debug.cc /branches/bleeding_edge/src/objects-inl.h /branches/bleeding_edge/src/top.cc /branches/bleeding_edge/src/top.h /branches/bleeding_edge/src/v8threads.cc /branches/bleeding_edge/src/v8threads.h /branches/bleeding_edge/src/x64/codegen-x64.cc /branches/bleeding_edge/test/cctest/SConscript /branches/bleeding_edge/test/cctest/test-threads.cc ======================================= --- /dev/null +++ /branches/bleeding_edge/test/cctest/test-thread-termination.cc Wed Aug 19 08:14:11 2009 @@ -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; +} ======================================= --- /branches/bleeding_edge/include/v8.h Mon Aug 17 06:34:41 2009 +++ /branches/bleeding_edge/include/v8.h Wed Aug 19 08:14:11 2009 @@ -2223,6 +2223,47 @@ */ 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 @@ */ 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 @@ void* exception_; void* message_; bool is_verbose_; + bool can_continue_; bool capture_message_; void* js_handler_; }; ======================================= --- /branches/bleeding_edge/src/api.cc Tue Aug 18 00:14:02 2009 +++ /branches/bleeding_edge/src/api.cc Wed Aug 19 08:14:11 2009 @@ -75,7 +75,7 @@ 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) @@ -1206,6 +1206,11 @@ bool v8::TryCatch::HasCaught() const { return !reinterpret_cast<i::Object*>(exception_)->IsTheHole(); } + + +bool v8::TryCatch::CanContinue() const { + return can_continue_; +} v8::Local<Value> v8::TryCatch::Exception() const { @@ -3353,6 +3358,34 @@ #endif 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()"); ======================================= --- /branches/bleeding_edge/src/arm/codegen-arm.cc Tue Aug 18 03:52:14 2009 +++ /branches/bleeding_edge/src/arm/codegen-arm.cc Wed Aug 19 08:14:11 2009 @@ -5701,7 +5701,8 @@ } -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 @@ // 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)); - - // 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)); + __ pop(r2); + __ str(r2, MemOperand(r3)); + + 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::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 @@ __ 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 @@ __ 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 @@ // 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 @@ // 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 @@ __ 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); ======================================= --- /branches/bleeding_edge/src/codegen.h Mon Jun 29 10:07:30 2009 +++ /branches/bleeding_edge/src/codegen.h Wed Aug 19 08:14:11 2009 @@ -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 @@ 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; } ======================================= --- /branches/bleeding_edge/src/execution.cc Mon Aug 17 03:19:00 2009 +++ /branches/bleeding_edge/src/execution.cc Wed Aug 19 08:14:11 2009 @@ -156,7 +156,8 @@ 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()); } @@ -326,6 +327,19 @@ thread_local_.interrupt_flags_ |= PREEMPT; set_limits(kInterruptLimit, access); } + + +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 @@ -638,6 +652,10 @@ } #endif if (StackGuard::IsPreempted()) RuntimePreempt(); + if (StackGuard::IsTerminateExecution()) { + StackGuard::Continue(TERMINATE); + return Top::TerminateExecution(); + } if (StackGuard::IsInterrupted()) { // interrupt StackGuard::Continue(INTERRUPT); ======================================= --- /branches/bleeding_edge/src/execution.h Wed Aug 19 03:18:30 2009 +++ /branches/bleeding_edge/src/execution.h Wed Aug 19 08:14:11 2009 @@ -37,7 +37,8 @@ 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 @@ 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. ======================================= --- /branches/bleeding_edge/src/heap.cc Wed Aug 19 03:36:19 2009 +++ /branches/bleeding_edge/src/heap.cc Wed Aug 19 08:14:11 2009 @@ -1412,6 +1412,9 @@ 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); ======================================= --- /branches/bleeding_edge/src/heap.h Tue Aug 18 04:26:14 2009 +++ /branches/bleeding_edge/src/heap.h Wed Aug 19 08:14:11 2009 @@ -111,6 +111,7 @@ 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) \ ======================================= --- /branches/bleeding_edge/src/ia32/codegen-ia32.cc Tue Aug 18 03:52:14 2009 +++ /branches/bleeding_edge/src/ia32/codegen-ia32.cc Wed Aug 19 08:14:11 2009 @@ -7505,6 +7505,7 @@ 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 @@ __ 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 @@ 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::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 @@ 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); - - // 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; + 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); + } + + // Clear the context pointer. __ xor_(esi, Operand(esi)); // Restore fp from handler and discard handler state. @@ -7667,11 +7670,14 @@ // 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 @@ // 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 @@ __ 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); ======================================= --- /branches/bleeding_edge/src/messages.cc Mon May 25 03:05:56 2009 +++ /branches/bleeding_edge/src/messages.cc Wed Aug 19 08:14:11 2009 @@ -147,14 +147,12 @@ 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>"); ======================================= --- /branches/bleeding_edge/src/objects-debug.cc Wed Aug 19 00:30:20 2009 +++ /branches/bleeding_edge/src/objects-debug.cc Wed Aug 19 08:14:11 2009 @@ -705,7 +705,8 @@ } 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); } } ======================================= --- /branches/bleeding_edge/src/objects-inl.h Wed Aug 19 00:30:20 2009 +++ /branches/bleeding_edge/src/objects-inl.h Wed Aug 19 08:14:11 2009 @@ -787,6 +787,7 @@ Failure* Failure::Exception() { return Construct(EXCEPTION); } + Failure* Failure::OutOfMemoryException() { return Construct(OUT_OF_MEMORY_EXCEPTION); ======================================= --- /branches/bleeding_edge/src/top.cc Fri Jun 26 06:09:50 2009 +++ /branches/bleeding_edge/src/top.cc Wed Aug 19 08:14:11 2009 @@ -98,6 +98,7 @@ 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(); @@ -596,6 +597,12 @@ DoThrow(*exception, NULL, kStackOverflowMessage); return Failure::Exception(); } + + +Failure* Top::TerminateExecution() { + DoThrow(Heap::termination_exception(), NULL, NULL); + return Failure::Exception(); +} Failure* Top::Throw(Object* exception, MessageLocation* location) { @@ -694,7 +701,8 @@ } -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 @@ // // 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 @@ // 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 @@ // 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 @@ } -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; - - // 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_) { + 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 (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_); ======================================= --- /branches/bleeding_edge/src/top.h Fri Jun 26 06:09:50 2009 +++ /branches/bleeding_edge/src/top.h Wed Aug 19 08:14:11 2009 @@ -46,6 +46,7 @@ // 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_; @@ -117,6 +118,10 @@ static void set_save_context(SaveContext* save) { 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() { @@ -152,7 +157,8 @@ // 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 @@ 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 @@ // Out of resource exception helpers. static Failure* StackOverflow(); + static Failure* TerminateExecution(); // Administration static void Initialize(); ======================================= --- /branches/bleeding_edge/src/v8threads.cc Wed May 20 01:05:12 2009 +++ /branches/bleeding_edge/src/v8threads.cc Wed Aug 19 08:14:11 2009 @@ -151,6 +151,10 @@ 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() : id_(ThreadManager::kInvalidId), + terminate_on_restore_(false), next_(this), previous_(this) { } @@ -317,7 +322,21 @@ 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); + } } } ======================================= --- /branches/bleeding_edge/src/v8threads.h Mon May 25 03:05:56 2009 +++ /branches/bleeding_edge/src/v8threads.h Wed Aug 19 08:14:11 2009 @@ -49,6 +49,12 @@ // Id of thread. 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_; } @@ -58,6 +64,7 @@ void AllocateSpace(); int id_; + bool terminate_on_restore_; char* data_; ThreadState* next_; ThreadState* previous_; @@ -88,6 +95,8 @@ static int CurrentId(); static void AssignId(); + static void TerminateExecution(int thread_id); + static const int kInvalidId = -1; private: static void EagerlyArchiveThread(); ======================================= --- /branches/bleeding_edge/src/x64/codegen-x64.cc Wed Aug 19 03:18:30 2009 +++ /branches/bleeding_edge/src/x64/codegen-x64.cc Wed Aug 19 08:14:11 2009 @@ -6747,6 +6747,7 @@ 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 @@ __ 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 @@ __ 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::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 @@ 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); - - // Set external caught exception to false. - __ movq(rax, Immediate(false)); - ExternalReference external_caught(Top::k_external_caught_exception_address); - __ 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); - - // Clear the context pointer; + __ movq(kScratchRegister, handler_address); + __ pop(Operand(kScratchRegister, 0)); + + 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. + ExternalReference pending_exception(Top::k_pending_exception_address); + __ movq(rax, Failure::OutOfMemoryException(), RelocInfo::NONE); + __ store_rax(pending_exception); + } + + // Clear the context pointer. __ xor_(rsi, rsi); // Restore registers from handler. @@ -6956,12 +6958,14 @@ // 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 @@ // 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 @@ __ 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); ======================================= --- /branches/bleeding_edge/test/cctest/SConscript Fri Jul 31 04:07:05 2009 +++ /branches/bleeding_edge/test/cctest/SConscript Wed Aug 19 08:14:11 2009 @@ -56,6 +56,7 @@ 'test-spaces.cc', 'test-strings.cc', 'test-threads.cc', + 'test-thread-termination.cc', 'test-utils.cc', 'test-version.cc' ], ======================================= --- /branches/bleeding_edge/test/cctest/test-threads.cc Wed Dec 17 09:40:02 2008 +++ /branches/bleeding_edge/test/cctest/test-threads.cc Wed Aug 19 08:14:11 2009 @@ -50,5 +50,3 @@ script->Run(); } - - --~--~---------~--~----~------------~-------~--~----~ v8-dev mailing list [email protected] http://groups.google.com/group/v8-dev -~----------~----~----~----~------~----~------~--~---
