From 4d1362720f2cf782b7f1c3759d09aea7c0e10331 Mon Sep 17 00:00:00 2001 From: "yangguo@chromium.org" Date: Mon, 22 Apr 2013 15:01:45 +0000 Subject: [PATCH] Add methods to allow resuming execution after calling TerminateExecution(). R=mvstanton@chromium.org BUG=v8:2361 Review URL: https://chromiumcodereview.appspot.com/14401008 Patch from Andrew Paprocki . git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@14378 ce2b1a6d-e550-0410-aec6-3dcde31c8c00 --- AUTHORS | 1 + include/v8.h | 48 ++++++++++++++++++++------ src/api.cc | 14 +++++++- src/execution.cc | 7 ++++ src/execution.h | 1 + src/isolate.cc | 19 ++++++++++ src/isolate.h | 1 + test/cctest/test-thread-termination.cc | 37 ++++++++++++++++++++ 8 files changed, 117 insertions(+), 11 deletions(-) diff --git a/AUTHORS b/AUTHORS index d25fc5af5c..1a927c4573 100644 --- a/AUTHORS +++ b/AUTHORS @@ -9,6 +9,7 @@ ARM Ltd. Hewlett-Packard Development Company, LP Igalia, S.L. Joyent, Inc. +Bloomberg Finance L.P. Akinori MUSHA Alexander Botero-Lowry diff --git a/include/v8.h b/include/v8.h index e1c020310b..a6b3ef6744 100644 --- a/include/v8.h +++ b/include/v8.h @@ -3675,6 +3675,24 @@ class V8EXPORT V8 { */ static bool IsExecutionTerminating(Isolate* isolate = NULL); + /** + * Resume execution capability in the given isolate, whose execution + * was previously forcefully terminated using TerminateExecution(). + * + * When execution is forcefully terminated using TerminateExecution(), + * the isolate can not resume execution until all JavaScript frames + * have propagated the uncatchable exception which is generated. This + * method allows the program embedding the engine to handle the + * termination event and resume execution capability, even if + * JavaScript frames remain on the stack. + * + * This method can be used by any thread even if that thread has not + * acquired the V8 lock with a Locker object. + * + * \param isolate The isolate in which to resume execution capability. + */ + static void CancelTerminateExecution(Isolate* isolate); + /** * Releases any resources used by v8 and stops any utility threads * that may be running. Note that disposing v8 is permanent, it @@ -3785,20 +3803,29 @@ class V8EXPORT TryCatch { bool HasCaught() const; /** - * For certain types of exceptions, it makes no sense to continue - * execution. + * 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. + * If CanContinue returns false, the correct action is to perform any C++ + * cleanup needed and then return. If CanContinue returns false and + * HasTerminated returns true, it is possible to call + * CancelTerminateExecution in order to continue calling into the engine. */ bool CanContinue() const; + /** + * Returns true if an exception has been caught due to script execution + * being terminated. + * + * There is no JavaScript representation of an execution termination + * exception. Such exceptions are thrown when the TerminateExecution + * methods are called to terminate a long-running script. + * + * If such an exception has been thrown, HasTerminated will return true, + * indicating that it is possible to call CancelTerminateExecution in order + * to continue calling into the engine. + */ + bool HasTerminated() const; + /** * Throws the exception caught by this TryCatch in a way that avoids * it being caught again by this same TryCatch. As with ThrowException @@ -3874,6 +3901,7 @@ class V8EXPORT TryCatch { bool can_continue_ : 1; bool capture_message_ : 1; bool rethrow_ : 1; + bool has_terminated_ : 1; friend class v8::internal::Isolate; }; diff --git a/src/api.cc b/src/api.cc index 724c8433b4..64928abb10 100644 --- a/src/api.cc +++ b/src/api.cc @@ -1909,7 +1909,8 @@ v8::TryCatch::TryCatch() is_verbose_(false), can_continue_(true), capture_message_(true), - rethrow_(false) { + rethrow_(false), + has_terminated_(false) { isolate_->RegisterTryCatchHandler(this); } @@ -1937,6 +1938,11 @@ bool v8::TryCatch::CanContinue() const { } +bool v8::TryCatch::HasTerminated() const { + return has_terminated_; +} + + v8::Handle v8::TryCatch::ReThrow() { if (!HasCaught()) return v8::Local(); rethrow_ = true; @@ -6116,6 +6122,12 @@ bool V8::IsExecutionTerminating(Isolate* isolate) { } +void V8::CancelTerminateExecution(Isolate* isolate) { + i::Isolate* i_isolate = reinterpret_cast(isolate); + i_isolate->stack_guard()->CancelTerminateExecution(); +} + + Isolate* Isolate::GetCurrent() { i::Isolate* isolate = i::Isolate::UncheckedCurrent(); return reinterpret_cast(isolate); diff --git a/src/execution.cc b/src/execution.cc index 025a25619f..c67fbc2bed 100644 --- a/src/execution.cc +++ b/src/execution.cc @@ -426,6 +426,13 @@ bool StackGuard::IsTerminateExecution() { } +void StackGuard::CancelTerminateExecution() { + ExecutionAccess access(isolate_); + Continue(TERMINATE); + isolate_->CancelTerminateExecution(); +} + + void StackGuard::TerminateExecution() { ExecutionAccess access(isolate_); thread_local_.interrupt_flags_ |= TERMINATE; diff --git a/src/execution.h b/src/execution.h index 9cf8ac649c..3cdbf63e93 100644 --- a/src/execution.h +++ b/src/execution.h @@ -190,6 +190,7 @@ class StackGuard { void Interrupt(); bool IsTerminateExecution(); void TerminateExecution(); + void CancelTerminateExecution(); #ifdef ENABLE_DEBUGGER_SUPPORT bool IsDebugBreak(); void DebugBreak(); diff --git a/src/isolate.cc b/src/isolate.cc index 5d01664bac..b5bd955de5 100644 --- a/src/isolate.cc +++ b/src/isolate.cc @@ -1077,6 +1077,23 @@ Failure* Isolate::TerminateExecution() { } +void Isolate::CancelTerminateExecution() { + if (try_catch_handler()) { + try_catch_handler()->has_terminated_ = false; + } + if (has_pending_exception() && + pending_exception() == heap_.termination_exception()) { + thread_local_top()->external_caught_exception_ = false; + clear_pending_exception(); + } + if (has_scheduled_exception() && + scheduled_exception() == heap_.termination_exception()) { + thread_local_top()->external_caught_exception_ = false; + clear_scheduled_exception(); + } +} + + Failure* Isolate::Throw(Object* exception, MessageLocation* location) { DoThrow(exception, location); return Failure::Exception(); @@ -1990,12 +2007,14 @@ void Isolate::PropagatePendingExceptionToExternalTryCatch() { } else if (thread_local_top_.pending_exception_ == heap()->termination_exception()) { try_catch_handler()->can_continue_ = false; + try_catch_handler()->has_terminated_ = true; try_catch_handler()->exception_ = heap()->null_value(); } else { // At this point all non-object (failure) exceptions have // been dealt with so this shouldn't fail. ASSERT(!pending_exception()->IsFailure()); try_catch_handler()->can_continue_ = true; + try_catch_handler()->has_terminated_ = false; try_catch_handler()->exception_ = pending_exception(); if (!thread_local_top_.pending_message_obj_->IsTheHole()) { try_catch_handler()->message_ = thread_local_top_.pending_message_obj_; diff --git a/src/isolate.h b/src/isolate.h index 927ad0e0fb..9e0293d210 100644 --- a/src/isolate.h +++ b/src/isolate.h @@ -783,6 +783,7 @@ class Isolate { // Out of resource exception helpers. Failure* StackOverflow(); Failure* TerminateExecution(); + void CancelTerminateExecution(); // Administration void Iterate(ObjectVisitor* v); diff --git a/test/cctest/test-thread-termination.cc b/test/cctest/test-thread-termination.cc index 190fc7bbd4..b22c1505f5 100644 --- a/test/cctest/test-thread-termination.cc +++ b/test/cctest/test-thread-termination.cc @@ -372,3 +372,40 @@ TEST(TerminateAndReenterFromThreadItself) { "f()"))->Run()->IsTrue()); context.Dispose(context->GetIsolate()); } + +v8::Handle DoLoopCancelTerminate(const v8::Arguments& args) { + v8::TryCatch try_catch; + CHECK(!v8::V8::IsExecutionTerminating()); + v8::Script::Compile(v8::String::New("var term = true;" + "while(true) {" + " if (term) terminate();" + " term = false;" + "}" + "fail();"))->Run(); + CHECK(try_catch.HasCaught()); + CHECK(try_catch.Exception()->IsNull()); + CHECK(try_catch.Message().IsEmpty()); + CHECK(!try_catch.CanContinue()); + CHECK(v8::V8::IsExecutionTerminating()); + CHECK(try_catch.HasTerminated()); + v8::V8::CancelTerminateExecution(v8::Isolate::GetCurrent()); + CHECK(!v8::V8::IsExecutionTerminating()); + return v8::Undefined(); +} + +// Test that a single thread of JavaScript execution can terminate +// itself and then resume execution. +TEST(TerminateCancelTerminateFromThreadItself) { + v8::HandleScope scope; + v8::Handle global = + CreateGlobalTemplate(TerminateCurrentThread, DoLoopCancelTerminate); + v8::Persistent context = v8::Context::New(NULL, global); + v8::Context::Scope context_scope(context); + CHECK(!v8::V8::IsExecutionTerminating()); + v8::Handle source = + v8::String::New("try { doloop(); } catch(e) { fail(); } 'completed';"); + // Check that execution completed with correct return value. + CHECK(v8::Script::Compile(source)->Run()->Equals(v8_str("completed"))); + context.Dispose(context->GetIsolate()); +} +