Bug 1465287 Part 3 - Middleman side of record/replay IPC, r=mccr8,jld.

--HG--
extra : rebase_source : c0fec84bf1bb88d3973d6367c273be2f4b442b31
This commit is contained in:
Brian Hackett 2018-07-22 11:49:17 +00:00
Родитель 14a96e97ac
Коммит b8c260609d
6 изменённых файлов: 2622 добавлений и 0 удалений

Просмотреть файл

@ -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