зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1556847 - Fix various record/replay bugs, r=loganfsmyth.
--HG-- extra : rebase_source : c4746f6baefb9cbdcbb89397807aba0060a2b369
This commit is contained in:
Родитель
2170492c75
Коммит
affccc3856
|
@ -367,15 +367,9 @@ function timeSinceCheckpoint(id) {
|
||||||
// The checkpoint up to which the recording runs.
|
// The checkpoint up to which the recording runs.
|
||||||
let gLastFlushCheckpoint = InvalidCheckpointId;
|
let gLastFlushCheckpoint = InvalidCheckpointId;
|
||||||
|
|
||||||
// The last saved checkpoint.
|
|
||||||
let gLastSavedCheckpoint = FirstCheckpointId;
|
|
||||||
|
|
||||||
// How often we want to flush the recording.
|
// How often we want to flush the recording.
|
||||||
const FlushMs = 0.5 * 1000;
|
const FlushMs = 0.5 * 1000;
|
||||||
|
|
||||||
// How often we want to save a checkpoint.
|
|
||||||
const SavedCheckpointMs = 0.25 * 1000;
|
|
||||||
|
|
||||||
function addSavedCheckpoint(checkpoint) {
|
function addSavedCheckpoint(checkpoint) {
|
||||||
if (getCheckpointInfo(checkpoint).owner) {
|
if (getCheckpointInfo(checkpoint).owner) {
|
||||||
return;
|
return;
|
||||||
|
@ -384,25 +378,14 @@ function addSavedCheckpoint(checkpoint) {
|
||||||
const owner = pickReplayingChild();
|
const owner = pickReplayingChild();
|
||||||
getCheckpointInfo(checkpoint).owner = owner;
|
getCheckpointInfo(checkpoint).owner = owner;
|
||||||
owner.addSavedCheckpoint(checkpoint);
|
owner.addSavedCheckpoint(checkpoint);
|
||||||
gLastSavedCheckpoint = checkpoint;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function addCheckpoint(checkpoint, duration) {
|
function addCheckpoint(checkpoint, duration) {
|
||||||
assert(!getCheckpointInfo(checkpoint).duration);
|
assert(!getCheckpointInfo(checkpoint).duration);
|
||||||
getCheckpointInfo(checkpoint).duration = duration;
|
getCheckpointInfo(checkpoint).duration = duration;
|
||||||
|
|
||||||
// Mark saved checkpoints as required, unless we haven't spawned any replaying
|
|
||||||
// children yet.
|
|
||||||
if (
|
|
||||||
timeSinceCheckpoint(gLastSavedCheckpoint) >= SavedCheckpointMs &&
|
|
||||||
gReplayingChildren.length > 0
|
|
||||||
) {
|
|
||||||
addSavedCheckpoint(checkpoint + 1);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function ownerChild(checkpoint) {
|
function ownerChild(checkpoint) {
|
||||||
assert(checkpoint <= gLastSavedCheckpoint);
|
|
||||||
while (!getCheckpointInfo(checkpoint).owner) {
|
while (!getCheckpointInfo(checkpoint).owner) {
|
||||||
checkpoint--;
|
checkpoint--;
|
||||||
}
|
}
|
||||||
|
@ -1006,8 +989,8 @@ function handleResumeManifestResponse({
|
||||||
consoleMessages.forEach(msg => gDebugger.onConsoleMessage(msg));
|
consoleMessages.forEach(msg => gDebugger.onConsoleMessage(msg));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (gDebugger && gDebugger.onNewScript) {
|
if (gDebugger) {
|
||||||
scripts.forEach(script => gDebugger.onNewScript(script));
|
scripts.forEach(script => gDebugger._onNewScript(script));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1071,8 +1054,8 @@ function ensureFlushed() {
|
||||||
spawnReplayingChildren();
|
spawnReplayingChildren();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Checkpoints where the recording was flushed to disk are always saved.
|
// Checkpoints where the recording was flushed to disk are saved. This allows
|
||||||
// This allows the recording to be scanned as soon as it has been flushed.
|
// the recording to be scanned as soon as it has been flushed.
|
||||||
addSavedCheckpoint(gLastFlushCheckpoint);
|
addSavedCheckpoint(gLastFlushCheckpoint);
|
||||||
|
|
||||||
// Flushing creates a new region of the recording for replaying children
|
// Flushing creates a new region of the recording for replaying children
|
||||||
|
|
|
@ -324,7 +324,7 @@ ReplayDebugger.prototype = {
|
||||||
replayPushThreadPause() {
|
replayPushThreadPause() {
|
||||||
// The thread has paused so that the user can interact with it. The child
|
// The thread has paused so that the user can interact with it. The child
|
||||||
// will stay paused until this thread-wide pause has been popped.
|
// will stay paused until this thread-wide pause has been popped.
|
||||||
assert(this._paused);
|
this._ensurePaused();
|
||||||
assert(!this._resumeCallback);
|
assert(!this._resumeCallback);
|
||||||
if (++this._threadPauseCount == 1) {
|
if (++this._threadPauseCount == 1) {
|
||||||
// There is no preferred direction of travel after an explicit pause.
|
// There is no preferred direction of travel after an explicit pause.
|
||||||
|
@ -363,7 +363,8 @@ ReplayDebugger.prototype = {
|
||||||
},
|
},
|
||||||
|
|
||||||
_performResume() {
|
_performResume() {
|
||||||
assert(this._paused && !this._threadPauseCount);
|
this._ensurePaused();
|
||||||
|
assert(!this._threadPauseCount);
|
||||||
if (this._resumeCallback && !this._threadPauseCount) {
|
if (this._resumeCallback && !this._threadPauseCount) {
|
||||||
const callback = this._resumeCallback;
|
const callback = this._resumeCallback;
|
||||||
this._invalidateAfterUnpause();
|
this._invalidateAfterUnpause();
|
||||||
|
@ -528,11 +529,11 @@ ReplayDebugger.prototype = {
|
||||||
return data.map(script => this._addScript(script));
|
return data.map(script => this._addScript(script));
|
||||||
},
|
},
|
||||||
|
|
||||||
findAllConsoleMessages() {
|
_onNewScript(data) {
|
||||||
const messages = this._sendRequestMainChild({
|
if (this.onNewScript) {
|
||||||
type: "findConsoleMessages",
|
const script = this._addScript(data);
|
||||||
});
|
this.onNewScript(script);
|
||||||
return messages.map(this._convertConsoleMessage.bind(this));
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////
|
||||||
|
@ -544,7 +545,9 @@ ReplayDebugger.prototype = {
|
||||||
if (source) {
|
if (source) {
|
||||||
return source;
|
return source;
|
||||||
}
|
}
|
||||||
return this._addSource(this._sendRequest({ type: "getSource", id }));
|
return this._addSource(
|
||||||
|
this._sendRequestMainChild({ type: "getSource", id })
|
||||||
|
);
|
||||||
},
|
},
|
||||||
|
|
||||||
_addSource(data) {
|
_addSource(data) {
|
||||||
|
@ -692,6 +695,13 @@ ReplayDebugger.prototype = {
|
||||||
return message;
|
return message;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
findAllConsoleMessages() {
|
||||||
|
const messages = this._sendRequestMainChild({
|
||||||
|
type: "findConsoleMessages",
|
||||||
|
});
|
||||||
|
return messages.map(this._convertConsoleMessage.bind(this));
|
||||||
|
},
|
||||||
|
|
||||||
/////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////
|
||||||
// Handlers
|
// Handlers
|
||||||
/////////////////////////////////////////////////////////
|
/////////////////////////////////////////////////////////
|
||||||
|
|
|
@ -810,6 +810,7 @@ const gManifestFinishedAfterCheckpointHandlers = {
|
||||||
},
|
},
|
||||||
|
|
||||||
runToPoint({ endpoint }, point) {
|
runToPoint({ endpoint }, point) {
|
||||||
|
assert(endpoint.checkpoint >= point.checkpoint);
|
||||||
if (!endpoint.position && point.checkpoint == endpoint.checkpoint) {
|
if (!endpoint.position && point.checkpoint == endpoint.checkpoint) {
|
||||||
RecordReplayControl.manifestFinished({ point });
|
RecordReplayControl.manifestFinished({ point });
|
||||||
}
|
}
|
||||||
|
@ -892,6 +893,7 @@ let gFrameStepsFrameIndex = 0;
|
||||||
// This must be specified for any manifest that uses ensurePositionHandler.
|
// This must be specified for any manifest that uses ensurePositionHandler.
|
||||||
const gManifestPositionHandlers = {
|
const gManifestPositionHandlers = {
|
||||||
resume(manifest, point) {
|
resume(manifest, point) {
|
||||||
|
clearPositionHandlers();
|
||||||
RecordReplayControl.manifestFinished({
|
RecordReplayControl.manifestFinished({
|
||||||
point,
|
point,
|
||||||
consoleMessages: gNewConsoleMessages,
|
consoleMessages: gNewConsoleMessages,
|
||||||
|
@ -1249,6 +1251,7 @@ function getPauseData() {
|
||||||
const names = getEnvironmentNames(env);
|
const names = getEnvironmentNames(env);
|
||||||
rv.environments[id] = { data, names };
|
rv.environments[id] = { data, names };
|
||||||
|
|
||||||
|
addObject(data.callee);
|
||||||
addEnvironment(data.parent);
|
addEnvironment(data.parent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -340,10 +340,30 @@ static void MM_CFTypeOutputArg(MiddlemanCallContext& aCx) {
|
||||||
MM_CFTypeOutput(aCx, arg, /* aOwnsReference = */ false);
|
MM_CFTypeOutput(aCx, arg, /* aOwnsReference = */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static void SendMessageToObject(const void* aObject, const char* aMessage) {
|
||||||
|
CallArguments arguments;
|
||||||
|
arguments.Arg<0, const void*>() = aObject;
|
||||||
|
arguments.Arg<1, SEL>() = sel_registerName(aMessage);
|
||||||
|
RecordReplayInvokeCall(gOriginal_objc_msgSend, &arguments);
|
||||||
|
}
|
||||||
|
|
||||||
// For APIs whose result will be released by the middleman's autorelease pool.
|
// For APIs whose result will be released by the middleman's autorelease pool.
|
||||||
static void MM_AutoreleaseCFTypeRval(MiddlemanCallContext& aCx) {
|
static void MM_AutoreleaseCFTypeRval(MiddlemanCallContext& aCx) {
|
||||||
auto& rval = aCx.mArguments->Rval<const void*>();
|
auto& rval = aCx.mArguments->Rval<const void*>();
|
||||||
MM_SystemOutput(aCx, &rval);
|
MM_SystemOutput(aCx, &rval);
|
||||||
|
|
||||||
|
if (rval) {
|
||||||
|
switch (aCx.mPhase) {
|
||||||
|
case MiddlemanCallPhase::MiddlemanOutput:
|
||||||
|
SendMessageToObject(rval, "retain");
|
||||||
|
break;
|
||||||
|
case MiddlemanCallPhase::MiddlemanRelease:
|
||||||
|
SendMessageToObject(rval, "autorelease");
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// For functions which have an input CFType value and also have side effects on
|
// For functions which have an input CFType value and also have side effects on
|
||||||
|
|
|
@ -264,13 +264,17 @@ void ChildProcessInfo::WaitUntilPaused() {
|
||||||
|
|
||||||
bool sentTerminateMessage = false;
|
bool sentTerminateMessage = false;
|
||||||
while (true) {
|
while (true) {
|
||||||
MonitorAutoLock lock(*gMonitor);
|
Maybe<MonitorAutoLock> lock;
|
||||||
|
lock.emplace(*gMonitor);
|
||||||
|
|
||||||
|
MaybeHandlePendingSyncMessage();
|
||||||
|
|
||||||
// Search for the first message received from this process.
|
// Search for the first message received from this process.
|
||||||
ChildProcessInfo* process = this;
|
ChildProcessInfo* process = this;
|
||||||
Message::UniquePtr msg = ExtractChildMessage(&process);
|
Message::UniquePtr msg = ExtractChildMessage(&process);
|
||||||
|
|
||||||
if (msg) {
|
if (msg) {
|
||||||
|
lock.reset();
|
||||||
OnIncomingMessage(*msg);
|
OnIncomingMessage(*msg);
|
||||||
if (IsPaused()) {
|
if (IsPaused()) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -260,89 +260,83 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
|
||||||
return MsgProcessed;
|
return MsgProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ForwardMessageSync(MiddlemanProtocol* aProtocol,
|
Message* mSyncMessage = nullptr;
|
||||||
Message* aMessage, Message** aReply) {
|
Message* mSyncMessageReply = nullptr;
|
||||||
PrintSpew("ForwardSyncMsg %s\n",
|
bool mSyncMessageIsCall = false;
|
||||||
IPC::StringFromIPCMessageType(aMessage->type()));
|
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(!*aReply);
|
void MaybeSendSyncMessage(bool aLockHeld) {
|
||||||
Message* nReply = new Message();
|
Maybe<MonitorAutoLock> lock;
|
||||||
if (!aProtocol->GetIPCChannel()->Send(aMessage, nReply)) {
|
if (!aLockHeld) {
|
||||||
MOZ_RELEASE_ASSERT(aProtocol->mSide == ipc::ParentSide);
|
lock.emplace(*gMonitor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!mSyncMessage) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PrintSpew("ForwardSyncMsg %s\n",
|
||||||
|
IPC::StringFromIPCMessageType(mSyncMessage->type()));
|
||||||
|
|
||||||
|
MOZ_RELEASE_ASSERT(!mSyncMessageReply);
|
||||||
|
mSyncMessageReply = new Message();
|
||||||
|
if (mSyncMessageIsCall
|
||||||
|
? !mOpposite->GetIPCChannel()->Call(mSyncMessage, mSyncMessageReply)
|
||||||
|
: !mOpposite->GetIPCChannel()->Send(mSyncMessage, mSyncMessageReply)) {
|
||||||
|
MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide);
|
||||||
BeginShutdown();
|
BeginShutdown();
|
||||||
}
|
}
|
||||||
|
|
||||||
MonitorAutoLock lock(*gMonitor);
|
mSyncMessage = nullptr;
|
||||||
*aReply = nReply;
|
|
||||||
gMonitor->Notify();
|
gMonitor->NotifyAll();
|
||||||
|
}
|
||||||
|
|
||||||
|
static void StaticMaybeSendSyncMessage(MiddlemanProtocol* aProtocol) {
|
||||||
|
aProtocol->MaybeSendSyncMessage(false);
|
||||||
|
}
|
||||||
|
|
||||||
|
void HandleSyncMessage(const Message& aMessage, Message*& aReply, bool aCall) {
|
||||||
|
MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
|
||||||
|
|
||||||
|
mSyncMessage = new Message();
|
||||||
|
mSyncMessage->CopyFrom(aMessage);
|
||||||
|
mSyncMessageIsCall = aCall;
|
||||||
|
|
||||||
|
mOppositeMessageLoop->PostTask(
|
||||||
|
NewRunnableFunction("StaticMaybeSendSyncMessage", StaticMaybeSendSyncMessage, this));
|
||||||
|
|
||||||
|
if (mSide == ipc::ChildSide) {
|
||||||
|
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
||||||
|
while (!mSyncMessageReply) {
|
||||||
|
MOZ_CRASH("NYI");
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
MonitorAutoLock lock(*gMonitor);
|
||||||
|
|
||||||
|
// If the main thread is blocked waiting for the recording child to pause,
|
||||||
|
// wake it up so it can call MaybeHandlePendingSyncMessage().
|
||||||
|
gMonitor->NotifyAll();
|
||||||
|
|
||||||
|
while (!mSyncMessageReply) {
|
||||||
|
gMonitor->Wait();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
aReply = mSyncMessageReply;
|
||||||
|
mSyncMessageReply = nullptr;
|
||||||
|
|
||||||
|
PrintSpew("SyncMsgDone\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
virtual Result OnMessageReceived(const Message& aMessage,
|
virtual Result OnMessageReceived(const Message& aMessage,
|
||||||
Message*& aReply) override {
|
Message*& aReply) override {
|
||||||
MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
|
HandleSyncMessage(aMessage, aReply, false);
|
||||||
|
|
||||||
Message* nMessage = new Message();
|
|
||||||
nMessage->CopyFrom(aMessage);
|
|
||||||
mOppositeMessageLoop->PostTask(
|
|
||||||
NewRunnableFunction("ForwardMessageSync", ForwardMessageSync, mOpposite,
|
|
||||||
nMessage, &aReply));
|
|
||||||
|
|
||||||
if (mSide == ipc::ChildSide) {
|
|
||||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
|
||||||
while (!aReply) {
|
|
||||||
MOZ_CRASH("NYI");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MonitorAutoLock lock(*gMonitor);
|
|
||||||
while (!aReply) {
|
|
||||||
gMonitor->Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintSpew("SyncMsgDone\n");
|
|
||||||
return MsgProcessed;
|
return MsgProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
static void ForwardCallMessage(MiddlemanProtocol* aProtocol,
|
|
||||||
Message* aMessage, Message** aReply) {
|
|
||||||
PrintSpew("ForwardSyncCall %s\n",
|
|
||||||
IPC::StringFromIPCMessageType(aMessage->type()));
|
|
||||||
|
|
||||||
MOZ_RELEASE_ASSERT(!*aReply);
|
|
||||||
Message* nReply = new Message();
|
|
||||||
if (!aProtocol->GetIPCChannel()->Call(aMessage, nReply)) {
|
|
||||||
MOZ_RELEASE_ASSERT(aProtocol->mSide == ipc::ParentSide);
|
|
||||||
BeginShutdown();
|
|
||||||
}
|
|
||||||
|
|
||||||
MonitorAutoLock lock(*gMonitor);
|
|
||||||
*aReply = nReply;
|
|
||||||
gMonitor->Notify();
|
|
||||||
}
|
|
||||||
|
|
||||||
virtual Result OnCallReceived(const Message& aMessage,
|
virtual Result OnCallReceived(const Message& aMessage,
|
||||||
Message*& aReply) override {
|
Message*& aReply) override {
|
||||||
MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
|
HandleSyncMessage(aMessage, aReply, true);
|
||||||
|
|
||||||
Message* nMessage = new Message();
|
|
||||||
nMessage->CopyFrom(aMessage);
|
|
||||||
mOppositeMessageLoop->PostTask(
|
|
||||||
NewRunnableFunction("ForwardCallMessage", ForwardCallMessage, mOpposite,
|
|
||||||
nMessage, &aReply));
|
|
||||||
|
|
||||||
if (mSide == ipc::ChildSide) {
|
|
||||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
|
||||||
while (!aReply) {
|
|
||||||
MOZ_CRASH("NYI");
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
MonitorAutoLock lock(*gMonitor);
|
|
||||||
while (!aReply) {
|
|
||||||
gMonitor->Wait();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PrintSpew("SyncCallDone\n");
|
|
||||||
return MsgProcessed;
|
return MsgProcessed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -357,6 +351,12 @@ class MiddlemanProtocol : public ipc::IToplevelProtocol {
|
||||||
static MiddlemanProtocol* gChildProtocol;
|
static MiddlemanProtocol* gChildProtocol;
|
||||||
static MiddlemanProtocol* gParentProtocol;
|
static MiddlemanProtocol* gParentProtocol;
|
||||||
|
|
||||||
|
void MaybeHandlePendingSyncMessage() {
|
||||||
|
if (gParentProtocol) {
|
||||||
|
gParentProtocol->MaybeSendSyncMessage(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
ipc::MessageChannel* ChannelToUIProcess() {
|
ipc::MessageChannel* ChannelToUIProcess() {
|
||||||
return gChildProtocol->GetIPCChannel();
|
return gChildProtocol->GetIPCChannel();
|
||||||
}
|
}
|
||||||
|
|
|
@ -51,6 +51,11 @@ bool MainThreadIsWaitingForIPDLReply();
|
||||||
// to block while waiting on an IPDL reply from the child.
|
// to block while waiting on an IPDL reply from the child.
|
||||||
void ResumeBeforeWaitingForIPDLReply();
|
void ResumeBeforeWaitingForIPDLReply();
|
||||||
|
|
||||||
|
// Immediately forward any sync child->parent IPDL message. These are sent on
|
||||||
|
// the main thread, which might be blocked waiting for a response from the
|
||||||
|
// recording child and unable to run an event loop.
|
||||||
|
void MaybeHandlePendingSyncMessage();
|
||||||
|
|
||||||
// Initialize state which handles incoming IPDL messages from the UI and
|
// Initialize state which handles incoming IPDL messages from the UI and
|
||||||
// recording child processes.
|
// recording child processes.
|
||||||
void InitializeForwarding();
|
void InitializeForwarding();
|
||||||
|
|
Загрузка…
Ссылка в новой задаче