Introduce v8::MicrotasksScope.

This scope is used to control microtasks execution when MicrotasksPolicy::kScoped is engaged.

BUG=chromium:585949
LOG=Y
TEST=ScopedMicrotasks

Review URL: https://codereview.chromium.org/1741893003

Cr-Commit-Position: refs/heads/master@{#34472}
This commit is contained in:
dgozman 2016-03-03 20:01:09 -08:00 committed by Commit bot
parent 4b613a67e0
commit db77cec242
8 changed files with 339 additions and 19 deletions

View File

@ -5059,6 +5059,53 @@ typedef void (*PromiseRejectCallback)(PromiseRejectMessage message);
typedef void (*MicrotasksCompletedCallback)(Isolate*);
typedef void (*MicrotaskCallback)(void* data);
/**
* Policy for running microtasks:
* - explicit: microtasks are invoked with Isolate::RunMicrotasks() method;
* - scoped: microtasks invocation is controlled by MicrotasksScope objects;
* - auto: microtasks are invoked when the script call depth decrements
* to zero.
*/
enum class MicrotasksPolicy { kExplicit, kScoped, kAuto };
/**
* This scope is used to control microtasks when kScopeMicrotasksInvocation
* is used on Isolate. In this mode every non-primitive call to V8 should be
* done inside some MicrotasksScope.
* Microtasks are executed when topmost MicrotasksScope marked as kRunMicrotasks
* exits.
* kDoNotRunMicrotasks should be used to annotate calls not intended to trigger
* microtasks.
*/
class V8_EXPORT MicrotasksScope {
public:
enum Type { kRunMicrotasks, kDoNotRunMicrotasks };
MicrotasksScope(Isolate* isolate, Type type);
~MicrotasksScope();
/**
* Runs microtasks if no kRunMicrotasks scope is currently active.
*/
static void PerformCheckpoint(Isolate* isolate);
/**
* Returns current depth of nested kRunMicrotasks scopes.
*/
static int GetCurrentDepth(Isolate* isolate);
private:
internal::Isolate* const isolate_;
bool run_;
// Prevent copying.
MicrotasksScope(const MicrotasksScope&);
MicrotasksScope& operator=(const MicrotasksScope&);
};
// --- Failed Access Check Callback ---
typedef void (*FailedAccessCheckCallback)(Local<Object> target,
AccessType type,
@ -5885,17 +5932,20 @@ class V8_EXPORT Isolate {
*/
void EnqueueMicrotask(MicrotaskCallback microtask, void* data = NULL);
/**
* Experimental: Controls whether the Microtask Work Queue is automatically
* run when the script call depth decrements to zero.
/**
* Experimental: Controls how Microtasks are invoked. See MicrotasksPolicy
* for details.
*/
void SetAutorunMicrotasks(bool autorun);
void SetMicrotasksPolicy(MicrotasksPolicy policy);
V8_DEPRECATE_SOON("Use SetMicrotasksPolicy",
void SetAutorunMicrotasks(bool autorun));
/**
* Experimental: Returns whether the Microtask Work Queue is automatically
* run when the script call depth decrements to zero.
* Experimental: Returns the policy controlling how Microtasks are invoked.
*/
bool WillAutorunMicrotasks() const;
MicrotasksPolicy GetMicrotasksPolicy() const;
V8_DEPRECATE_SOON("Use GetMicrotasksPolicy",
bool WillAutorunMicrotasks() const);
/**
* Experimental: adds a callback to notify the host application after

View File

@ -157,6 +157,18 @@ class InternalEscapableScope : public v8::EscapableHandleScope {
};
#ifdef V8_ENABLE_CHECKS
void CheckMicrotasksScopesConsistency(i::Isolate* isolate) {
auto handle_scope_implementer = isolate->handle_scope_implementer();
if (handle_scope_implementer->microtasks_policy() ==
v8::MicrotasksPolicy::kScoped) {
DCHECK(handle_scope_implementer->GetMicrotasksScopeDepth() ||
!handle_scope_implementer->DebugMicrotasksScopeDepthIsZero());
}
}
#endif
class CallDepthScope {
public:
explicit CallDepthScope(i::Isolate* isolate, Local<Context> context,
@ -176,6 +188,9 @@ class CallDepthScope {
if (!context_.IsEmpty()) context_->Exit();
if (!escaped_) isolate_->handle_scope_implementer()->DecrementCallDepth();
if (do_callback_) isolate_->FireCallCompletedCallback();
#ifdef V8_ENABLE_CHECKS
if (do_callback_) CheckMicrotasksScopesConsistency(isolate_);
#endif
}
void Escape() {
@ -7298,10 +7313,12 @@ Isolate::SuppressMicrotaskExecutionScope::SuppressMicrotaskExecutionScope(
Isolate* isolate)
: isolate_(reinterpret_cast<i::Isolate*>(isolate)) {
isolate_->handle_scope_implementer()->IncrementCallDepth();
isolate_->handle_scope_implementer()->IncrementMicrotasksSuppressions();
}
Isolate::SuppressMicrotaskExecutionScope::~SuppressMicrotaskExecutionScope() {
isolate_->handle_scope_implementer()->DecrementMicrotasksSuppressions();
isolate_->handle_scope_implementer()->DecrementCallDepth();
}
@ -7443,6 +7460,7 @@ void Isolate::SetPromiseRejectCallback(PromiseRejectCallback callback) {
void Isolate::RunMicrotasks() {
DCHECK(MicrotasksPolicy::kScoped != GetMicrotasksPolicy());
reinterpret_cast<i::Isolate*>(this)->RunMicrotasks();
}
@ -7466,12 +7484,25 @@ void Isolate::EnqueueMicrotask(MicrotaskCallback microtask, void* data) {
void Isolate::SetAutorunMicrotasks(bool autorun) {
reinterpret_cast<i::Isolate*>(this)->set_autorun_microtasks(autorun);
SetMicrotasksPolicy(MicrotasksPolicy::kAuto);
}
bool Isolate::WillAutorunMicrotasks() const {
return reinterpret_cast<const i::Isolate*>(this)->autorun_microtasks();
return GetMicrotasksPolicy() == MicrotasksPolicy::kAuto;
}
void Isolate::SetMicrotasksPolicy(MicrotasksPolicy policy) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(this);
isolate->handle_scope_implementer()->set_microtasks_policy(policy);
}
MicrotasksPolicy Isolate::GetMicrotasksPolicy() const {
i::Isolate* isolate =
reinterpret_cast<i::Isolate*>(const_cast<Isolate*>(this));
return isolate->handle_scope_implementer()->microtasks_policy();
}
@ -7710,6 +7741,49 @@ void Isolate::VisitWeakHandles(PersistentHandleVisitor* visitor) {
}
MicrotasksScope::MicrotasksScope(Isolate* isolate, MicrotasksScope::Type type)
: isolate_(reinterpret_cast<i::Isolate*>(isolate)),
run_(type == MicrotasksScope::kRunMicrotasks) {
auto handle_scope_implementer = isolate_->handle_scope_implementer();
if (run_) handle_scope_implementer->IncrementMicrotasksScopeDepth();
#ifdef V8_ENABLE_CHECKS
if (!run_) handle_scope_implementer->IncrementDebugMicrotasksScopeDepth();
#endif
}
MicrotasksScope::~MicrotasksScope() {
auto handle_scope_implementer = isolate_->handle_scope_implementer();
if (run_) {
handle_scope_implementer->DecrementMicrotasksScopeDepth();
if (MicrotasksPolicy::kScoped ==
handle_scope_implementer->microtasks_policy()) {
PerformCheckpoint(reinterpret_cast<Isolate*>(isolate_));
}
}
#ifdef V8_ENABLE_CHECKS
if (!run_) handle_scope_implementer->DecrementDebugMicrotasksScopeDepth();
#endif
}
void MicrotasksScope::PerformCheckpoint(Isolate* v8Isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8Isolate);
if (IsExecutionTerminatingCheck(isolate)) return;
auto handle_scope_implementer = isolate->handle_scope_implementer();
if (!handle_scope_implementer->GetMicrotasksScopeDepth() &&
!handle_scope_implementer->HasMicrotasksSuppressions()) {
isolate->RunMicrotasks();
}
}
int MicrotasksScope::GetCurrentDepth(Isolate* v8Isolate) {
i::Isolate* isolate = reinterpret_cast<i::Isolate*>(v8Isolate);
return isolate->handle_scope_implementer()->GetMicrotasksScopeDepth();
}
String::Utf8Value::Utf8Value(v8::Local<v8::Value> obj)
: str_(NULL), length_(0) {
if (obj.IsEmpty()) return;

View File

@ -452,6 +452,12 @@ class HandleScopeImplementer {
saved_contexts_(0),
spare_(NULL),
call_depth_(0),
microtasks_depth_(0),
microtasks_suppressions_(0),
#ifdef V8_ENABLE_CHECKS
debug_microtasks_depth_(0),
#endif
microtasks_policy_(v8::MicrotasksPolicy::kAuto),
last_handle_before_deferred_block_(NULL) { }
~HandleScopeImplementer() {
@ -472,10 +478,36 @@ class HandleScopeImplementer {
inline internal::Object** GetSpareOrNewBlock();
inline void DeleteExtensions(internal::Object** prev_limit);
// Call depth represents nested v8 api calls.
inline void IncrementCallDepth() {call_depth_++;}
inline void DecrementCallDepth() {call_depth_--;}
inline bool CallDepthIsZero() { return call_depth_ == 0; }
// Microtasks scope depth represents nested scopes controlling microtasks
// invocation, which happens when depth reaches zero.
inline void IncrementMicrotasksScopeDepth() {microtasks_depth_++;}
inline void DecrementMicrotasksScopeDepth() {microtasks_depth_--;}
inline int GetMicrotasksScopeDepth() { return microtasks_depth_; }
// Possibly nested microtasks suppression scopes prevent microtasks
// from running.
inline void IncrementMicrotasksSuppressions() {microtasks_suppressions_++;}
inline void DecrementMicrotasksSuppressions() {microtasks_suppressions_--;}
inline bool HasMicrotasksSuppressions() { return !!microtasks_suppressions_; }
#ifdef V8_ENABLE_CHECKS
// In debug we check that calls not intended to invoke microtasks are
// still correctly wrapped with microtask scopes.
inline void IncrementDebugMicrotasksScopeDepth() {debug_microtasks_depth_++;}
inline void DecrementDebugMicrotasksScopeDepth() {debug_microtasks_depth_--;}
inline bool DebugMicrotasksScopeDepthIsZero() {
return debug_microtasks_depth_ == 0;
}
#endif
inline void set_microtasks_policy(v8::MicrotasksPolicy policy);
inline v8::MicrotasksPolicy microtasks_policy() const;
inline void EnterContext(Handle<Context> context);
inline void LeaveContext();
inline bool LastEnteredContextWas(Handle<Context> context);
@ -532,6 +564,12 @@ class HandleScopeImplementer {
List<Context*> saved_contexts_;
Object** spare_;
int call_depth_;
int microtasks_depth_;
int microtasks_suppressions_;
#ifdef V8_ENABLE_CHECKS
int debug_microtasks_depth_;
#endif
v8::MicrotasksPolicy microtasks_policy_;
Object** last_handle_before_deferred_block_;
// This is only used for threading support.
HandleScopeData handle_scope_data_;
@ -550,6 +588,17 @@ class HandleScopeImplementer {
const int kHandleBlockSize = v8::internal::KB - 2; // fit in one page
void HandleScopeImplementer::set_microtasks_policy(
v8::MicrotasksPolicy policy) {
microtasks_policy_ = policy;
}
v8::MicrotasksPolicy HandleScopeImplementer::microtasks_policy() const {
return microtasks_policy_;
}
void HandleScopeImplementer::SaveContext(Context* context) {
saved_contexts_.Add(context);
}

View File

@ -2710,7 +2710,11 @@ void Isolate::RemoveCallCompletedCallback(CallCompletedCallback callback) {
void Isolate::FireCallCompletedCallback() {
bool has_call_completed_callbacks = !call_completed_callbacks_.is_empty();
bool run_microtasks = autorun_microtasks() && pending_microtask_count();
bool run_microtasks =
pending_microtask_count() &&
!handle_scope_implementer()->HasMicrotasksSuppressions() &&
handle_scope_implementer()->microtasks_policy() ==
v8::MicrotasksPolicy::kAuto;
if (!has_call_completed_callbacks && !run_microtasks) return;
if (!handle_scope_implementer()->CallDepthIsZero()) return;

View File

@ -378,7 +378,6 @@ typedef List<HeapObject*> DebugObjectCache;
V(HashMap*, external_reference_map, NULL) \
V(HashMap*, root_index_map, NULL) \
V(int, pending_microtask_count, 0) \
V(bool, autorun_microtasks, true) \
V(HStatistics*, hstatistics, NULL) \
V(CompilationStatistics*, turbo_statistics, NULL) \
V(HTracer*, htracer, NULL) \

View File

@ -20921,12 +20921,16 @@ TEST(CallCompletedCallbackTwoExceptions) {
static void MicrotaskOne(const v8::FunctionCallbackInfo<Value>& info) {
v8::HandleScope scope(info.GetIsolate());
v8::MicrotasksScope microtasks(info.GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CompileRun("ext1Calls++;");
}
static void MicrotaskTwo(const v8::FunctionCallbackInfo<Value>& info) {
v8::HandleScope scope(info.GetIsolate());
v8::MicrotasksScope microtasks(info.GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CompileRun("ext2Calls++;");
}
@ -21061,7 +21065,7 @@ TEST(SetAutorunMicrotasks) {
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(1u, microtasks_completed_callback_count);
env->GetIsolate()->SetAutorunMicrotasks(false);
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
env->GetIsolate()->EnqueueMicrotask(
@ -21088,7 +21092,7 @@ TEST(SetAutorunMicrotasks) {
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(3u, microtasks_completed_callback_count);
env->GetIsolate()->SetAutorunMicrotasks(true);
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
CompileRun("1+1;");
@ -21125,7 +21129,7 @@ TEST(SetAutorunMicrotasks) {
TEST(RunMicrotasksWithoutEnteringContext) {
v8::Isolate* isolate = CcTest::isolate();
HandleScope handle_scope(isolate);
isolate->SetAutorunMicrotasks(false);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
Local<Context> context = Context::New(isolate);
{
Context::Scope context_scope(context);
@ -21138,7 +21142,147 @@ TEST(RunMicrotasksWithoutEnteringContext) {
Context::Scope context_scope(context);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(context).FromJust());
}
isolate->SetAutorunMicrotasks(true);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
}
TEST(ScopedMicrotasks) {
LocalContext env;
v8::HandleScope handles(env->GetIsolate());
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kScoped);
{
v8::MicrotasksScope scope1(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
CompileRun(
"var ext1Calls = 0;"
"var ext2Calls = 0;");
CompileRun("1+1;");
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
{
v8::MicrotasksScope scope2(env->GetIsolate(),
v8::MicrotasksScope::kRunMicrotasks);
CompileRun("1+1;");
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
{
v8::MicrotasksScope scope3(env->GetIsolate(),
v8::MicrotasksScope::kRunMicrotasks);
CompileRun("1+1;");
CHECK_EQ(0,
CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0,
CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
CHECK_EQ(0, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
}
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
{
v8::MicrotasksScope scope1(env->GetIsolate(),
v8::MicrotasksScope::kRunMicrotasks);
CompileRun("1+1;");
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
{
v8::MicrotasksScope scope2(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
}
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(0, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
}
{
v8::Isolate::SuppressMicrotaskExecutionScope scope1(env->GetIsolate());
{
v8::MicrotasksScope scope2(env->GetIsolate(),
v8::MicrotasksScope::kRunMicrotasks);
}
v8::MicrotasksScope scope3(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
{
v8::MicrotasksScope scope1(env->GetIsolate(),
v8::MicrotasksScope::kRunMicrotasks);
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(1, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(2, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskTwo).ToLocalChecked());
}
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
env->GetIsolate()->EnqueueMicrotask(
Function::New(env.local(), MicrotaskOne).ToLocalChecked());
{
v8::Isolate::SuppressMicrotaskExecutionScope scope1(env->GetIsolate());
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
v8::MicrotasksScope scope2(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(1, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
v8::MicrotasksScope::PerformCheckpoint(env->GetIsolate());
{
v8::MicrotasksScope scope(env->GetIsolate(),
v8::MicrotasksScope::kDoNotRunMicrotasks);
CHECK_EQ(2, CompileRun("ext1Calls")->Int32Value(env.local()).FromJust());
CHECK_EQ(3, CompileRun("ext2Calls")->Int32Value(env.local()).FromJust());
}
env->GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
}
@ -21161,7 +21305,7 @@ TEST(Regress385349) {
i::FLAG_allow_natives_syntax = true;
v8::Isolate* isolate = CcTest::isolate();
HandleScope handle_scope(isolate);
isolate->SetAutorunMicrotasks(false);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
Local<Context> context = Context::New(isolate);
v8::Debug::SetDebugEventListener(isolate, DebugEventInObserver);
{
@ -21171,7 +21315,7 @@ TEST(Regress385349) {
"obj.a = 0;");
}
isolate->RunMicrotasks();
isolate->SetAutorunMicrotasks(true);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kAuto);
v8::Debug::SetDebugEventListener(isolate, nullptr);
}

View File

@ -105,7 +105,7 @@ TEST(MicrotaskPerIsolateState) {
HarmonyIsolate isolate;
v8::HandleScope scope(isolate.GetIsolate());
LocalContext context1(isolate.GetIsolate());
isolate.GetIsolate()->SetAutorunMicrotasks(false);
isolate.GetIsolate()->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
CompileRun(
"var obj = { calls: 0 };");
v8::Local<v8::Value> obj = CompileRun("obj");

View File

@ -403,7 +403,7 @@ TEST(TerminateFromOtherThreadWhileMicrotaskRunning) {
thread.Start();
v8::Isolate* isolate = CcTest::isolate();
isolate->SetAutorunMicrotasks(false);
isolate->SetMicrotasksPolicy(v8::MicrotasksPolicy::kExplicit);
v8::HandleScope scope(isolate);
v8::Local<v8::ObjectTemplate> global =
CreateGlobalTemplate(CcTest::isolate(), Signal, DoLoop);