зеркало из https://github.com/mozilla/gecko-dev.git
Bug 1465287 Part 3 - Middleman side of record/replay IPC, r=mccr8,jld.
--HG-- extra : rebase_source : c0fec84bf1bb88d3973d6367c273be2f4b442b31
This commit is contained in:
Родитель
14a96e97ac
Коммит
b8c260609d
|
@ -0,0 +1,690 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#include "ParentInternal.h"
|
||||
|
||||
#include "base/task.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "Thread.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
// A saved introduction message for sending to all children.
|
||||
static IntroductionMessage* gIntroductionMessage;
|
||||
|
||||
// How many channels have been constructed so far.
|
||||
static size_t gNumChannels;
|
||||
|
||||
// Whether children might be debugged and should not be treated as hung.
|
||||
static bool gChildrenAreDebugging;
|
||||
|
||||
// Whether we are allowed to restart crashed/hung child processes.
|
||||
static bool gRestartEnabled;
|
||||
|
||||
/* static */ void
|
||||
ChildProcessInfo::SetIntroductionMessage(IntroductionMessage* aMessage)
|
||||
{
|
||||
gIntroductionMessage = aMessage;
|
||||
}
|
||||
|
||||
ChildProcessInfo::ChildProcessInfo(UniquePtr<ChildRole> aRole, bool aRecording)
|
||||
: mProcess(nullptr)
|
||||
, mChannel(nullptr)
|
||||
, mRecording(aRecording)
|
||||
, mRecoveryStage(RecoveryStage::None)
|
||||
, mPaused(false)
|
||||
, mPausedMessage(nullptr)
|
||||
, mLastCheckpoint(CheckpointId::Invalid)
|
||||
, mNumRecoveredMessages(0)
|
||||
, mNumRestarts(0)
|
||||
, mRole(std::move(aRole))
|
||||
, mPauseNeeded(false)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
static bool gFirst = false;
|
||||
if (!gFirst) {
|
||||
gFirst = true;
|
||||
gChildrenAreDebugging = !!getenv("WAIT_AT_START");
|
||||
gRestartEnabled = !getenv("NO_RESTARTS");
|
||||
}
|
||||
|
||||
mRole->SetProcess(this);
|
||||
|
||||
LaunchSubprocess();
|
||||
|
||||
// Replaying processes always save the first checkpoint, if saving
|
||||
// checkpoints is allowed. This is currently assumed by the rewinding
|
||||
// mechanism in the replaying process, and would be nice to investigate
|
||||
// removing.
|
||||
if (!aRecording && CanRewind()) {
|
||||
SendMessage(SetSaveCheckpointMessage(CheckpointId::First, true));
|
||||
}
|
||||
|
||||
mRole->Initialize();
|
||||
}
|
||||
|
||||
ChildProcessInfo::~ChildProcessInfo()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
if (IsRecording()) {
|
||||
SendMessage(TerminateMessage());
|
||||
}
|
||||
TerminateSubprocess();
|
||||
}
|
||||
|
||||
ChildProcessInfo::Disposition
|
||||
ChildProcessInfo::GetDisposition()
|
||||
{
|
||||
// We can determine the disposition of the child by looking at the first
|
||||
// resume message sent since the last time it reached a checkpoint.
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::Resume) {
|
||||
const ResumeMessage& nmsg = static_cast<const ResumeMessage&>(*msg);
|
||||
return nmsg.mForward ? AfterLastCheckpoint : BeforeLastCheckpoint;
|
||||
}
|
||||
}
|
||||
return AtLastCheckpoint;
|
||||
}
|
||||
|
||||
bool
|
||||
ChildProcessInfo::IsPausedAtCheckpoint()
|
||||
{
|
||||
return IsPaused() && mPausedMessage->mType == MessageType::HitCheckpoint;
|
||||
}
|
||||
|
||||
bool
|
||||
ChildProcessInfo::IsPausedAtRecordingEndpoint()
|
||||
{
|
||||
if (!IsPaused()) {
|
||||
return false;
|
||||
}
|
||||
if (mPausedMessage->mType == MessageType::HitCheckpoint) {
|
||||
return static_cast<HitCheckpointMessage*>(mPausedMessage)->mRecordingEndpoint;
|
||||
}
|
||||
if (mPausedMessage->mType == MessageType::HitBreakpoint) {
|
||||
return static_cast<HitBreakpointMessage*>(mPausedMessage)->mRecordingEndpoint;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
bool
|
||||
ChildProcessInfo::IsPausedAtMatchingBreakpoint(const BreakpointFilter& aFilter)
|
||||
{
|
||||
if (!IsPaused() || mPausedMessage->mType != MessageType::HitBreakpoint) {
|
||||
return false;
|
||||
}
|
||||
|
||||
HitBreakpointMessage* npaused = static_cast<HitBreakpointMessage*>(mPausedMessage);
|
||||
for (size_t i = 0; i < npaused->NumBreakpoints(); i++) {
|
||||
uint32_t breakpointId = npaused->Breakpoints()[i];
|
||||
|
||||
// Find the last time we sent a SetBreakpoint message to this process for
|
||||
// this breakpoint ID.
|
||||
SetBreakpointMessage* lastSet = nullptr;
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::SetBreakpoint) {
|
||||
SetBreakpointMessage* nmsg = static_cast<SetBreakpointMessage*>(msg);
|
||||
if (nmsg->mId == breakpointId) {
|
||||
lastSet = nmsg;
|
||||
}
|
||||
}
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(lastSet &&
|
||||
lastSet->mPosition.kind != JS::replay::ExecutionPosition::Invalid);
|
||||
if (aFilter(lastSet->mPosition.kind)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::AddMajorCheckpoint(size_t aId)
|
||||
{
|
||||
// Major checkpoints should be listed in order.
|
||||
MOZ_RELEASE_ASSERT(mMajorCheckpoints.empty() || aId > mMajorCheckpoints.back());
|
||||
mMajorCheckpoints.append(aId);
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::SetRole(UniquePtr<ChildRole> aRole)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!IsRecovering());
|
||||
|
||||
PrintSpew("SetRole:%d %s\n", (int) GetId(), ChildRole::TypeString(aRole->GetType()));
|
||||
|
||||
mRole = std::move(aRole);
|
||||
mRole->SetProcess(this);
|
||||
mRole->Initialize();
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::OnIncomingMessage(size_t aChannelId, const Message& aMsg)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Ignore messages from channels for subprocesses we terminated already.
|
||||
if (aChannelId != mChannel->GetId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Always handle fatal errors in the same way.
|
||||
if (aMsg.mType == MessageType::FatalError) {
|
||||
const FatalErrorMessage& nmsg = static_cast<const FatalErrorMessage&>(aMsg);
|
||||
AttemptRestart(nmsg.Error());
|
||||
return;
|
||||
}
|
||||
|
||||
mLastMessageTime = TimeStamp::Now();
|
||||
|
||||
if (IsRecovering()) {
|
||||
OnIncomingRecoveryMessage(aMsg);
|
||||
return;
|
||||
}
|
||||
|
||||
// Update paused state.
|
||||
MOZ_RELEASE_ASSERT(!IsPaused());
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint:
|
||||
case MessageType::HitBreakpoint:
|
||||
MOZ_RELEASE_ASSERT(!mPausedMessage);
|
||||
mPausedMessage = aMsg.Clone();
|
||||
MOZ_FALLTHROUGH;
|
||||
case MessageType::DebuggerResponse:
|
||||
case MessageType::RecordingFlushed:
|
||||
MOZ_RELEASE_ASSERT(mPausedMessage);
|
||||
mPaused = true;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
if (aMsg.mType == MessageType::HitCheckpoint) {
|
||||
const HitCheckpointMessage& nmsg = static_cast<const HitCheckpointMessage&>(aMsg);
|
||||
mLastCheckpoint = nmsg.mCheckpointId;
|
||||
|
||||
// All messages sent since the last checkpoint are now obsolete, except
|
||||
// SetBreakpoint messages.
|
||||
InfallibleVector<Message*> newMessages;
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::SetBreakpoint) {
|
||||
// Look for an older SetBreakpoint on the same ID to overwrite.
|
||||
bool found = false;
|
||||
for (Message*& older : newMessages) {
|
||||
if (static_cast<SetBreakpointMessage*>(msg)->mId ==
|
||||
static_cast<SetBreakpointMessage*>(older)->mId) {
|
||||
free(older);
|
||||
older = msg;
|
||||
found = true;
|
||||
}
|
||||
}
|
||||
if (!found) {
|
||||
newMessages.emplaceBack(msg);
|
||||
}
|
||||
} else {
|
||||
free(msg);
|
||||
}
|
||||
}
|
||||
mMessages = std::move(newMessages);
|
||||
}
|
||||
|
||||
// The primordial HitCheckpoint messages is not forwarded to the role, as it
|
||||
// has not been initialized yet.
|
||||
if (aMsg.mType != MessageType::HitCheckpoint || mLastCheckpoint) {
|
||||
mRole->OnIncomingMessage(aMsg);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::SendMessage(const Message& aMsg)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!IsRecovering());
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
// Update paused state.
|
||||
MOZ_RELEASE_ASSERT(IsPaused() ||
|
||||
aMsg.mType == MessageType::CreateCheckpoint ||
|
||||
aMsg.mType == MessageType::Terminate);
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::Resume:
|
||||
case MessageType::RestoreCheckpoint:
|
||||
free(mPausedMessage);
|
||||
mPausedMessage = nullptr;
|
||||
MOZ_FALLTHROUGH;
|
||||
case MessageType::DebuggerRequest:
|
||||
case MessageType::FlushRecording:
|
||||
mPaused = false;
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep track of messages which affect the child's behavior.
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::Resume:
|
||||
case MessageType::RestoreCheckpoint:
|
||||
case MessageType::DebuggerRequest:
|
||||
case MessageType::SetBreakpoint:
|
||||
mMessages.emplaceBack(aMsg.Clone());
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
// Keep track of the checkpoints the process will save.
|
||||
if (aMsg.mType == MessageType::SetSaveCheckpoint) {
|
||||
const SetSaveCheckpointMessage& nmsg = static_cast<const SetSaveCheckpointMessage&>(aMsg);
|
||||
MOZ_RELEASE_ASSERT(nmsg.mCheckpoint > MostRecentCheckpoint());
|
||||
VectorAddOrRemoveEntry(mShouldSaveCheckpoints, nmsg.mCheckpoint, nmsg.mSave);
|
||||
}
|
||||
|
||||
SendMessageRaw(aMsg);
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::SendMessageRaw(const Message& aMsg)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
mLastMessageTime = TimeStamp::Now();
|
||||
mChannel->SendMessage(aMsg);
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::Recover(bool aPaused, Message* aPausedMessage, size_t aLastCheckpoint,
|
||||
Message** aMessages, size_t aNumMessages)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
|
||||
SendMessageRaw(SetIsActiveMessage(false));
|
||||
|
||||
size_t mostRecentCheckpoint = MostRecentCheckpoint();
|
||||
bool pausedAtCheckpoint = IsPausedAtCheckpoint();
|
||||
|
||||
// Clear out all messages that have been sent to this process.
|
||||
for (Message* msg : mMessages) {
|
||||
if (msg->mType == MessageType::SetBreakpoint) {
|
||||
SetBreakpointMessage* nmsg = static_cast<SetBreakpointMessage*>(msg);
|
||||
SendMessageRaw(SetBreakpointMessage(nmsg->mId, JS::replay::ExecutionPosition()));
|
||||
}
|
||||
free(msg);
|
||||
}
|
||||
mMessages.clear();
|
||||
|
||||
mPaused = aPaused;
|
||||
mPausedMessage = aPausedMessage;
|
||||
mLastCheckpoint = aLastCheckpoint;
|
||||
for (size_t i = 0; i < aNumMessages; i++) {
|
||||
mMessages.append(aMessages[i]->Clone());
|
||||
}
|
||||
|
||||
mNumRecoveredMessages = 0;
|
||||
|
||||
if (mostRecentCheckpoint < mLastCheckpoint) {
|
||||
mRecoveryStage = RecoveryStage::ReachingCheckpoint;
|
||||
SendMessageRaw(ResumeMessage(/* aForward = */ true));
|
||||
} else if (mostRecentCheckpoint > mLastCheckpoint || !pausedAtCheckpoint) {
|
||||
mRecoveryStage = RecoveryStage::ReachingCheckpoint;
|
||||
// Rewind to the last saved checkpoint at or prior to the target.
|
||||
size_t targetCheckpoint = CheckpointId::Invalid;
|
||||
for (size_t saved : mShouldSaveCheckpoints) {
|
||||
if (saved <= mLastCheckpoint && saved > targetCheckpoint) {
|
||||
targetCheckpoint = saved;
|
||||
}
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(targetCheckpoint != CheckpointId::Invalid);
|
||||
SendMessageRaw(RestoreCheckpointMessage(targetCheckpoint));
|
||||
} else {
|
||||
mRecoveryStage = RecoveryStage::PlayingMessages;
|
||||
SendNextRecoveryMessage();
|
||||
}
|
||||
|
||||
WaitUntil([=]() { return !IsRecovering(); });
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::Recover(ChildProcessInfo* aTargetProcess)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(aTargetProcess->IsPaused());
|
||||
Recover(true, aTargetProcess->mPausedMessage->Clone(),
|
||||
aTargetProcess->mLastCheckpoint,
|
||||
aTargetProcess->mMessages.begin(), aTargetProcess->mMessages.length());
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::RecoverToCheckpoint(size_t aCheckpoint)
|
||||
{
|
||||
HitCheckpointMessage pausedMessage(aCheckpoint,
|
||||
/* aRecordingEndpoint = */ false,
|
||||
/* aDuration = */ 0);
|
||||
Recover(true, pausedMessage.Clone(), aCheckpoint, nullptr, 0);
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::OnIncomingRecoveryMessage(const Message& aMsg)
|
||||
{
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint: {
|
||||
MOZ_RELEASE_ASSERT(mRecoveryStage == RecoveryStage::ReachingCheckpoint);
|
||||
const HitCheckpointMessage& nmsg = static_cast<const HitCheckpointMessage&>(aMsg);
|
||||
if (nmsg.mCheckpointId < mLastCheckpoint) {
|
||||
SendMessageRaw(ResumeMessage(/* aForward = */ true));
|
||||
} else {
|
||||
MOZ_RELEASE_ASSERT(nmsg.mCheckpointId == mLastCheckpoint);
|
||||
mRecoveryStage = RecoveryStage::PlayingMessages;
|
||||
SendNextRecoveryMessage();
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageType::HitBreakpoint:
|
||||
case MessageType::DebuggerResponse:
|
||||
SendNextRecoveryMessage();
|
||||
break;
|
||||
default:
|
||||
MOZ_CRASH("Unexpected message during recovery");
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::SendNextRecoveryMessage()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mRecoveryStage == RecoveryStage::PlayingMessages);
|
||||
|
||||
// Keep sending messages to the child as long as it stays paused.
|
||||
Message* msg;
|
||||
do {
|
||||
// Check if we have recovered to the desired paused state.
|
||||
if (mNumRecoveredMessages == mMessages.length()) {
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
mRecoveryStage = RecoveryStage::None;
|
||||
return;
|
||||
}
|
||||
msg = mMessages[mNumRecoveredMessages++];
|
||||
SendMessageRaw(*msg);
|
||||
|
||||
// If we just sent a SetBreakpoint message then the child process is still
|
||||
// paused, so keep sending more messages.
|
||||
} while (msg->mType == MessageType::SetBreakpoint);
|
||||
|
||||
// If we have sent all messages and are in an unpaused state, we are done
|
||||
// recovering.
|
||||
if (mNumRecoveredMessages == mMessages.length() && !IsPaused()) {
|
||||
mRecoveryStage = RecoveryStage::None;
|
||||
}
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Subprocess Management
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
void
|
||||
ChildProcessInfo::LaunchSubprocess()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!mProcess);
|
||||
|
||||
// Create a new channel every time we launch a new subprocess, without
|
||||
// deleting or tearing down the old one's state. This is pretty lame and it
|
||||
// would be nice if we could do something better here, especially because
|
||||
// with restarts we could create any number of channels over time.
|
||||
size_t channelId = gNumChannels++;
|
||||
mChannel = new Channel(channelId, [=](Message* aMsg) {
|
||||
ReceiveChildMessageOnMainThread(channelId, aMsg);
|
||||
});
|
||||
|
||||
mProcess = new ipc::GeckoChildProcessHost(GeckoProcessType_Content);
|
||||
|
||||
std::vector<std::string> extraArgs;
|
||||
char buf[20];
|
||||
|
||||
SprintfLiteral(buf, "%d", (int) GetId());
|
||||
extraArgs.push_back(gChannelIDOption);
|
||||
extraArgs.push_back(buf);
|
||||
|
||||
SprintfLiteral(buf, "%d", (int) IsRecording() ? ProcessKind::Recording : ProcessKind::Replaying);
|
||||
extraArgs.push_back(gProcessKindOption);
|
||||
extraArgs.push_back(buf);
|
||||
|
||||
extraArgs.push_back(gRecordingFileOption);
|
||||
extraArgs.push_back(gRecordingFilename);
|
||||
|
||||
if (!mProcess->LaunchAndWaitForProcessHandle(extraArgs)) {
|
||||
MOZ_CRASH("ChildProcessInfo::LaunchSubprocess");
|
||||
}
|
||||
|
||||
mLastMessageTime = TimeStamp::Now();
|
||||
|
||||
// The child should send us a HitCheckpoint with an invalid ID to pause.
|
||||
WaitUntilPaused();
|
||||
|
||||
// Send the child a handle to the graphics shmem via mach IPC.
|
||||
char portName[128];
|
||||
SprintfLiteral(portName, "WebReplay.%d.%d", getpid(), (int) channelId);
|
||||
MachPortSender sender(portName);
|
||||
MachSendMessage message(GraphicsMessageId);
|
||||
message.AddDescriptor(MachMsgPortDescriptor(gGraphicsPort, MACH_MSG_TYPE_COPY_SEND));
|
||||
kern_return_t kr = sender.SendMessage(message, 1000);
|
||||
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
||||
|
||||
MOZ_RELEASE_ASSERT(gIntroductionMessage);
|
||||
SendMessage(*gIntroductionMessage);
|
||||
}
|
||||
|
||||
// Whether the main thread is waiting on a child process to be terminated.
|
||||
static bool gWaitingOnTerminateChildProcess;
|
||||
|
||||
void
|
||||
ChildProcess::TerminateSubprocess()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
MOZ_RELEASE_ASSERT(!gWaitingOnTerminateChildProcess);
|
||||
gWaitingOnTerminateChildProcess = true;
|
||||
|
||||
// Child processes need to be destroyed on the correct thread.
|
||||
XRE_GetIOMessageLoop()->PostTask(NewRunnableFunction("TerminateSubprocess", Terminate, mProcess));
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (gWaitingOnTerminateChildProcess) {
|
||||
gMonitor->Wait();
|
||||
}
|
||||
|
||||
mProcess = nullptr;
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
ChildProcess::Terminate(ipc::GeckoChildProcessHost* aProcess)
|
||||
{
|
||||
// The destructor for GeckoChildProcessHost will teardown the child process.
|
||||
delete aProcess;
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
MOZ_RELEASE_ASSERT(gWaitingOnTerminateChildProcess);
|
||||
gWaitingOnTerminateChildProcess = false;
|
||||
gMonitor->Notify();
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Recovering Crashed / Hung Children
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// The number of times we will restart a process before giving up.
|
||||
static const size_t MaxRestarts = 5;
|
||||
|
||||
bool
|
||||
ChildProcessInfo::CanRestart()
|
||||
{
|
||||
return gRestartEnabled
|
||||
&& !IsRecording()
|
||||
&& !IsPaused()
|
||||
&& !IsRecovering()
|
||||
&& mNumRestarts < MaxRestarts;
|
||||
}
|
||||
|
||||
void
|
||||
ChildProcessInfo::AttemptRestart(const char* aWhy)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
PrintSpew("Warning: Child process died [%d]: %s\n", (int) GetId(), aWhy);
|
||||
|
||||
if (!CanRestart()) {
|
||||
nsAutoCString why(aWhy);
|
||||
dom::ContentChild::GetSingleton()->SendRecordReplayFatalError(why);
|
||||
Thread::WaitForeverNoIdle();
|
||||
}
|
||||
|
||||
mNumRestarts++;
|
||||
|
||||
TerminateSubprocess();
|
||||
|
||||
bool newPaused = mPaused;
|
||||
Message* newPausedMessage = mPausedMessage;
|
||||
|
||||
mPaused = false;
|
||||
mPausedMessage = nullptr;
|
||||
|
||||
size_t newLastCheckpoint = mLastCheckpoint;
|
||||
mLastCheckpoint = CheckpointId::Invalid;
|
||||
|
||||
InfallibleVector<Message*> newMessages;
|
||||
newMessages.append(mMessages.begin(), mMessages.length());
|
||||
mMessages.clear();
|
||||
|
||||
InfallibleVector<size_t> newShouldSaveCheckpoints;
|
||||
newShouldSaveCheckpoints.append(mShouldSaveCheckpoints.begin(), mShouldSaveCheckpoints.length());
|
||||
mShouldSaveCheckpoints.clear();
|
||||
|
||||
LaunchSubprocess();
|
||||
|
||||
// Disallow child processes from intentionally crashing after restarting.
|
||||
SendMessage(SetAllowIntentionalCrashesMessage(false));
|
||||
|
||||
for (size_t checkpoint : newShouldSaveCheckpoints) {
|
||||
SendMessage(SetSaveCheckpointMessage(checkpoint, true));
|
||||
}
|
||||
|
||||
Recover(newPaused, newPausedMessage, newLastCheckpoint,
|
||||
newMessages.begin(), newMessages.length());
|
||||
}
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Handling Channel Messages
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// When messages are received from child processes, we want their handler to
|
||||
// execute on the main thread. The main thread might be blocked in WaitUntil,
|
||||
// so runnables associated with child processes have special handling.
|
||||
|
||||
// All messages received on a channel thread which the main thread has not
|
||||
// processed yet. This is protected by gMonitor.
|
||||
struct PendingMessage
|
||||
{
|
||||
ChildProcessInfo* mProcess;
|
||||
size_t mChannelId;
|
||||
Message* mMsg;
|
||||
};
|
||||
static StaticInfallibleVector<PendingMessage> gPendingMessages;
|
||||
|
||||
// Whether there is a pending task on the main thread's message loop to handle
|
||||
// all pending messages.
|
||||
static bool gHasPendingMessageRunnable;
|
||||
|
||||
// Process a pending message from aProcess (or any process if aProcess is null)
|
||||
// and return whether such a message was found. This must be called on the main
|
||||
// thread with gMonitor held.
|
||||
/* static */ bool
|
||||
ChildProcessInfo::MaybeProcessPendingMessage(ChildProcessInfo* aProcess)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
for (size_t i = 0; i < gPendingMessages.length(); i++) {
|
||||
if (!aProcess || gPendingMessages[i].mProcess == aProcess) {
|
||||
PendingMessage copy = gPendingMessages[i];
|
||||
gPendingMessages.erase(&gPendingMessages[i]);
|
||||
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
copy.mProcess->OnIncomingMessage(copy.mChannelId, *copy.mMsg);
|
||||
free(copy.mMsg);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// How many seconds to wait without hearing from an unpaused child before
|
||||
// considering that child to be hung.
|
||||
static const size_t HangSeconds = 5;
|
||||
|
||||
void
|
||||
ChildProcessInfo::WaitUntil(const std::function<bool()>& aCallback)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
while (!aCallback()) {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
if (!MaybeProcessPendingMessage(this)) {
|
||||
if (gChildrenAreDebugging) {
|
||||
// Don't watch for hangs when children are being debugged.
|
||||
gMonitor->Wait();
|
||||
} else {
|
||||
TimeStamp deadline = mLastMessageTime + TimeDuration::FromSeconds(HangSeconds);
|
||||
if (TimeStamp::Now() >= deadline) {
|
||||
MonitorAutoUnlock unlock(*gMonitor);
|
||||
AttemptRestart("Child process non-responsive");
|
||||
}
|
||||
gMonitor->WaitUntil(deadline);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Runnable created on the main thread to handle any tasks sent by the replay
|
||||
// message loop thread which were not handled while the main thread was blocked.
|
||||
/* static */ void
|
||||
ChildProcessInfo::MaybeProcessPendingMessageRunnable()
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
MOZ_RELEASE_ASSERT(gHasPendingMessageRunnable);
|
||||
gHasPendingMessageRunnable = false;
|
||||
while (MaybeProcessPendingMessage(nullptr)) {}
|
||||
}
|
||||
|
||||
// Execute a task that processes a message received from the child. This is
|
||||
// called on a channel thread, and the function executes asynchronously on
|
||||
// the main thread.
|
||||
void
|
||||
ChildProcessInfo::ReceiveChildMessageOnMainThread(size_t aChannelId, Message* aMsg)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(!NS_IsMainThread());
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
|
||||
PendingMessage pending;
|
||||
pending.mProcess = this;
|
||||
pending.mChannelId = aChannelId;
|
||||
pending.mMsg = aMsg;
|
||||
gPendingMessages.append(pending);
|
||||
|
||||
// Notify the main thread, if it is waiting in WaitUntil.
|
||||
gMonitor->NotifyAll();
|
||||
|
||||
// Make sure there is a task on the main thread's message loop that can
|
||||
// process this task if necessary.
|
||||
if (!gHasPendingMessageRunnable) {
|
||||
gHasPendingMessageRunnable = true;
|
||||
MainThreadMessageLoop()->PostTask(NewRunnableFunction("MaybeProcessPendingMessageRunnable",
|
||||
MaybeProcessPendingMessageRunnable));
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace parent
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,392 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file has the logic which the middleman process uses to forward IPDL
|
||||
// messages from the recording process to the UI process, and from the UI
|
||||
// process to either itself, the recording process, or both.
|
||||
|
||||
#include "ParentInternal.h"
|
||||
|
||||
#include "mozilla/dom/PBrowserChild.h"
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/layers/CompositorBridgeChild.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
// Known associations between managee and manager routing IDs.
|
||||
static StaticInfallibleVector<std::pair<int32_t, int32_t>> gProtocolManagers;
|
||||
|
||||
// The routing IDs of actors in the parent process that have been destroyed.
|
||||
static StaticInfallibleVector<int32_t> gDeadRoutingIds;
|
||||
|
||||
static void
|
||||
NoteProtocolManager(int32_t aManagee, int32_t aManager)
|
||||
{
|
||||
gProtocolManagers.emplaceBack(aManagee, aManager);
|
||||
for (auto id : gDeadRoutingIds) {
|
||||
if (id == aManager) {
|
||||
gDeadRoutingIds.emplaceBack(aManagee);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void
|
||||
DestroyRoutingId(int32_t aId)
|
||||
{
|
||||
gDeadRoutingIds.emplaceBack(aId);
|
||||
for (auto manager : gProtocolManagers) {
|
||||
if (manager.second == aId) {
|
||||
DestroyRoutingId(manager.first);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Return whether a message from the child process to the UI process is being
|
||||
// sent to a target that is being destroyed, and should be suppressed.
|
||||
static bool
|
||||
MessageTargetIsDead(const IPC::Message& aMessage)
|
||||
{
|
||||
// After the parent process destroys a browser, we handle the destroy in
|
||||
// both the middleman and child processes. Both processes will respond to
|
||||
// the destroy by sending additional messages to the UI process indicating
|
||||
// the browser has been destroyed, but we need to ignore such messages from
|
||||
// the child process (if it is still recording) to avoid confusing the UI
|
||||
// process.
|
||||
for (int32_t id : gDeadRoutingIds) {
|
||||
if (id == aMessage.routing_id()) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool
|
||||
HandleMessageInMiddleman(ipc::Side aSide, const IPC::Message& aMessage)
|
||||
{
|
||||
IPC::Message::msgid_t type = aMessage.type();
|
||||
|
||||
// Ignore messages sent from the child to dead UI process targets.
|
||||
if (aSide == ipc::ParentSide) {
|
||||
// When the browser is destroyed in the UI process all its children will
|
||||
// also be destroyed. Figure out the routing IDs of children which we need
|
||||
// to recognize as dead once the browser is destroyed. This is not a
|
||||
// complete list of all the browser's children, but only includes ones
|
||||
// where crashes have been seen as a result.
|
||||
if (type == dom::PBrowser::Msg_PDocAccessibleConstructor__ID) {
|
||||
PickleIterator iter(aMessage);
|
||||
ipc::ActorHandle handle;
|
||||
|
||||
if (!IPC::ReadParam(&aMessage, &iter, &handle))
|
||||
MOZ_CRASH("IPC::ReadParam failed");
|
||||
|
||||
NoteProtocolManager(handle.mId, aMessage.routing_id());
|
||||
}
|
||||
|
||||
if (MessageTargetIsDead(aMessage)) {
|
||||
PrintSpew("Suppressing %s message to dead target\n", IPC::StringFromIPCMessageType(type));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle messages that should be sent to both the middleman and the
|
||||
// child process.
|
||||
if (// Initialization that must be performed in both processes.
|
||||
type == dom::PContent::Msg_PBrowserConstructor__ID ||
|
||||
type == dom::PContent::Msg_RegisterChrome__ID ||
|
||||
type == dom::PContent::Msg_SetXPCOMProcessAttributes__ID ||
|
||||
type == dom::PContent::Msg_SetProcessSandbox__ID ||
|
||||
// Graphics messages that affect both processes.
|
||||
type == dom::PBrowser::Msg_InitRendering__ID ||
|
||||
type == dom::PBrowser::Msg_SetDocShellIsActive__ID ||
|
||||
type == dom::PBrowser::Msg_PRenderFrameConstructor__ID ||
|
||||
type == dom::PBrowser::Msg_RenderLayers__ID ||
|
||||
// May be loading devtools code that runs in the middleman process.
|
||||
type == dom::PBrowser::Msg_LoadRemoteScript__ID ||
|
||||
// May be sending a message for receipt by devtools code.
|
||||
type == dom::PBrowser::Msg_AsyncMessage__ID ||
|
||||
// Teardown that must be performed in both processes.
|
||||
type == dom::PBrowser::Msg_Destroy__ID) {
|
||||
ipc::IProtocol::Result r =
|
||||
dom::ContentChild::GetSingleton()->PContentChild::OnMessageReceived(aMessage);
|
||||
MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
|
||||
if (type == dom::PContent::Msg_SetXPCOMProcessAttributes__ID) {
|
||||
// Preferences are initialized via the SetXPCOMProcessAttributes message.
|
||||
PreferencesLoaded();
|
||||
}
|
||||
if (type == dom::PBrowser::Msg_Destroy__ID) {
|
||||
DestroyRoutingId(aMessage.routing_id());
|
||||
}
|
||||
if (type == dom::PBrowser::Msg_RenderLayers__ID) {
|
||||
// Graphics are being loaded or unloaded for a tab, so update what we are
|
||||
// showing to the UI process according to the last paint performed.
|
||||
UpdateGraphicsInUIProcess(nullptr);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
// Handle messages that should only be sent to the middleman.
|
||||
if (// Initialization that should only happen in the middleman.
|
||||
type == dom::PContent::Msg_InitRendering__ID ||
|
||||
// Record/replay specific messages.
|
||||
type == dom::PContent::Msg_SaveRecording__ID ||
|
||||
// Teardown that should only happen in the middleman.
|
||||
type == dom::PContent::Msg_Shutdown__ID) {
|
||||
ipc::IProtocol::Result r = dom::ContentChild::GetSingleton()->PContentChild::OnMessageReceived(aMessage);
|
||||
MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
|
||||
return true;
|
||||
}
|
||||
|
||||
// The content process has its own compositor, so compositor messages from
|
||||
// the UI process should only be handled in the middleman.
|
||||
if (type >= layers::PCompositorBridge::PCompositorBridgeStart &&
|
||||
type <= layers::PCompositorBridge::PCompositorBridgeEnd) {
|
||||
layers::CompositorBridgeChild* compositorChild = layers::CompositorBridgeChild::Get();
|
||||
ipc::IProtocol::Result r = compositorChild->OnMessageReceived(aMessage);
|
||||
MOZ_RELEASE_ASSERT(r == ipc::IProtocol::MsgProcessed);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static bool gMainThreadIsWaitingForIPDLReply = false;
|
||||
|
||||
bool
|
||||
MainThreadIsWaitingForIPDLReply()
|
||||
{
|
||||
return gMainThreadIsWaitingForIPDLReply;
|
||||
}
|
||||
|
||||
// Helper for places where the main thread will block while waiting on a
|
||||
// synchronous IPDL reply from a child process. Incoming messages from the
|
||||
// child must be handled immediately.
|
||||
struct MOZ_RAII AutoMarkMainThreadWaitingForIPDLReply
|
||||
{
|
||||
AutoMarkMainThreadWaitingForIPDLReply() {
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
MOZ_RELEASE_ASSERT(!gMainThreadIsWaitingForIPDLReply);
|
||||
gMainThreadIsWaitingForIPDLReply = true;
|
||||
}
|
||||
|
||||
~AutoMarkMainThreadWaitingForIPDLReply() {
|
||||
gMainThreadIsWaitingForIPDLReply = false;
|
||||
}
|
||||
};
|
||||
|
||||
class MiddlemanProtocol : public ipc::IToplevelProtocol
|
||||
{
|
||||
public:
|
||||
ipc::Side mSide;
|
||||
MiddlemanProtocol* mOpposite;
|
||||
MessageLoop* mOppositeMessageLoop;
|
||||
|
||||
explicit MiddlemanProtocol(ipc::Side aSide)
|
||||
: ipc::IToplevelProtocol("MiddlemanProtocol", PContentMsgStart, aSide)
|
||||
, mSide(aSide)
|
||||
, mOpposite(nullptr)
|
||||
, mOppositeMessageLoop(nullptr)
|
||||
{}
|
||||
|
||||
virtual void RemoveManagee(int32_t, IProtocol*) override {
|
||||
MOZ_CRASH("MiddlemanProtocol::RemoveManagee");
|
||||
}
|
||||
|
||||
static void ForwardMessageAsync(MiddlemanProtocol* aProtocol, Message* aMessage) {
|
||||
if (ActiveChildIsRecording()) {
|
||||
PrintSpew("ForwardAsyncMsg %s %s %d\n",
|
||||
(aProtocol->mSide == ipc::ChildSide) ? "Child" : "Parent",
|
||||
IPC::StringFromIPCMessageType(aMessage->type()),
|
||||
(int) aMessage->routing_id());
|
||||
if (!aProtocol->GetIPCChannel()->Send(aMessage)) {
|
||||
MOZ_CRASH("MiddlemanProtocol::ForwardMessageAsync");
|
||||
}
|
||||
} else {
|
||||
delete aMessage;
|
||||
}
|
||||
}
|
||||
|
||||
virtual Result OnMessageReceived(const Message& aMessage) override {
|
||||
// If we do not have a recording process then just see if the message can
|
||||
// be handled in the middleman.
|
||||
if (!mOppositeMessageLoop) {
|
||||
MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide);
|
||||
HandleMessageInMiddleman(mSide, aMessage);
|
||||
return MsgProcessed;
|
||||
}
|
||||
|
||||
// Copy the message first, since HandleMessageInMiddleman may destructively
|
||||
// modify it through OnMessageReceived calls.
|
||||
Message* nMessage = new Message();
|
||||
nMessage->CopyFrom(aMessage);
|
||||
|
||||
if (HandleMessageInMiddleman(mSide, aMessage)) {
|
||||
delete nMessage;
|
||||
return MsgProcessed;
|
||||
}
|
||||
|
||||
mOppositeMessageLoop->PostTask(NewRunnableFunction("ForwardMessageAsync", ForwardMessageAsync,
|
||||
mOpposite, nMessage));
|
||||
return MsgProcessed;
|
||||
}
|
||||
|
||||
static void ForwardMessageSync(MiddlemanProtocol* aProtocol, Message* aMessage, Message** aReply) {
|
||||
PrintSpew("ForwardSyncMsg %s\n", IPC::StringFromIPCMessageType(aMessage->type()));
|
||||
|
||||
MOZ_RELEASE_ASSERT(!*aReply);
|
||||
Message* nReply = new Message();
|
||||
if (!aProtocol->GetIPCChannel()->Send(aMessage, nReply)) {
|
||||
MOZ_CRASH("MiddlemanProtocol::ForwardMessageSync");
|
||||
}
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
*aReply = nReply;
|
||||
gMonitor->Notify();
|
||||
}
|
||||
|
||||
virtual Result OnMessageReceived(const Message& aMessage, Message*& aReply) override {
|
||||
MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
|
||||
MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide || !MessageTargetIsDead(aMessage));
|
||||
|
||||
Message* nMessage = new Message();
|
||||
nMessage->CopyFrom(aMessage);
|
||||
mOppositeMessageLoop->PostTask(NewRunnableFunction("ForwardMessageSync", ForwardMessageSync,
|
||||
mOpposite, nMessage, &aReply));
|
||||
|
||||
if (mSide == ipc::ChildSide) {
|
||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
||||
ActiveRecordingChild()->WaitUntil([&]() { return !!aReply; });
|
||||
} else {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (!aReply) {
|
||||
gMonitor->Wait();
|
||||
}
|
||||
}
|
||||
|
||||
PrintSpew("SyncMsgDone\n");
|
||||
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_CRASH("MiddlemanProtocol::ForwardCallMessage");
|
||||
}
|
||||
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
*aReply = nReply;
|
||||
gMonitor->Notify();
|
||||
}
|
||||
|
||||
virtual Result OnCallReceived(const Message& aMessage, Message*& aReply) override {
|
||||
MOZ_RELEASE_ASSERT(mOppositeMessageLoop);
|
||||
MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide || !MessageTargetIsDead(aMessage));
|
||||
|
||||
Message* nMessage = new Message();
|
||||
nMessage->CopyFrom(aMessage);
|
||||
mOppositeMessageLoop->PostTask(NewRunnableFunction("ForwardCallMessage", ForwardCallMessage,
|
||||
mOpposite, nMessage, &aReply));
|
||||
|
||||
if (mSide == ipc::ChildSide) {
|
||||
AutoMarkMainThreadWaitingForIPDLReply blocked;
|
||||
ActiveRecordingChild()->WaitUntil([&]() { return !!aReply; });
|
||||
} else {
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (!aReply) {
|
||||
gMonitor->Wait();
|
||||
}
|
||||
}
|
||||
|
||||
PrintSpew("SyncCallDone\n");
|
||||
return MsgProcessed;
|
||||
}
|
||||
|
||||
virtual int32_t GetProtocolTypeId() override {
|
||||
MOZ_CRASH("MiddlemanProtocol::GetProtocolTypeId");
|
||||
}
|
||||
|
||||
virtual void OnChannelClose() override {
|
||||
MOZ_RELEASE_ASSERT(mSide == ipc::ChildSide);
|
||||
MainThreadMessageLoop()->PostTask(NewRunnableFunction("Shutdown", Shutdown));
|
||||
}
|
||||
|
||||
virtual void OnChannelError() override {
|
||||
MOZ_CRASH("MiddlemanProtocol::OnChannelError");
|
||||
}
|
||||
};
|
||||
|
||||
static MiddlemanProtocol* gChildProtocol;
|
||||
static MiddlemanProtocol* gParentProtocol;
|
||||
|
||||
ipc::MessageChannel*
|
||||
ChannelToUIProcess()
|
||||
{
|
||||
return gChildProtocol->GetIPCChannel();
|
||||
}
|
||||
|
||||
// Message loop for forwarding messages between the parent process and a
|
||||
// recording process.
|
||||
static MessageLoop* gForwardingMessageLoop;
|
||||
|
||||
static bool gParentProtocolOpened = false;
|
||||
|
||||
// Main routine for the forwarding message loop thread.
|
||||
static void
|
||||
ForwardingMessageLoopMain(void*)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(ActiveChildIsRecording());
|
||||
|
||||
MessageLoop messageLoop;
|
||||
gForwardingMessageLoop = &messageLoop;
|
||||
|
||||
gChildProtocol->mOppositeMessageLoop = gForwardingMessageLoop;
|
||||
|
||||
gParentProtocol->Open(gRecordingProcess->GetChannel(),
|
||||
base::GetProcId(gRecordingProcess->GetChildProcessHandle()));
|
||||
|
||||
// Notify the main thread that we have finished initialization.
|
||||
{
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
gParentProtocolOpened = true;
|
||||
gMonitor->Notify();
|
||||
}
|
||||
|
||||
messageLoop.Run();
|
||||
}
|
||||
|
||||
void
|
||||
InitializeForwarding()
|
||||
{
|
||||
gChildProtocol = new MiddlemanProtocol(ipc::ChildSide);
|
||||
|
||||
if (gProcessKind == ProcessKind::MiddlemanRecording) {
|
||||
gParentProtocol = new MiddlemanProtocol(ipc::ParentSide);
|
||||
gParentProtocol->mOpposite = gChildProtocol;
|
||||
gChildProtocol->mOpposite = gParentProtocol;
|
||||
|
||||
gParentProtocol->mOppositeMessageLoop = MainThreadMessageLoop();
|
||||
|
||||
if (!PR_CreateThread(PR_USER_THREAD, ForwardingMessageLoopMain, nullptr,
|
||||
PR_PRIORITY_NORMAL, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD, 0)) {
|
||||
MOZ_CRASH("parent::Initialize");
|
||||
}
|
||||
|
||||
// Wait for the forwarding message loop thread to finish initialization.
|
||||
MonitorAutoLock lock(*gMonitor);
|
||||
while (!gParentProtocolOpened) {
|
||||
gMonitor->Wait();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace parent
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
|
@ -0,0 +1,51 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
// This file has the logic which the middleman process uses to send messages to
|
||||
// the UI process with painting data from the child process.
|
||||
|
||||
#include "ParentInternal.h"
|
||||
|
||||
#include "chrome/common/mach_ipc_mac.h"
|
||||
#include "mozilla/dom/TabChild.h"
|
||||
#include "mozilla/layers/CompositorBridgeChild.h"
|
||||
#include "mozilla/layers/ImageDataSerializer.h"
|
||||
#include "mozilla/layers/LayerTransactionChild.h"
|
||||
#include "mozilla/layers/PTextureChild.h"
|
||||
|
||||
#include <mach/mach_vm.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
void* gGraphicsMemory;
|
||||
mach_port_t gGraphicsPort;
|
||||
|
||||
void
|
||||
InitializeGraphicsMemory()
|
||||
{
|
||||
mach_vm_address_t address;
|
||||
kern_return_t kr = mach_vm_allocate(mach_task_self(), &address,
|
||||
GraphicsMemorySize, VM_FLAGS_ANYWHERE);
|
||||
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
||||
|
||||
memory_object_size_t memoryObjectSize = GraphicsMemorySize;
|
||||
kr = mach_make_memory_entry_64(mach_task_self(),
|
||||
&memoryObjectSize,
|
||||
address,
|
||||
VM_PROT_DEFAULT,
|
||||
&gGraphicsPort,
|
||||
MACH_PORT_NULL);
|
||||
MOZ_RELEASE_ASSERT(kr == KERN_SUCCESS);
|
||||
MOZ_RELEASE_ASSERT(memoryObjectSize == GraphicsMemorySize);
|
||||
|
||||
gGraphicsMemory = (void*) address;
|
||||
}
|
||||
|
||||
} // namespace parent
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
Разница между файлами не показана из-за своего большого размера
Загрузить разницу
|
@ -0,0 +1,56 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_recordreplay_ParentIPC_h
|
||||
#define mozilla_recordreplay_ParentIPC_h
|
||||
|
||||
#include "mozilla/dom/ContentChild.h"
|
||||
#include "mozilla/ipc/MessageChannel.h"
|
||||
#include "mozilla/ipc/ProcessChild.h"
|
||||
#include "mozilla/ipc/ProtocolUtils.h"
|
||||
#include "mozilla/ipc/ScopedXREEmbed.h"
|
||||
#include "mozilla/ipc/Shmem.h"
|
||||
#include "js/ReplayHooks.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
// The middleman process is a content process that manages communication with
|
||||
// one or more child recording or replaying processes. It performs IPC with the
|
||||
// UI process in the normal fashion for a content process, using the normal
|
||||
// IPDL protocols. Communication with a recording or replaying process is done
|
||||
// via a special IPC channel (see Channel.h), and communication with a
|
||||
// recording process can additionally be done via IPDL messages, usually by
|
||||
// forwarding them from the UI process.
|
||||
|
||||
// UI process API
|
||||
|
||||
// Initialize state in a UI process.
|
||||
void InitializeUIProcess(int aArgc, char** aArgv);
|
||||
|
||||
// Get any directory where content process recordings should be saved.
|
||||
const char* SaveAllRecordingsDirectory();
|
||||
|
||||
// Middleman process API
|
||||
|
||||
// Save the recording up to the current point in execution.
|
||||
void SaveRecording(const nsCString& aFilename);
|
||||
|
||||
// Get the message channel used to communicate with the UI process.
|
||||
ipc::MessageChannel* ChannelToUIProcess();
|
||||
|
||||
// Initialize state in a middleman process.
|
||||
void InitializeMiddleman(int aArgc, char* aArgv[], base::ProcessId aParentPid);
|
||||
|
||||
// Note the contents of the prefs shmem for use by the child process.
|
||||
void NotePrefsShmemContents(char* aPrefs, size_t aPrefsLen);
|
||||
|
||||
} // namespace parent
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_recordreplay_ParentIPC_h
|
|
@ -0,0 +1,329 @@
|
|||
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
|
||||
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
|
||||
/* This Source Code Form is subject to the terms of the Mozilla Public
|
||||
* License, v. 2.0. If a copy of the MPL was not distributed with this file,
|
||||
* You can obtain one at http://mozilla.org/MPL/2.0/. */
|
||||
|
||||
#ifndef mozilla_recordreplay_ParentInternal_h
|
||||
#define mozilla_recordreplay_ParentInternal_h
|
||||
|
||||
#include "ParentIPC.h"
|
||||
|
||||
#include "Channel.h"
|
||||
#include "mozilla/ipc/GeckoChildProcessHost.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
namespace parent {
|
||||
|
||||
// This file has internal declarations for interaction between different
|
||||
// components of middleman logic.
|
||||
|
||||
class ChildProcessInfo;
|
||||
|
||||
// Get the message loop for the main thread.
|
||||
MessageLoop* MainThreadMessageLoop();
|
||||
|
||||
// Called after prefs are available to this process.
|
||||
void PreferencesLoaded();
|
||||
|
||||
// Return whether replaying processes are allowed to save checkpoints and
|
||||
// rewind. Can only be called after PreferencesLoaded().
|
||||
bool CanRewind();
|
||||
|
||||
// Whether the child currently being interacted with is recording.
|
||||
bool ActiveChildIsRecording();
|
||||
|
||||
// Get the active recording child process.
|
||||
ChildProcessInfo* ActiveRecordingChild();
|
||||
|
||||
// Return whether the middleman's main thread is blocked waiting on a
|
||||
// synchronous IPDL reply from the recording child.
|
||||
bool MainThreadIsWaitingForIPDLReply();
|
||||
|
||||
// Initialize state which handles incoming IPDL messages from the UI and
|
||||
// recording child processes.
|
||||
void InitializeForwarding();
|
||||
|
||||
// Terminate all children and kill this process.
|
||||
void Shutdown();
|
||||
|
||||
// Monitor used for synchronizing between the main and channel or message loop threads.
|
||||
static Monitor* gMonitor;
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Graphics
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
extern mach_port_t gGraphicsPort;
|
||||
extern void* gGraphicsMemory;
|
||||
|
||||
void InitializeGraphicsMemory();
|
||||
|
||||
// Update the graphics painted in the UI process, per painting data received
|
||||
// from a child process, or null for the last paint performed.
|
||||
void UpdateGraphicsInUIProcess(const PaintMessage* aMsg);
|
||||
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
// Child Processes
|
||||
///////////////////////////////////////////////////////////////////////////////
|
||||
|
||||
// Information about the role which a child process is fulfilling, and governs
|
||||
// how the process responds to incoming messages.
|
||||
class ChildRole
|
||||
{
|
||||
public:
|
||||
// See ParentIPC.cpp for the meaning of these role types.
|
||||
#define ForEachRoleType(Macro) \
|
||||
Macro(Active) \
|
||||
Macro(Standby) \
|
||||
Macro(Inert)
|
||||
|
||||
enum Type {
|
||||
#define DefineType(Name) Name,
|
||||
ForEachRoleType(DefineType)
|
||||
#undef DefineType
|
||||
};
|
||||
|
||||
static const char* TypeString(Type aType) {
|
||||
switch (aType) {
|
||||
#define GetTypeString(Name) case Name: return #Name;
|
||||
ForEachRoleType(GetTypeString)
|
||||
#undef GetTypeString
|
||||
default: MOZ_CRASH("Bad ChildRole type");
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
ChildProcessInfo* mProcess;
|
||||
Type mType;
|
||||
|
||||
explicit ChildRole(Type aType)
|
||||
: mProcess(nullptr), mType(aType)
|
||||
{}
|
||||
|
||||
public:
|
||||
void SetProcess(ChildProcessInfo* aProcess) {
|
||||
MOZ_RELEASE_ASSERT(!mProcess);
|
||||
mProcess = aProcess;
|
||||
}
|
||||
Type GetType() const { return mType; }
|
||||
|
||||
virtual ~ChildRole() {}
|
||||
|
||||
// The methods below are all called on the main thread.
|
||||
|
||||
virtual void Initialize() {}
|
||||
|
||||
// When the child is paused and potentially sitting idle, notify the role
|
||||
// that state affecting its behavior has changed and may want to become
|
||||
// active again.
|
||||
virtual void Poke() {}
|
||||
|
||||
virtual void OnIncomingMessage(const Message& aMsg) = 0;
|
||||
};
|
||||
|
||||
// Information about a recording or replaying child process.
|
||||
class ChildProcessInfo
|
||||
{
|
||||
// Handle for the process.
|
||||
ipc::GeckoChildProcessHost* mProcess;
|
||||
|
||||
// Channel for communicating with the process.
|
||||
Channel* mChannel;
|
||||
|
||||
// The last time we sent or received a message from this process.
|
||||
TimeStamp mLastMessageTime;
|
||||
|
||||
// Whether this process is recording.
|
||||
bool mRecording;
|
||||
|
||||
// The current recovery stage of this process.
|
||||
//
|
||||
// Recovery is used when we are shepherding a child to a particular state:
|
||||
// a particular execution position and sets of installed breakpoints and
|
||||
// saved checkpoints. Recovery is used when changing a child's role, and when
|
||||
// spawning a new process to replace a crashed child process.
|
||||
//
|
||||
// When recovering, the child process won't yet be in the exact place
|
||||
// reflected by the state below, but the main thread will wait until it has
|
||||
// finished reaching this state before it is able to send or receive
|
||||
// messages.
|
||||
enum class RecoveryStage {
|
||||
// No recovery is being performed, and the process can be interacted with.
|
||||
None,
|
||||
|
||||
// The process has not yet reached mLastCheckpoint.
|
||||
ReachingCheckpoint,
|
||||
|
||||
// The process has reached mLastCheckpoint, and additional messages are
|
||||
// being sent to change its intra-checkpoint execution position or install
|
||||
// breakpoints.
|
||||
PlayingMessages
|
||||
};
|
||||
RecoveryStage mRecoveryStage;
|
||||
|
||||
// Whether the process is currently paused.
|
||||
bool mPaused;
|
||||
|
||||
// If the process is paused, or if it is running while handling a message
|
||||
// that won't cause it to change its execution point, the last message which
|
||||
// caused it to pause.
|
||||
Message* mPausedMessage;
|
||||
|
||||
// The last checkpoint which the child process reached. The child is
|
||||
// somewhere between this and either the next or previous checkpoint,
|
||||
// depending on the messages that have been sent to it.
|
||||
size_t mLastCheckpoint;
|
||||
|
||||
// Messages sent to the process which will affect its behavior as it runs
|
||||
// forward or backward from mLastCheckpoint. This includes all messages that
|
||||
// will need to be sent to another process to recover it to the same state as
|
||||
// this process.
|
||||
InfallibleVector<Message*> mMessages;
|
||||
|
||||
// In the PlayingMessages recovery stage, how much of mMessages has been sent
|
||||
// to the process.
|
||||
size_t mNumRecoveredMessages;
|
||||
|
||||
// The number of times we have restarted this process.
|
||||
size_t mNumRestarts;
|
||||
|
||||
// Current role of this process.
|
||||
UniquePtr<ChildRole> mRole;
|
||||
|
||||
// Unsorted list of the checkpoints the process has been instructed to save.
|
||||
// Those at or before the most recent checkpoint will have been saved.
|
||||
InfallibleVector<size_t> mShouldSaveCheckpoints;
|
||||
|
||||
// Sorted major checkpoints for this process. See ParentIPC.cpp.
|
||||
InfallibleVector<size_t> mMajorCheckpoints;
|
||||
|
||||
// Whether we need this child to pause while the recording is updated.
|
||||
bool mPauseNeeded;
|
||||
|
||||
static void Terminate(ipc::GeckoChildProcessHost* aProcess);
|
||||
|
||||
void OnIncomingMessage(size_t aChannelId, const Message& aMsg);
|
||||
void OnIncomingRecoveryMessage(const Message& aMsg);
|
||||
void SendNextRecoveryMessage();
|
||||
void SendMessageRaw(const Message& aMsg);
|
||||
|
||||
static void MaybeProcessPendingMessageRunnable();
|
||||
void ReceiveChildMessageOnMainThread(size_t aChannelId, Message* aMsg);
|
||||
|
||||
// Get the position of this process relative to its last checkpoint.
|
||||
enum Disposition {
|
||||
AtLastCheckpoint,
|
||||
BeforeLastCheckpoint,
|
||||
AfterLastCheckpoint
|
||||
};
|
||||
Disposition GetDisposition();
|
||||
|
||||
void Recover(bool aPaused, Message* aPausedMessage, size_t aLastCheckpoint,
|
||||
Message** aMessages, size_t aNumMessages);
|
||||
|
||||
bool CanRestart();
|
||||
void AttemptRestart(const char* aWhy);
|
||||
void LaunchSubprocess();
|
||||
void TerminateSubprocess();
|
||||
|
||||
public:
|
||||
ChildProcessInfo(UniquePtr<ChildRole> aRole, bool aRecording);
|
||||
~ChildProcessInfo();
|
||||
|
||||
ChildRole* Role() { return mRole.get(); }
|
||||
ipc::GeckoChildProcessHost* Process() { return mProcess; }
|
||||
size_t GetId() { return mChannel->GetId(); }
|
||||
bool IsRecording() { return mRecording; }
|
||||
size_t LastCheckpoint() { return mLastCheckpoint; }
|
||||
bool IsRecovering() { return mRecoveryStage != RecoveryStage::None; }
|
||||
bool PauseNeeded() { return mPauseNeeded; }
|
||||
const InfallibleVector<size_t>& MajorCheckpoints() { return mMajorCheckpoints; }
|
||||
|
||||
bool IsPaused() { return mPaused; }
|
||||
bool IsPausedAtCheckpoint();
|
||||
bool IsPausedAtRecordingEndpoint();
|
||||
|
||||
// Return whether this process is paused at a breakpoint whose kind matches
|
||||
// the supplied filter.
|
||||
typedef std::function<bool(JS::replay::ExecutionPosition::Kind)> BreakpointFilter;
|
||||
bool IsPausedAtMatchingBreakpoint(const BreakpointFilter& aFilter);
|
||||
|
||||
// Get the checkpoint at or earlier to the process' position. This is either
|
||||
// the last reached checkpoint or the previous one.
|
||||
size_t MostRecentCheckpoint() {
|
||||
return (GetDisposition() == BeforeLastCheckpoint) ? mLastCheckpoint - 1 : mLastCheckpoint;
|
||||
}
|
||||
|
||||
// Get the checkpoint which needs to be saved in order for this process
|
||||
// (or another at the same place) to rewind.
|
||||
size_t RewindTargetCheckpoint() {
|
||||
switch (GetDisposition()) {
|
||||
case BeforeLastCheckpoint:
|
||||
case AtLastCheckpoint:
|
||||
// This will return CheckpointId::Invalid if we are the beginning of the
|
||||
// recording.
|
||||
return LastCheckpoint() - 1;
|
||||
case AfterLastCheckpoint:
|
||||
return LastCheckpoint();
|
||||
}
|
||||
}
|
||||
|
||||
bool ShouldSaveCheckpoint(size_t aId) {
|
||||
return VectorContains(mShouldSaveCheckpoints, aId);
|
||||
}
|
||||
|
||||
bool IsMajorCheckpoint(size_t aId) {
|
||||
return VectorContains(mMajorCheckpoints, aId);
|
||||
}
|
||||
|
||||
bool HasSavedCheckpoint(size_t aId) {
|
||||
return (aId <= MostRecentCheckpoint()) && ShouldSaveCheckpoint(aId);
|
||||
}
|
||||
|
||||
size_t MostRecentSavedCheckpoint() {
|
||||
size_t id = MostRecentCheckpoint();
|
||||
while (!ShouldSaveCheckpoint(id)) {
|
||||
id--;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
void SetPauseNeeded() {
|
||||
MOZ_RELEASE_ASSERT(!mPauseNeeded);
|
||||
mPauseNeeded = true;
|
||||
}
|
||||
|
||||
void ClearPauseNeeded() {
|
||||
MOZ_RELEASE_ASSERT(IsPaused());
|
||||
mPauseNeeded = false;
|
||||
mRole->Poke();
|
||||
}
|
||||
|
||||
void AddMajorCheckpoint(size_t aId);
|
||||
void SetRole(UniquePtr<ChildRole> aRole);
|
||||
void SendMessage(const Message& aMessage);
|
||||
|
||||
// Recover to the same state as another process.
|
||||
void Recover(ChildProcessInfo* aTargetProcess);
|
||||
|
||||
// Recover to be paused at a checkpoint with no breakpoints set.
|
||||
void RecoverToCheckpoint(size_t aCheckpoint);
|
||||
|
||||
// Handle incoming messages from this process (and no others) until the
|
||||
// callback succeeds.
|
||||
void WaitUntil(const std::function<bool()>& aCallback);
|
||||
|
||||
void WaitUntilPaused() { WaitUntil([=]() { return IsPaused(); }); }
|
||||
|
||||
static bool MaybeProcessPendingMessage(ChildProcessInfo* aProcess);
|
||||
|
||||
static void SetIntroductionMessage(IntroductionMessage* aMessage);
|
||||
};
|
||||
|
||||
} // namespace parent
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_recordreplay_ParentInternal_h
|
Загрузка…
Ссылка в новой задаче