Introduce debug events for Microtask queue.

R=yangguo@chromium.org, adamk@chromium.org, rafaelw@chromium.org, rossberg@chromium.org
BUG=chromium:272416
LOG=Y

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

git-svn-id: https://v8.googlecode.com/svn/branches/bleeding_edge@22204 ce2b1a6d-e550-0410-aec6-3dcde31c8c00
This commit is contained in:
aandrey@chromium.org 2014-07-03 15:56:01 +00:00
parent 7acb28a120
commit 952a986dd1
15 changed files with 228 additions and 11 deletions

View File

@ -21,7 +21,8 @@ enum DebugEvent {
AfterCompile = 5,
CompileError = 6,
PromiseEvent = 7,
BreakForCommand = 8
AsyncTaskEvent = 8,
BreakForCommand = 9
};

View File

@ -20,7 +20,8 @@ Debug.DebugEvent = { Break: 1,
BeforeCompile: 4,
AfterCompile: 5,
CompileError: 6,
PromiseEvent: 7 };
PromiseEvent: 7,
AsyncTaskEvent: 8 };
// Types of exceptions that can be broken upon.
Debug.ExceptionBreak = { Caught : 0,
@ -1226,6 +1227,33 @@ NewPromiseEvent.prototype.resolver = function() {
}
function MakeAsyncTaskEvent(event_data) {
return new AsyncTaskEvent(event_data);
}
function AsyncTaskEvent(event_data) {
this.type_ = event_data.type;
this.name_ = event_data.name;
this.id_ = event_data.id;
}
AsyncTaskEvent.prototype.type = function() {
return this.type_;
}
AsyncTaskEvent.prototype.name = function() {
return this.name_;
}
AsyncTaskEvent.prototype.id = function() {
return this.id_;
}
function DebugCommandProcessor(exec_state, opt_is_running) {
this.exec_state_ = exec_state;
this.running_ = opt_is_running || false;

View File

@ -2554,6 +2554,13 @@ MaybeHandle<Object> Debug::MakePromiseEvent(Handle<JSObject> event_data) {
}
MaybeHandle<Object> Debug::MakeAsyncTaskEvent(Handle<JSObject> task_event) {
// Create the async task event object.
Handle<Object> argv[] = { task_event };
return MakeJSObject("MakeAsyncTaskEvent", ARRAY_SIZE(argv), argv);
}
void Debug::OnException(Handle<Object> exception, bool uncaught) {
if (in_debug_scope() || ignore_events()) return;
@ -2718,6 +2725,25 @@ void Debug::OnPromiseEvent(Handle<JSObject> data) {
}
void Debug::OnAsyncTaskEvent(Handle<JSObject> data) {
if (in_debug_scope() || ignore_events()) return;
HandleScope scope(isolate_);
DebugScope debug_scope(this);
if (debug_scope.failed()) return;
// Create the script collected state object.
Handle<Object> event_data;
// Bail out and don't call debugger if exception.
if (!MakeAsyncTaskEvent(data).ToHandle(&event_data)) return;
// Process debug event.
ProcessDebugEvent(v8::AsyncTaskEvent,
Handle<JSObject>::cast(event_data),
true);
}
void Debug::ProcessDebugEvent(v8::DebugEvent event,
Handle<JSObject> event_data,
bool auto_continue) {

View File

@ -366,6 +366,7 @@ class Debug {
void OnBeforeCompile(Handle<Script> script);
void OnAfterCompile(Handle<Script> script);
void OnPromiseEvent(Handle<JSObject> data);
void OnAsyncTaskEvent(Handle<JSObject> data);
// API facing.
void SetEventListener(Handle<Object> callback, Handle<Object> data);
@ -538,6 +539,8 @@ class Debug {
Handle<Script> script, v8::DebugEvent type);
MUST_USE_RESULT MaybeHandle<Object> MakePromiseEvent(
Handle<JSObject> promise_event);
MUST_USE_RESULT MaybeHandle<Object> MakeAsyncTaskEvent(
Handle<JSObject> task_event);
// Mirror cache handling.
void ClearMirrorCache();

View File

@ -45,6 +45,7 @@ function GetObservationStateJS() {
observationState.notifierObjectInfoMap = %ObservationWeakMapCreate();
observationState.pendingObservers = null;
observationState.nextCallbackPriority = 0;
observationState.lastMicrotaskId = 0;
}
return observationState;
@ -421,7 +422,18 @@ function ObserverEnqueueIfActive(observer, objectInfo, changeRecord) {
var callbackInfo = CallbackInfoNormalize(callback);
if (IS_NULL(GetPendingObservers())) {
SetPendingObservers(nullProtoObject());
%EnqueueMicrotask(ObserveMicrotaskRunner);
if (DEBUG_IS_ACTIVE) {
var id = ++GetObservationStateJS().lastMicrotaskId;
var name = "Object.observe";
%EnqueueMicrotask(function() {
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
ObserveMicrotaskRunner();
%DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
});
%DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
} else {
%EnqueueMicrotask(ObserveMicrotaskRunner);
}
}
GetPendingObservers()[callbackInfo.priority] = callback;
callbackInfo.push(changeRecord);

View File

@ -29,6 +29,7 @@ var promiseValue = GLOBAL_PRIVATE("Promise#value");
var promiseOnResolve = GLOBAL_PRIVATE("Promise#onResolve");
var promiseOnReject = GLOBAL_PRIVATE("Promise#onReject");
var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
var lastMicrotaskId = 0;
(function() {
@ -71,7 +72,7 @@ var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
function PromiseDone(promise, status, value, promiseQueue) {
if (GET_PRIVATE(promise, promiseStatus) === 0) {
PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue));
PromiseEnqueue(value, GET_PRIVATE(promise, promiseQueue), status);
PromiseSet(promise, status, value);
}
}
@ -123,12 +124,24 @@ var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
}
}
function PromiseEnqueue(value, tasks) {
function PromiseEnqueue(value, tasks, status) {
var id, name, instrumenting = DEBUG_IS_ACTIVE;
%EnqueueMicrotask(function() {
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "willHandle", id: id, name: name });
}
for (var i = 0; i < tasks.length; i += 2) {
PromiseHandle(value, tasks[i], tasks[i + 1])
}
if (instrumenting) {
%DebugAsyncTaskEvent({ type: "didHandle", id: id, name: name });
}
});
if (instrumenting) {
id = ++lastMicrotaskId;
name = status > 0 ? "Promise.Resolved" : "Promise.Rejected";
%DebugAsyncTaskEvent({ type: "enqueue", id: id, name: name });
}
}
function PromiseIdResolveHandler(x) { return x }
@ -199,7 +212,7 @@ var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
// Simple chaining.
PromiseChain = function PromiseChain(onResolve, onReject) { // a.k.a.
// flatMap
// flatMap
onResolve = IS_UNDEFINED(onResolve) ? PromiseIdResolveHandler : onResolve;
onReject = IS_UNDEFINED(onReject) ? PromiseIdRejectHandler : onReject;
var deferred = %_CallFunction(this.constructor, PromiseDeferred);
@ -211,10 +224,14 @@ var promiseRaw = GLOBAL_PRIVATE("Promise#raw");
GET_PRIVATE(this, promiseOnReject).push(onReject, deferred);
break;
case +1: // Resolved
PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onResolve, deferred]);
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onResolve, deferred],
+1);
break;
case -1: // Rejected
PromiseEnqueue(GET_PRIVATE(this, promiseValue), [onReject, deferred]);
PromiseEnqueue(GET_PRIVATE(this, promiseValue),
[onReject, deferred],
-1);
break;
}
return deferred.promise;

View File

@ -5576,6 +5576,15 @@ RUNTIME_FUNCTION(Runtime_DebugPromiseEvent) {
}
RUNTIME_FUNCTION(Runtime_DebugAsyncTaskEvent) {
ASSERT(args.length() == 1);
HandleScope scope(isolate);
CONVERT_ARG_HANDLE_CHECKED(JSObject, data, 0);
isolate->debug()->OnAsyncTaskEvent(data);
return isolate->heap()->undefined_value();
}
RUNTIME_FUNCTION(Runtime_DeleteProperty) {
HandleScope scope(isolate);
ASSERT(args.length() == 3);

View File

@ -78,6 +78,7 @@ namespace internal {
F(DebugPromiseHandlePrologue, 1, 1) \
F(DebugPromiseHandleEpilogue, 0, 1) \
F(DebugPromiseEvent, 1, 1) \
F(DebugAsyncTaskEvent, 1, 1) \
F(FlattenString, 1, 1) \
F(LoadMutableDouble, 2, 1) \
F(TryMigrateInstance, 1, 1) \

View File

@ -0,0 +1,61 @@
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-debug-as debug
Debug = debug.Debug;
var base_id = -1;
var exception = null;
var expected = [
"enqueue #1",
"willHandle #1",
"then #1",
"enqueue #2",
"didHandle #1",
"willHandle #2",
"then #2",
"enqueue #3",
"didHandle #2",
"willHandle #3",
"didHandle #3"
];
function assertLog(msg) {
print(msg);
assertTrue(expected.length > 0);
assertEquals(expected.shift(), msg);
if (!expected.length) {
Debug.setListener(null);
}
}
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.AsyncTaskEvent) return;
try {
if (base_id < 0)
base_id = event_data.id();
var id = event_data.id() - base_id + 1;
assertEquals("Promise.Resolved", event_data.name());
assertLog(event_data.type() + " #" + id);
} catch (e) {
print(e + e.stack)
exception = e;
}
}
Debug.setListener(listener);
var resolver;
var p = new Promise(function(resolve, reject) {
resolver = resolve;
});
p.then(function() {
assertLog("then #1");
}).then(function() {
assertLog("then #2");
});
resolver();
assertNull(exception);

View File

@ -30,6 +30,7 @@ q.catch(
});
function listener(event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.AsyncTaskEvent) return;
try {
// Ignore exceptions during startup in stress runs.
if (step >= 1) return;

View File

@ -26,6 +26,7 @@ var q = p.chain(
});
function listener(event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.AsyncTaskEvent) return;
try {
// Ignore exceptions during startup in stress runs.
if (step >= 1) return;

View File

@ -25,6 +25,7 @@ var q = p.chain(
});
function listener(event, exec_state, event_data, data) {
if (event == Debug.DebugEvent.AsyncTaskEvent) return;
try {
// Ignore exceptions during startup in stress runs.
if (step >= 1) return;

View File

@ -0,0 +1,51 @@
// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
// Flags: --expose-debug-as debug
Debug = debug.Debug;
var base_id = -1;
var exception = null;
var expected = [
"enqueue #1",
"willHandle #1",
"didHandle #1",
];
function assertLog(msg) {
print(msg);
assertTrue(expected.length > 0);
assertEquals(expected.shift(), msg);
if (!expected.length) {
Debug.setListener(null);
}
}
function listener(event, exec_state, event_data, data) {
if (event != Debug.DebugEvent.AsyncTaskEvent) return;
try {
if (base_id < 0)
base_id = event_data.id();
var id = event_data.id() - base_id + 1;
assertEquals("Object.observe", event_data.name());
assertLog(event_data.type() + " #" + id);
} catch (e) {
print(e + e.stack)
exception = e;
}
}
Debug.setListener(listener);
var obj = {};
Object.observe(obj, function(changes) {
print(change.type + " " + change.name + " " + change.oldValue);
});
obj.foo = 1;
obj.zoo = 2;
obj.foo = 3;
assertNull(exception);

View File

@ -0,0 +1,5 @@
// Copyright 2014 the V8 project authors. All rights reserved.
// AUTO-GENERATED BY tools/generate-runtime-tests.py, DO NOT MODIFY
// Flags: --allow-natives-syntax --harmony
var _data = new Object();
%DebugAsyncTaskEvent(_data);

View File

@ -47,11 +47,11 @@ EXPAND_MACROS = [
# that the parser doesn't bit-rot. Change the values as needed when you add,
# remove or change runtime functions, but make sure we don't lose our ability
# to parse them!
EXPECTED_FUNCTION_COUNT = 416
EXPECTED_FUZZABLE_COUNT = 331
EXPECTED_FUNCTION_COUNT = 417
EXPECTED_FUZZABLE_COUNT = 332
EXPECTED_CCTEST_COUNT = 6
EXPECTED_UNKNOWN_COUNT = 4
EXPECTED_BUILTINS_COUNT = 808
EXPECTED_BUILTINS_COUNT = 810
# Don't call these at all.