Introduce API to temporarily interrupt long running JavaScript code.
It is different from termination API as interrupted JavaScript will continue to execute normally when registered InterruptCallback returns. /** * Request V8 to interrupt long running JavaScript code and invoke * the given |callback| passing the given |data| to it. After |callback| * returns control will be returned to the JavaScript code. * At any given moment V8 can remember only a single callback for the very * last interrupt request. * Can be called from another thread without acquiring a |Locker|. * Registered |callback| must not reenter interrupted Isolate. */ void RequestInterrupt(InterruptCallback callback, void* data); /** * Clear interrupt request created by |RequestInterrupt|. * Can be called from another thread without acquiring a |Locker|. */ void ClearInterrupt(); Fix Hydrogen SCE pass to avoid eliminating stack guards too aggressively. Only normal JavaScript functions are guaranteed to have stack guard in the prologue. If function is a builtin or has a custom call IC it will lack one. BUG= R=danno@chromium.org, dcarney@chromium.org Review URL: https://codereview.chromium.org/102063004 git-svn-id: http://v8.googlecode.com/svn/branches/bleeding_edge@18363 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
parent
e3752b63c0
commit
64d2dfa232
19
include/v8.h
19
include/v8.h
@ -3918,6 +3918,8 @@ enum GCCallbackFlags {
|
||||
typedef void (*GCPrologueCallback)(GCType type, GCCallbackFlags flags);
|
||||
typedef void (*GCEpilogueCallback)(GCType type, GCCallbackFlags flags);
|
||||
|
||||
typedef void (*InterruptCallback)(Isolate* isolate, void* data);
|
||||
|
||||
|
||||
/**
|
||||
* Collection of V8 heap information.
|
||||
@ -4174,6 +4176,23 @@ class V8_EXPORT Isolate {
|
||||
*/
|
||||
void RemoveGCEpilogueCallback(GCEpilogueCallback callback);
|
||||
|
||||
/**
|
||||
* Request V8 to interrupt long running JavaScript code and invoke
|
||||
* the given |callback| passing the given |data| to it. After |callback|
|
||||
* returns control will be returned to the JavaScript code.
|
||||
* At any given moment V8 can remember only a single callback for the very
|
||||
* last interrupt request.
|
||||
* Can be called from another thread without acquiring a |Locker|.
|
||||
* Registered |callback| must not reenter interrupted Isolate.
|
||||
*/
|
||||
void RequestInterrupt(InterruptCallback callback, void* data);
|
||||
|
||||
/**
|
||||
* Clear interrupt request created by |RequestInterrupt|.
|
||||
* Can be called from another thread without acquiring a |Locker|.
|
||||
*/
|
||||
void ClearInterrupt();
|
||||
|
||||
private:
|
||||
Isolate();
|
||||
Isolate(const Isolate&);
|
||||
|
11
src/api.cc
11
src/api.cc
@ -6500,6 +6500,17 @@ void V8::CancelTerminateExecution(Isolate* isolate) {
|
||||
}
|
||||
|
||||
|
||||
void Isolate::RequestInterrupt(InterruptCallback callback, void* data) {
|
||||
reinterpret_cast<i::Isolate*>(this)->stack_guard()->RequestInterrupt(
|
||||
callback, data);
|
||||
}
|
||||
|
||||
|
||||
void Isolate::ClearInterrupt() {
|
||||
reinterpret_cast<i::Isolate*>(this)->stack_guard()->ClearInterrupt();
|
||||
}
|
||||
|
||||
|
||||
Isolate* Isolate::GetCurrent() {
|
||||
i::Isolate* isolate = i::Isolate::UncheckedCurrent();
|
||||
return reinterpret_cast<Isolate*>(isolate);
|
||||
|
@ -540,6 +540,48 @@ void StackGuard::Continue(InterruptFlag after_what) {
|
||||
}
|
||||
|
||||
|
||||
void StackGuard::RequestInterrupt(InterruptCallback callback, void* data) {
|
||||
ExecutionAccess access(isolate_);
|
||||
thread_local_.interrupt_flags_ |= API_INTERRUPT;
|
||||
thread_local_.interrupt_callback_ = callback;
|
||||
thread_local_.interrupt_callback_data_ = data;
|
||||
set_interrupt_limits(access);
|
||||
}
|
||||
|
||||
|
||||
void StackGuard::ClearInterrupt() {
|
||||
thread_local_.interrupt_callback_ = 0;
|
||||
thread_local_.interrupt_callback_data_ = 0;
|
||||
Continue(API_INTERRUPT);
|
||||
}
|
||||
|
||||
|
||||
bool StackGuard::IsAPIInterrupt() {
|
||||
ExecutionAccess access(isolate_);
|
||||
return thread_local_.interrupt_flags_ & API_INTERRUPT;
|
||||
}
|
||||
|
||||
|
||||
void StackGuard::InvokeInterruptCallback() {
|
||||
InterruptCallback callback = 0;
|
||||
void* data = 0;
|
||||
|
||||
{
|
||||
ExecutionAccess access(isolate_);
|
||||
callback = thread_local_.interrupt_callback_;
|
||||
data = thread_local_.interrupt_callback_data_;
|
||||
thread_local_.interrupt_callback_ = NULL;
|
||||
thread_local_.interrupt_callback_data_ = NULL;
|
||||
}
|
||||
|
||||
if (callback != NULL) {
|
||||
VMState<EXTERNAL> state(isolate_);
|
||||
HandleScope handle_scope(isolate_);
|
||||
callback(reinterpret_cast<v8::Isolate*>(isolate_), data);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
char* StackGuard::ArchiveStackGuard(char* to) {
|
||||
ExecutionAccess access(isolate_);
|
||||
OS::MemCopy(to, reinterpret_cast<char*>(&thread_local_), sizeof(ThreadLocal));
|
||||
@ -581,6 +623,7 @@ void StackGuard::ThreadLocal::Clear() {
|
||||
nesting_ = 0;
|
||||
postpone_interrupts_nesting_ = 0;
|
||||
interrupt_flags_ = 0;
|
||||
interrupt_callback_ = 0;
|
||||
}
|
||||
|
||||
|
||||
@ -601,6 +644,7 @@ bool StackGuard::ThreadLocal::Initialize(Isolate* isolate) {
|
||||
nesting_ = 0;
|
||||
postpone_interrupts_nesting_ = 0;
|
||||
interrupt_flags_ = 0;
|
||||
interrupt_callback_ = 0;
|
||||
return should_set_stack_limits;
|
||||
}
|
||||
|
||||
@ -936,6 +980,11 @@ MaybeObject* Execution::HandleStackGuardInterrupt(Isolate* isolate) {
|
||||
return isolate->heap()->undefined_value();
|
||||
}
|
||||
|
||||
if (stack_guard->IsAPIInterrupt()) {
|
||||
stack_guard->InvokeInterruptCallback();
|
||||
stack_guard->Continue(API_INTERRUPT);
|
||||
}
|
||||
|
||||
if (stack_guard->IsGCRequest()) {
|
||||
isolate->heap()->CollectAllGarbage(Heap::kNoGCFlags,
|
||||
"StackGuard GC request");
|
||||
|
@ -43,7 +43,8 @@ enum InterruptFlag {
|
||||
TERMINATE = 1 << 4,
|
||||
GC_REQUEST = 1 << 5,
|
||||
FULL_DEOPT = 1 << 6,
|
||||
INSTALL_CODE = 1 << 7
|
||||
INSTALL_CODE = 1 << 7,
|
||||
API_INTERRUPT = 1 << 8
|
||||
};
|
||||
|
||||
|
||||
@ -222,6 +223,11 @@ class StackGuard {
|
||||
void FullDeopt();
|
||||
void Continue(InterruptFlag after_what);
|
||||
|
||||
void RequestInterrupt(InterruptCallback callback, void* data);
|
||||
void ClearInterrupt();
|
||||
bool IsAPIInterrupt();
|
||||
void InvokeInterruptCallback();
|
||||
|
||||
// This provides an asynchronous read of the stack limits for the current
|
||||
// thread. There are no locks protecting this, but it is assumed that you
|
||||
// have the global V8 lock if you are using multiple V8 threads.
|
||||
@ -307,6 +313,9 @@ class StackGuard {
|
||||
int nesting_;
|
||||
int postpone_interrupts_nesting_;
|
||||
int interrupt_flags_;
|
||||
|
||||
InterruptCallback interrupt_callback_;
|
||||
void* interrupt_callback_data_;
|
||||
};
|
||||
|
||||
// TODO(isolates): Technically this could be calculated directly from a
|
||||
|
@ -1245,7 +1245,7 @@ class HInstruction : public HValue {
|
||||
virtual void Verify() V8_OVERRIDE;
|
||||
#endif
|
||||
|
||||
virtual bool IsCall() { return false; }
|
||||
virtual bool HasStackCheck() { return false; }
|
||||
|
||||
DECLARE_ABSTRACT_INSTRUCTION(Instruction)
|
||||
|
||||
@ -2243,8 +2243,6 @@ class HCall : public HTemplateInstruction<V> {
|
||||
return -argument_count();
|
||||
}
|
||||
|
||||
virtual bool IsCall() V8_FINAL V8_OVERRIDE { return true; }
|
||||
|
||||
private:
|
||||
int argument_count_;
|
||||
};
|
||||
@ -2316,6 +2314,12 @@ class HInvokeFunction V8_FINAL : public HBinaryCall {
|
||||
Handle<JSFunction> known_function() { return known_function_; }
|
||||
int formal_parameter_count() const { return formal_parameter_count_; }
|
||||
|
||||
virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE {
|
||||
return !known_function().is_null() &&
|
||||
(known_function()->code()->kind() == Code::FUNCTION ||
|
||||
known_function()->code()->kind() == Code::OPTIMIZED_FUNCTION);
|
||||
}
|
||||
|
||||
DECLARE_CONCRETE_INSTRUCTION(InvokeFunction)
|
||||
|
||||
private:
|
||||
@ -2348,6 +2352,11 @@ class HCallConstantFunction V8_FINAL : public HCall<0> {
|
||||
return Representation::None();
|
||||
}
|
||||
|
||||
virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE {
|
||||
return (function()->code()->kind() == Code::FUNCTION ||
|
||||
function()->code()->kind() == Code::OPTIMIZED_FUNCTION);
|
||||
}
|
||||
|
||||
DECLARE_CONCRETE_INSTRUCTION(CallConstantFunction)
|
||||
|
||||
private:
|
||||
@ -2465,6 +2474,11 @@ class HCallKnownGlobal V8_FINAL : public HCall<0> {
|
||||
return Representation::None();
|
||||
}
|
||||
|
||||
virtual bool HasStackCheck() V8_FINAL V8_OVERRIDE {
|
||||
return (target()->code()->kind() == Code::FUNCTION ||
|
||||
target()->code()->kind() == Code::OPTIMIZED_FUNCTION);
|
||||
}
|
||||
|
||||
DECLARE_CONCRETE_INSTRUCTION(CallKnownGlobal)
|
||||
|
||||
private:
|
||||
|
@ -43,7 +43,7 @@ void HStackCheckEliminationPhase::Run() {
|
||||
HBasicBlock* dominator = back_edge;
|
||||
while (true) {
|
||||
for (HInstructionIterator it(dominator); !it.Done(); it.Advance()) {
|
||||
if (it.Current()->IsCall()) {
|
||||
if (it.Current()->HasStackCheck()) {
|
||||
block->loop_information()->stack_check()->Eliminate();
|
||||
break;
|
||||
}
|
||||
|
@ -20736,6 +20736,9 @@ THREADED_TEST(SemaphoreInterruption) {
|
||||
}
|
||||
|
||||
|
||||
#endif // V8_OS_POSIX
|
||||
|
||||
|
||||
static bool NamedAccessAlwaysBlocked(Local<v8::Object> global,
|
||||
Local<Value> name,
|
||||
v8::AccessType type,
|
||||
@ -21022,7 +21025,258 @@ THREADED_TEST(CrankshaftInterceptorFieldWrite) {
|
||||
}
|
||||
|
||||
|
||||
#endif // V8_OS_POSIX
|
||||
class RequestInterruptTestBase {
|
||||
public:
|
||||
RequestInterruptTestBase()
|
||||
: env_(),
|
||||
isolate_(env_->GetIsolate()),
|
||||
sem_(0),
|
||||
warmup_(20000),
|
||||
should_continue_(true) {
|
||||
}
|
||||
|
||||
virtual ~RequestInterruptTestBase() { }
|
||||
|
||||
virtual void TestBody() = 0;
|
||||
|
||||
void RunTest() {
|
||||
i::FLAG_print_opt_code = true;
|
||||
i::FLAG_code_comments = true;
|
||||
i::FLAG_print_code_stubs = true;
|
||||
InterruptThread i_thread(this);
|
||||
i_thread.Start();
|
||||
|
||||
v8::HandleScope handle_scope(isolate_);
|
||||
|
||||
TestBody();
|
||||
|
||||
isolate_->ClearInterrupt();
|
||||
}
|
||||
|
||||
void WakeUpInterruptor() {
|
||||
sem_.Signal();
|
||||
}
|
||||
|
||||
bool should_continue() const { return should_continue_; }
|
||||
|
||||
bool ShouldContinue() {
|
||||
if (warmup_ > 0) {
|
||||
if (--warmup_ == 0) {
|
||||
WakeUpInterruptor();
|
||||
}
|
||||
}
|
||||
|
||||
return should_continue_;
|
||||
}
|
||||
|
||||
protected:
|
||||
static void ShouldContinueCallback(
|
||||
const v8::FunctionCallbackInfo<Value>& info) {
|
||||
RequestInterruptTestBase* test =
|
||||
reinterpret_cast<RequestInterruptTestBase*>(
|
||||
info.Data().As<v8::External>()->Value());
|
||||
info.GetReturnValue().Set(test->ShouldContinue());
|
||||
}
|
||||
|
||||
class InterruptThread : public i::Thread {
|
||||
public:
|
||||
explicit InterruptThread(RequestInterruptTestBase* test)
|
||||
: Thread("RequestInterruptTest"), test_(test) {}
|
||||
|
||||
virtual void Run() {
|
||||
test_->sem_.Wait();
|
||||
test_->isolate_->RequestInterrupt(&OnInterrupt, test_);
|
||||
}
|
||||
|
||||
static void OnInterrupt(v8::Isolate* isolate, void* data) {
|
||||
reinterpret_cast<RequestInterruptTestBase*>(data)->
|
||||
should_continue_ = false;
|
||||
}
|
||||
|
||||
private:
|
||||
RequestInterruptTestBase* test_;
|
||||
};
|
||||
|
||||
LocalContext env_;
|
||||
v8::Isolate* isolate_;
|
||||
i::Semaphore sem_;
|
||||
int warmup_;
|
||||
bool should_continue_;
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithFunctionCall : public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
Local<Function> func = Function::New(
|
||||
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this));
|
||||
env_->Global()->Set(v8_str("ShouldContinue"), func);
|
||||
|
||||
CompileRun("while (ShouldContinue()) { }");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithMethodCall : public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
||||
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
||||
proto->Set(v8_str("shouldContinue"), Function::New(
|
||||
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)));
|
||||
env_->Global()->Set(v8_str("Klass"), t->GetFunction());
|
||||
|
||||
CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithAccessor : public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
||||
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
||||
proto->SetAccessorProperty(v8_str("shouldContinue"), FunctionTemplate::New(
|
||||
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)));
|
||||
env_->Global()->Set(v8_str("Klass"), t->GetFunction());
|
||||
|
||||
CompileRun("var obj = new Klass; while (obj.shouldContinue) { }");
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithNativeAccessor : public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
||||
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
||||
proto->SetNativeDataProperty(v8_str("shouldContinue"),
|
||||
&ShouldContinueNativeGetter,
|
||||
NULL,
|
||||
v8::External::New(isolate_, this));
|
||||
env_->Global()->Set(v8_str("Klass"), t->GetFunction());
|
||||
|
||||
CompileRun("var obj = new Klass; while (obj.shouldContinue) { }");
|
||||
}
|
||||
|
||||
private:
|
||||
static void ShouldContinueNativeGetter(
|
||||
Local<String> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
RequestInterruptTestBase* test =
|
||||
reinterpret_cast<RequestInterruptTestBase*>(
|
||||
info.Data().As<v8::External>()->Value());
|
||||
info.GetReturnValue().Set(test->ShouldContinue());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithMethodCallAndInterceptor
|
||||
: public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
v8::Local<v8::FunctionTemplate> t = v8::FunctionTemplate::New(isolate_);
|
||||
v8::Local<v8::Template> proto = t->PrototypeTemplate();
|
||||
proto->Set(v8_str("shouldContinue"), Function::New(
|
||||
isolate_, ShouldContinueCallback, v8::External::New(isolate_, this)));
|
||||
v8::Local<v8::ObjectTemplate> instance_template = t->InstanceTemplate();
|
||||
instance_template->SetNamedPropertyHandler(EmptyInterceptor);
|
||||
|
||||
env_->Global()->Set(v8_str("Klass"), t->GetFunction());
|
||||
|
||||
CompileRun("var obj = new Klass; while (obj.shouldContinue()) { }");
|
||||
}
|
||||
|
||||
private:
|
||||
static void EmptyInterceptor(
|
||||
Local<String> property,
|
||||
const v8::PropertyCallbackInfo<v8::Value>& info) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
class RequestInterruptTestWithMathAbs : public RequestInterruptTestBase {
|
||||
public:
|
||||
virtual void TestBody() {
|
||||
env_->Global()->Set(v8_str("WakeUpInterruptor"), Function::New(
|
||||
isolate_,
|
||||
WakeUpInterruptorCallback,
|
||||
v8::External::New(isolate_, this)));
|
||||
|
||||
env_->Global()->Set(v8_str("ShouldContinue"), Function::New(
|
||||
isolate_,
|
||||
ShouldContinueCallback,
|
||||
v8::External::New(isolate_, this)));
|
||||
|
||||
i::FLAG_allow_natives_syntax = true;
|
||||
CompileRun("function loopish(o) {"
|
||||
" var pre = 10;"
|
||||
" while (o.abs(1) > 0) {"
|
||||
" if (o.abs(1) >= 0 && !ShouldContinue()) break;"
|
||||
" if (pre > 0) {"
|
||||
" if (--pre === 0) WakeUpInterruptor(o === Math);"
|
||||
" }"
|
||||
" }"
|
||||
"}"
|
||||
"var i = 50;"
|
||||
"var obj = {abs: function () { return i-- }, x: null};"
|
||||
"delete obj.x;"
|
||||
"loopish(obj);"
|
||||
"%OptimizeFunctionOnNextCall(loopish);"
|
||||
"loopish(Math);");
|
||||
|
||||
i::FLAG_allow_natives_syntax = false;
|
||||
}
|
||||
|
||||
private:
|
||||
static void WakeUpInterruptorCallback(
|
||||
const v8::FunctionCallbackInfo<Value>& info) {
|
||||
if (!info[0]->BooleanValue()) return;
|
||||
|
||||
RequestInterruptTestBase* test =
|
||||
reinterpret_cast<RequestInterruptTestBase*>(
|
||||
info.Data().As<v8::External>()->Value());
|
||||
test->WakeUpInterruptor();
|
||||
}
|
||||
|
||||
static void ShouldContinueCallback(
|
||||
const v8::FunctionCallbackInfo<Value>& info) {
|
||||
RequestInterruptTestBase* test =
|
||||
reinterpret_cast<RequestInterruptTestBase*>(
|
||||
info.Data().As<v8::External>()->Value());
|
||||
info.GetReturnValue().Set(test->should_continue());
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithFunctionCall) {
|
||||
RequestInterruptTestWithFunctionCall().RunTest();
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithMethodCall) {
|
||||
RequestInterruptTestWithMethodCall().RunTest();
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithAccessor) {
|
||||
RequestInterruptTestWithAccessor().RunTest();
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithNativeAccessor) {
|
||||
RequestInterruptTestWithNativeAccessor().RunTest();
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithMethodCallAndInterceptor) {
|
||||
RequestInterruptTestWithMethodCallAndInterceptor().RunTest();
|
||||
}
|
||||
|
||||
|
||||
THREADED_TEST(RequestInterruptTestWithMathAbs) {
|
||||
RequestInterruptTestWithMathAbs().RunTest();
|
||||
}
|
||||
|
||||
|
||||
static Local<Value> function_new_expected_env;
|
||||
|
Loading…
Reference in New Issue
Block a user