gecko-dev/toolkit/recordreplay/ProcessRewind.cpp
Sylvestre Ledru 265e672179 Bug 1511181 - Reformat everything to the Google coding style r=ehsan a=clang-format
# ignore-this-changeset

--HG--
extra : amend_source : 4d301d3b0b8711c4692392aa76088ba7fd7d1022
2018-11-30 11:46:48 +01:00

319 lines
10 KiB
C++

/* -*- 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 "ProcessRewind.h"
#include "nsString.h"
#include "ipc/ChildInternal.h"
#include "ipc/ParentInternal.h"
#include "mozilla/dom/ScriptSettings.h"
#include "mozilla/StaticMutex.h"
#include "InfallibleVector.h"
#include "MemorySnapshot.h"
#include "Monitor.h"
#include "ProcessRecordReplay.h"
#include "ThreadSnapshot.h"
namespace mozilla {
namespace recordreplay {
// Information about the current rewinding state. The contents of this structure
// are in untracked memory.
struct RewindInfo {
// The most recent checkpoint which was encountered.
CheckpointId mLastCheckpoint;
// Whether this is the active child process. See the comment under
// 'Child Roles' in ParentIPC.cpp.
bool mIsActiveChild;
// Checkpoints which have been saved. This includes only entries from
// mShouldSaveCheckpoints, plus all temporary checkpoints.
InfallibleVector<SavedCheckpoint, 1024, AllocPolicy<MemoryKind::Generic>>
mSavedCheckpoints;
// Unsorted list of checkpoints which the middleman has instructed us to
// save. All those equal to or prior to mLastCheckpoint will have been saved.
InfallibleVector<size_t, 1024, AllocPolicy<MemoryKind::Generic>>
mShouldSaveCheckpoints;
};
static RewindInfo* gRewindInfo;
// Lock for managing pending main thread callbacks.
static Monitor* gMainThreadCallbackMonitor;
// Callbacks to execute on the main thread, in FIFO order. Protected by
// gMainThreadCallbackMonitor.
static StaticInfallibleVector<std::function<void()>> gMainThreadCallbacks;
void InitializeRewindState() {
MOZ_RELEASE_ASSERT(gRewindInfo == nullptr);
void* memory = AllocateMemory(sizeof(RewindInfo), MemoryKind::Generic);
gRewindInfo = new (memory) RewindInfo();
gMainThreadCallbackMonitor = new Monitor();
}
static bool CheckpointPrecedes(const CheckpointId& aFirst,
const CheckpointId& aSecond) {
return aFirst.mNormal < aSecond.mNormal ||
aFirst.mTemporary < aSecond.mTemporary;
}
void RestoreCheckpointAndResume(const CheckpointId& aCheckpoint) {
MOZ_RELEASE_ASSERT(IsReplaying());
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
MOZ_RELEASE_ASSERT(
aCheckpoint == gRewindInfo->mLastCheckpoint ||
CheckpointPrecedes(aCheckpoint, gRewindInfo->mLastCheckpoint));
// Make sure we don't lose pending main thread callbacks due to rewinding.
{
MonitorAutoLock lock(*gMainThreadCallbackMonitor);
MOZ_RELEASE_ASSERT(gMainThreadCallbacks.empty());
}
Thread::WaitForIdleThreads();
double start = CurrentTime();
{
// Rewind heap memory to the target checkpoint, which must have been saved.
AutoDisallowMemoryChanges disallow;
CheckpointId newCheckpoint =
gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
RestoreMemoryToLastSavedCheckpoint();
while (CheckpointPrecedes(aCheckpoint, newCheckpoint)) {
gRewindInfo->mSavedCheckpoints.back().ReleaseContents();
gRewindInfo->mSavedCheckpoints.popBack();
RestoreMemoryToLastSavedDiffCheckpoint();
newCheckpoint = gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
}
MOZ_RELEASE_ASSERT(newCheckpoint == aCheckpoint);
}
FixupFreeRegionsAfterRewind();
double end = CurrentTime();
PrintSpew("Restore #%d:%d -> #%d:%d %.2fs\n",
(int)gRewindInfo->mLastCheckpoint.mNormal,
(int)gRewindInfo->mLastCheckpoint.mTemporary,
(int)aCheckpoint.mNormal, (int)aCheckpoint.mTemporary,
(end - start) / 1000000.0);
// Finally, let threads restore themselves to their stacks at the checkpoint
// we are rewinding to.
RestoreAllThreads(gRewindInfo->mSavedCheckpoints.back());
Unreachable();
}
void SetSaveCheckpoint(size_t aCheckpoint, bool aSave) {
MOZ_RELEASE_ASSERT(aCheckpoint > gRewindInfo->mLastCheckpoint.mNormal);
VectorAddOrRemoveEntry(gRewindInfo->mShouldSaveCheckpoints, aCheckpoint,
aSave);
}
bool NewCheckpoint(bool aTemporary) {
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
MOZ_RELEASE_ASSERT(IsReplaying() || !aTemporary);
navigation::BeforeCheckpoint();
// Get the ID of the new checkpoint.
CheckpointId checkpoint =
gRewindInfo->mLastCheckpoint.NextCheckpoint(aTemporary);
// Save all checkpoints the middleman tells us to, and temporary checkpoints
// (which the middleman never knows about).
bool save = aTemporary || VectorContains(gRewindInfo->mShouldSaveCheckpoints,
checkpoint.mNormal);
bool reachedCheckpoint = true;
if (save) {
Thread::WaitForIdleThreads();
PrintSpew("Starting checkpoint...\n");
double start = CurrentTime();
// Record either the first or a subsequent diff memory snapshot.
if (gRewindInfo->mSavedCheckpoints.empty()) {
TakeFirstMemorySnapshot();
} else {
TakeDiffMemorySnapshot();
}
gRewindInfo->mSavedCheckpoints.emplaceBack(checkpoint);
double end = CurrentTime();
// Save all thread stacks for the checkpoint. If we rewind here from a
// later point of execution then this will return false.
if (SaveAllThreads(gRewindInfo->mSavedCheckpoints.back())) {
PrintSpew("Saved checkpoint #%d:%d %.2fs\n", (int)checkpoint.mNormal,
(int)checkpoint.mTemporary, (end - start) / 1000000.0);
} else {
PrintSpew("Restored checkpoint #%d:%d\n", (int)checkpoint.mNormal,
(int)checkpoint.mTemporary);
reachedCheckpoint = false;
// After restoring, make sure all threads have updated their stacks
// before letting any of them resume execution. Threads might have
// pointers into each others' stacks.
WaitForIdleThreadsToRestoreTheirStacks();
}
Thread::ResumeIdleThreads();
}
gRewindInfo->mLastCheckpoint = checkpoint;
navigation::AfterCheckpoint(checkpoint);
return reachedCheckpoint;
}
static bool gUnhandledDivergeAllowed;
void DivergeFromRecording() {
MOZ_RELEASE_ASSERT(IsReplaying());
Thread* thread = Thread::Current();
MOZ_RELEASE_ASSERT(thread->IsMainThread());
if (!thread->HasDivergedFromRecording()) {
// Reset middleman call state whenever we first diverge from the recording.
child::SendResetMiddlemanCalls();
// Make sure all non-main threads are idle before we begin diverging. This
// thread's new behavior can change values used by other threads and induce
// recording mismatches.
Thread::WaitForIdleThreads();
thread->DivergeFromRecording();
}
gUnhandledDivergeAllowed = true;
}
extern "C" {
MOZ_EXPORT bool RecordReplayInterface_InternalHasDivergedFromRecording() {
Thread* thread = Thread::Current();
return thread && thread->HasDivergedFromRecording();
}
} // extern "C"
void DisallowUnhandledDivergeFromRecording() {
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
gUnhandledDivergeAllowed = false;
}
void EnsureNotDivergedFromRecording() {
// If we have diverged from the recording and encounter an operation we can't
// handle, rewind to the last checkpoint.
AssertEventsAreNotPassedThrough();
if (HasDivergedFromRecording()) {
MOZ_RELEASE_ASSERT(gUnhandledDivergeAllowed);
// Crash instead of rewinding if a repaint is about to fail and is not
// allowed.
if (child::CurrentRepaintCannotFail()) {
MOZ_CRASH("Recording divergence while repainting");
}
PrintSpew("Unhandled recording divergence, restoring checkpoint...\n");
RestoreCheckpointAndResume(
gRewindInfo->mSavedCheckpoints.back().mCheckpoint);
Unreachable();
}
}
bool HasSavedCheckpoint() {
return gRewindInfo && !gRewindInfo->mSavedCheckpoints.empty();
}
CheckpointId GetLastSavedCheckpoint() {
MOZ_RELEASE_ASSERT(HasSavedCheckpoint());
return gRewindInfo->mSavedCheckpoints.back().mCheckpoint;
}
static bool gMainThreadShouldPause = false;
bool MainThreadShouldPause() { return gMainThreadShouldPause; }
void PauseMainThreadAndServiceCallbacks() {
MOZ_RELEASE_ASSERT(Thread::CurrentIsMainThread());
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
AssertEventsAreNotPassedThrough();
// Whether there is a PauseMainThreadAndServiceCallbacks frame on the stack.
static bool gMainThreadIsPaused = false;
if (gMainThreadIsPaused) {
return;
}
gMainThreadIsPaused = true;
MonitorAutoLock lock(*gMainThreadCallbackMonitor);
// Loop and invoke callbacks until one of them unpauses this thread.
while (gMainThreadShouldPause) {
if (!gMainThreadCallbacks.empty()) {
std::function<void()> callback = gMainThreadCallbacks[0];
gMainThreadCallbacks.erase(&gMainThreadCallbacks[0]);
{
MonitorAutoUnlock unlock(*gMainThreadCallbackMonitor);
AutoDisallowThreadEvents disallow;
callback();
}
} else {
gMainThreadCallbackMonitor->Wait();
}
}
// As for RestoreCheckpointAndResume, we shouldn't resume the main thread
// while it still has callbacks to execute.
MOZ_RELEASE_ASSERT(gMainThreadCallbacks.empty());
// If we diverge from the recording the only way we can get back to resuming
// normal execution is to rewind to a checkpoint prior to the divergence.
MOZ_RELEASE_ASSERT(!HasDivergedFromRecording());
gMainThreadIsPaused = false;
}
void PauseMainThreadAndInvokeCallback(const std::function<void()>& aCallback) {
{
MonitorAutoLock lock(*gMainThreadCallbackMonitor);
gMainThreadShouldPause = true;
gMainThreadCallbacks.append(aCallback);
gMainThreadCallbackMonitor->Notify();
}
if (Thread::CurrentIsMainThread()) {
PauseMainThreadAndServiceCallbacks();
}
}
void ResumeExecution() {
MonitorAutoLock lock(*gMainThreadCallbackMonitor);
gMainThreadShouldPause = false;
gMainThreadCallbackMonitor->Notify();
}
void SetIsActiveChild(bool aActive) { gRewindInfo->mIsActiveChild = aActive; }
bool IsActiveChild() { return gRewindInfo->mIsActiveChild; }
} // namespace recordreplay
} // namespace mozilla