mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-10-23 02:05:42 +00:00
Bug 1465287 Part 1 - IPC channels, r=mccr8,jld.
--HG-- extra : rebase_source : c08c055d06a4ffc3241c7220ac57a8a820cc03fd
This commit is contained in:
parent
38ee8c769d
commit
dbf2a5eadf
267
toolkit/recordreplay/ipc/Channel.cpp
Normal file
267
toolkit/recordreplay/ipc/Channel.cpp
Normal file
@ -0,0 +1,267 @@
|
||||
/* -*- 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 "Channel.h"
|
||||
|
||||
#include "ChildIPC.h"
|
||||
#include "ProcessRewind.h"
|
||||
#include "Thread.h"
|
||||
|
||||
#include "MainThreadUtils.h"
|
||||
|
||||
#include "base/process_util.h"
|
||||
|
||||
#include <sys/socket.h>
|
||||
#include <sys/un.h>
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
|
||||
static void
|
||||
GetSocketAddress(struct sockaddr_un* addr, base::ProcessId aMiddlemanPid, size_t aId)
|
||||
{
|
||||
addr->sun_family = AF_UNIX;
|
||||
int n = snprintf(addr->sun_path, sizeof(addr->sun_path), "/tmp/WebReplay_%d_%d", aMiddlemanPid, (int) aId);
|
||||
MOZ_RELEASE_ASSERT(n >= 0 && n < (int) sizeof(addr->sun_path));
|
||||
addr->sun_len = SUN_LEN(addr);
|
||||
}
|
||||
|
||||
struct HelloMessage
|
||||
{
|
||||
int32_t mMagic;
|
||||
};
|
||||
|
||||
Channel::Channel(size_t aId, const MessageHandler& aHandler)
|
||||
: mId(aId)
|
||||
, mHandler(aHandler)
|
||||
, mInitialized(false)
|
||||
, mConnectionFd(0)
|
||||
, mFd(0)
|
||||
, mMessageBytes(0)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread());
|
||||
|
||||
if (IsRecordingOrReplaying()) {
|
||||
MOZ_RELEASE_ASSERT(AreThreadEventsPassedThrough());
|
||||
|
||||
mFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
MOZ_RELEASE_ASSERT(mFd > 0);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
GetSocketAddress(&addr, child::MiddlemanProcessId(), mId);
|
||||
|
||||
int rv = HANDLE_EINTR(connect(mFd, (sockaddr*) &addr, SUN_LEN(&addr)));
|
||||
MOZ_RELEASE_ASSERT(rv >= 0);
|
||||
|
||||
DirectDeleteFile(addr.sun_path);
|
||||
} else {
|
||||
MOZ_RELEASE_ASSERT(IsMiddleman());
|
||||
|
||||
mConnectionFd = socket(AF_UNIX, SOCK_STREAM, 0);
|
||||
MOZ_RELEASE_ASSERT(mConnectionFd > 0);
|
||||
|
||||
struct sockaddr_un addr;
|
||||
GetSocketAddress(&addr, base::GetCurrentProcId(), mId);
|
||||
|
||||
int rv = bind(mConnectionFd, (sockaddr*) &addr, SUN_LEN(&addr));
|
||||
MOZ_RELEASE_ASSERT(rv >= 0);
|
||||
|
||||
rv = listen(mConnectionFd, 1);
|
||||
MOZ_RELEASE_ASSERT(rv >= 0);
|
||||
}
|
||||
|
||||
Thread::SpawnNonRecordedThread(ThreadMain, this);
|
||||
}
|
||||
|
||||
/* static */ void
|
||||
Channel::ThreadMain(void* aChannelArg)
|
||||
{
|
||||
Channel* channel = (Channel*) aChannelArg;
|
||||
|
||||
static const int32_t MagicValue = 0x914522b9;
|
||||
|
||||
if (IsRecordingOrReplaying()) {
|
||||
HelloMessage msg;
|
||||
|
||||
int rv = HANDLE_EINTR(recv(channel->mFd, &msg, sizeof(msg), MSG_WAITALL));
|
||||
MOZ_RELEASE_ASSERT(rv == sizeof(msg));
|
||||
MOZ_RELEASE_ASSERT(msg.mMagic == MagicValue);
|
||||
} else {
|
||||
MOZ_RELEASE_ASSERT(IsMiddleman());
|
||||
|
||||
channel->mFd = HANDLE_EINTR(accept(channel->mConnectionFd, nullptr, 0));
|
||||
MOZ_RELEASE_ASSERT(channel->mFd > 0);
|
||||
|
||||
HelloMessage msg;
|
||||
msg.mMagic = MagicValue;
|
||||
|
||||
int rv = HANDLE_EINTR(send(channel->mFd, &msg, sizeof(msg), 0));
|
||||
MOZ_RELEASE_ASSERT(rv == sizeof(msg));
|
||||
}
|
||||
|
||||
{
|
||||
MonitorAutoLock lock(channel->mMonitor);
|
||||
channel->mInitialized = true;
|
||||
channel->mMonitor.Notify();
|
||||
}
|
||||
|
||||
while (true) {
|
||||
Message* msg = channel->WaitForMessage();
|
||||
if (!msg) {
|
||||
break;
|
||||
}
|
||||
channel->mHandler(msg);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
Channel::SendMessage(const Message& aMsg)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(NS_IsMainThread() || aMsg.mType == MessageType::FatalError);
|
||||
|
||||
// Block until the channel is initialized.
|
||||
if (!mInitialized) {
|
||||
MonitorAutoLock lock(mMonitor);
|
||||
while (!mInitialized) {
|
||||
mMonitor.Wait();
|
||||
}
|
||||
}
|
||||
|
||||
PrintMessage("SendMsg", aMsg);
|
||||
|
||||
const char* ptr = (const char*) &aMsg;
|
||||
size_t nbytes = aMsg.mSize;
|
||||
while (nbytes) {
|
||||
int rv = HANDLE_EINTR(send(mFd, ptr, nbytes, 0));
|
||||
MOZ_RELEASE_ASSERT((size_t) rv <= nbytes);
|
||||
ptr += rv;
|
||||
nbytes -= rv;
|
||||
}
|
||||
}
|
||||
|
||||
Message*
|
||||
Channel::WaitForMessage()
|
||||
{
|
||||
if (!mMessageBuffer.length()) {
|
||||
mMessageBuffer.appendN(0, PageSize);
|
||||
}
|
||||
|
||||
size_t messageSize = 0;
|
||||
while (true) {
|
||||
if (mMessageBytes >= sizeof(Message)) {
|
||||
Message* msg = (Message*) mMessageBuffer.begin();
|
||||
messageSize = msg->mSize;
|
||||
if (mMessageBytes >= messageSize) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// Make sure the buffer is large enough for the entire incoming message.
|
||||
if (messageSize > mMessageBuffer.length()) {
|
||||
mMessageBuffer.appendN(0, messageSize - mMessageBuffer.length());
|
||||
}
|
||||
|
||||
ssize_t nbytes = HANDLE_EINTR(recv(mFd, &mMessageBuffer[mMessageBytes],
|
||||
mMessageBuffer.length() - mMessageBytes, 0));
|
||||
if (nbytes < 0) {
|
||||
MOZ_RELEASE_ASSERT(errno == EAGAIN);
|
||||
continue;
|
||||
} else if (nbytes == 0) {
|
||||
// The other side of the channel has shut down.
|
||||
if (IsMiddleman()) {
|
||||
return nullptr;
|
||||
}
|
||||
PrintSpew("Channel disconnected, exiting...\n");
|
||||
DeleteSnapshotFiles();
|
||||
_exit(0);
|
||||
}
|
||||
|
||||
mMessageBytes += nbytes;
|
||||
}
|
||||
|
||||
Message* res = ((Message*)mMessageBuffer.begin())->Clone();
|
||||
|
||||
// Remove the message we just received from the incoming buffer.
|
||||
size_t remaining = mMessageBytes - messageSize;
|
||||
if (remaining) {
|
||||
memmove(mMessageBuffer.begin(), &mMessageBuffer[messageSize], remaining);
|
||||
}
|
||||
mMessageBytes = remaining;
|
||||
|
||||
PrintMessage("RecvMsg", *res);
|
||||
return res;
|
||||
}
|
||||
|
||||
void
|
||||
Channel::PrintMessage(const char* aPrefix, const Message& aMsg)
|
||||
{
|
||||
if (!SpewEnabled()) {
|
||||
return;
|
||||
}
|
||||
AutoEnsurePassThroughThreadEvents pt;
|
||||
nsCString data;
|
||||
switch (aMsg.mType) {
|
||||
case MessageType::HitCheckpoint: {
|
||||
const HitCheckpointMessage& nmsg = (const HitCheckpointMessage&) aMsg;
|
||||
data.AppendPrintf("Id %d Endpoint %d Duration %.2f ms",
|
||||
(int) nmsg.mCheckpointId, nmsg.mRecordingEndpoint,
|
||||
nmsg.mDurationMicroseconds / 1000.0);
|
||||
break;
|
||||
}
|
||||
case MessageType::HitBreakpoint: {
|
||||
const HitBreakpointMessage& nmsg = (const HitBreakpointMessage&) aMsg;
|
||||
data.AppendPrintf("Endpoint %d", nmsg.mRecordingEndpoint);
|
||||
for (size_t i = 0; i < nmsg.NumBreakpoints(); i++) {
|
||||
data.AppendPrintf(" Id %d", nmsg.Breakpoints()[i]);
|
||||
}
|
||||
break;
|
||||
}
|
||||
case MessageType::Resume: {
|
||||
const ResumeMessage& nmsg = (const ResumeMessage&) aMsg;
|
||||
data.AppendPrintf("Forward %d", nmsg.mForward);
|
||||
break;
|
||||
}
|
||||
case MessageType::RestoreCheckpoint: {
|
||||
const RestoreCheckpointMessage& nmsg = (const RestoreCheckpointMessage&) aMsg;
|
||||
data.AppendPrintf("Id %d", (int) nmsg.mCheckpoint);
|
||||
break;
|
||||
}
|
||||
case MessageType::SetBreakpoint: {
|
||||
const SetBreakpointMessage& nmsg = (const SetBreakpointMessage&) aMsg;
|
||||
data.AppendPrintf("Id %d, Kind %s, Script %d, Offset %d, Frame %d",
|
||||
(int) nmsg.mId, nmsg.mPosition.KindString(), (int) nmsg.mPosition.mScript,
|
||||
(int) nmsg.mPosition.mOffset, (int) nmsg.mPosition.mFrameIndex);
|
||||
break;
|
||||
}
|
||||
case MessageType::DebuggerRequest: {
|
||||
const DebuggerRequestMessage& nmsg = (const DebuggerRequestMessage&) aMsg;
|
||||
data = NS_ConvertUTF16toUTF8(nsDependentString(nmsg.Buffer(), nmsg.BufferSize()));
|
||||
break;
|
||||
}
|
||||
case MessageType::DebuggerResponse: {
|
||||
const DebuggerResponseMessage& nmsg = (const DebuggerResponseMessage&) aMsg;
|
||||
data = NS_ConvertUTF16toUTF8(nsDependentString(nmsg.Buffer(), nmsg.BufferSize()));
|
||||
break;
|
||||
}
|
||||
case MessageType::SetIsActive: {
|
||||
const SetIsActiveMessage& nmsg = (const SetIsActiveMessage&) aMsg;
|
||||
data.AppendPrintf("%d", nmsg.mActive);
|
||||
break;
|
||||
}
|
||||
case MessageType::SetSaveCheckpoint: {
|
||||
const SetSaveCheckpointMessage& nmsg = (const SetSaveCheckpointMessage&) aMsg;
|
||||
data.AppendPrintf("Id %d, Save %d", (int) nmsg.mCheckpoint, nmsg.mSave);
|
||||
break;
|
||||
}
|
||||
default:
|
||||
break;
|
||||
}
|
||||
const char* kind = IsMiddleman() ? "Middleman" : (IsRecording() ? "Recording" : "Replaying");
|
||||
PrintSpew("%s%s:%d %s %s\n", kind, aPrefix, (int) mId, aMsg.TypeString(), data.get());
|
||||
}
|
||||
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
454
toolkit/recordreplay/ipc/Channel.h
Normal file
454
toolkit/recordreplay/ipc/Channel.h
Normal file
@ -0,0 +1,454 @@
|
||||
/* -*- 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_Channel_h
|
||||
#define mozilla_recordreplay_Channel_h
|
||||
|
||||
#include "base/process.h"
|
||||
|
||||
#include "js/ReplayHooks.h"
|
||||
#include "mozilla/gfx/Types.h"
|
||||
#include "mozilla/Maybe.h"
|
||||
|
||||
#include "File.h"
|
||||
#include "Monitor.h"
|
||||
|
||||
namespace mozilla {
|
||||
namespace recordreplay {
|
||||
|
||||
// This file has definitions for creating and communicating on a special
|
||||
// bidirectional channel between a middleman process and a recording or
|
||||
// replaying process. This communication is not included in the recording, and
|
||||
// when replaying this is the only mechanism the child can use to communicate
|
||||
// with the middleman process.
|
||||
//
|
||||
// Replaying processes can rewind themselves, restoring execution state and the
|
||||
// contents of all heap memory to that at an earlier point. To keep the
|
||||
// replaying process and middleman from getting out of sync with each other,
|
||||
// there are tight constraints on when messages may be sent across the channel
|
||||
// by one process or the other. At any given time the child process may be
|
||||
// either paused or unpaused. If it is paused, it is not doing any execution
|
||||
// and cannot rewind itself. If it is unpaused, it may execute content and may
|
||||
// rewind itself.
|
||||
//
|
||||
// Messages can be sent from the child process to the middleman only when the
|
||||
// child process is unpaused, and messages can only be sent from the middleman
|
||||
// to the child process when the child process is paused. This prevents
|
||||
// messages from being lost when they are sent from the middleman as the
|
||||
// replaying process rewinds itself. A few exceptions to this rule are noted
|
||||
// below.
|
||||
//
|
||||
// Some additional synchronization is needed between different child processes:
|
||||
// replaying processes can read from the same file which a recording process is
|
||||
// writing to. While it is ok for a replaying process to read from the file
|
||||
// while the recording process is appending new chunks to it (see File.cpp),
|
||||
// all replaying processes must be paused when the recording process is
|
||||
// flushing a new index to the file.
|
||||
|
||||
#define ForEachMessageType(_Macro) \
|
||||
/* Messages sent from the middleman to the child process. */ \
|
||||
\
|
||||
/* Sent at startup. */ \
|
||||
_Macro(Introduction) \
|
||||
\
|
||||
/* Sent to recording processes when exiting. */ \
|
||||
_Macro(Terminate) \
|
||||
\
|
||||
/* Flush the current recording to disk. */ \
|
||||
_Macro(FlushRecording) \
|
||||
\
|
||||
/* Poke a child that is recording to create an artificial checkpoint, rather than */ \
|
||||
/* (potentially) idling indefinitely. This has no effect on a replaying process. */ \
|
||||
_Macro(CreateCheckpoint) \
|
||||
\
|
||||
/* Debugger JSON messages are initially sent from the parent. The child unpauses */ \
|
||||
/* after receiving the message and will pause after it sends a DebuggerResponse. */ \
|
||||
_Macro(DebuggerRequest) \
|
||||
\
|
||||
/* Set or clear a JavaScript breakpoint. */ \
|
||||
_Macro(SetBreakpoint) \
|
||||
\
|
||||
/* Unpause the child and play execution either to the next point when a */ \
|
||||
/* breakpoint is hit, or to the next checkpoint. Resumption may be either */ \
|
||||
/* forward or backward. */ \
|
||||
_Macro(Resume) \
|
||||
\
|
||||
/* Rewind to a particular saved checkpoint in the past. */ \
|
||||
_Macro(RestoreCheckpoint) \
|
||||
\
|
||||
/* Notify the child whether it is the active child and should send paint and similar */ \
|
||||
/* messages to the middleman. */ \
|
||||
_Macro(SetIsActive) \
|
||||
\
|
||||
/* Set whether to perform intentional crashes, for testing. */ \
|
||||
_Macro(SetAllowIntentionalCrashes) \
|
||||
\
|
||||
/* Set whether to save a particular checkpoint. */ \
|
||||
_Macro(SetSaveCheckpoint) \
|
||||
\
|
||||
/* Messages sent from the child process to the middleman. */ \
|
||||
\
|
||||
/* Sent in response to a FlushRecording, telling the middleman that the flush */ \
|
||||
/* has finished. */ \
|
||||
_Macro(RecordingFlushed) \
|
||||
\
|
||||
/* A critical error occurred and execution cannot continue. The child will */ \
|
||||
/* stop executing after sending this message and will wait to be terminated. */ \
|
||||
_Macro(FatalError) \
|
||||
\
|
||||
/* The child's graphics were repainted. */ \
|
||||
_Macro(Paint) \
|
||||
\
|
||||
/* Notify the middleman that a checkpoint or breakpoint was hit. */ \
|
||||
/* The child will pause after sending these messages. */ \
|
||||
_Macro(HitCheckpoint) \
|
||||
_Macro(HitBreakpoint) \
|
||||
\
|
||||
/* Send a response to a DebuggerRequest message. */ \
|
||||
_Macro(DebuggerResponse) \
|
||||
\
|
||||
/* Notify that the 'AlwaysMarkMajorCheckpoints' directive was invoked. */ \
|
||||
_Macro(AlwaysMarkMajorCheckpoints)
|
||||
|
||||
enum class MessageType
|
||||
{
|
||||
#define DefineEnum(Kind) Kind,
|
||||
ForEachMessageType(DefineEnum)
|
||||
#undef DefineEnum
|
||||
};
|
||||
|
||||
struct Message
|
||||
{
|
||||
MessageType mType;
|
||||
|
||||
// Total message size, including the header.
|
||||
uint32_t mSize;
|
||||
|
||||
protected:
|
||||
Message(MessageType aType, uint32_t aSize)
|
||||
: mType(aType), mSize(aSize)
|
||||
{
|
||||
MOZ_RELEASE_ASSERT(mSize >= sizeof(*this));
|
||||
}
|
||||
|
||||
public:
|
||||
Message* Clone() const {
|
||||
char* res = (char*) malloc(mSize);
|
||||
memcpy(res, this, mSize);
|
||||
return (Message*) res;
|
||||
}
|
||||
|
||||
const char* TypeString() const {
|
||||
switch (mType) {
|
||||
#define EnumToString(Kind) case MessageType::Kind: return #Kind;
|
||||
ForEachMessageType(EnumToString)
|
||||
#undef EnumToString
|
||||
default: return "Unknown";
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
template <typename T, typename Elem>
|
||||
Elem* Data() { return (Elem*) (sizeof(T) + (char*) this); }
|
||||
|
||||
template <typename T, typename Elem>
|
||||
const Elem* Data() const { return (const Elem*) (sizeof(T) + (const char*) this); }
|
||||
|
||||
template <typename T, typename Elem>
|
||||
size_t DataSize() const { return (mSize - sizeof(T)) / sizeof(Elem); }
|
||||
|
||||
template <typename T, typename Elem, typename... Args>
|
||||
static T* NewWithData(size_t aBufferSize, Args&&... aArgs) {
|
||||
size_t size = sizeof(T) + aBufferSize * sizeof(Elem);
|
||||
void* ptr = malloc(size);
|
||||
return new(ptr) T(size, std::forward<Args>(aArgs)...);
|
||||
}
|
||||
};
|
||||
|
||||
struct IntroductionMessage : public Message
|
||||
{
|
||||
base::ProcessId mParentPid;
|
||||
uint32_t mPrefsLen;
|
||||
uint32_t mArgc;
|
||||
|
||||
IntroductionMessage(uint32_t aSize, base::ProcessId aParentPid, uint32_t aPrefsLen, uint32_t aArgc)
|
||||
: Message(MessageType::Introduction, aSize)
|
||||
, mParentPid(aParentPid)
|
||||
, mPrefsLen(aPrefsLen)
|
||||
, mArgc(aArgc)
|
||||
{}
|
||||
|
||||
char* PrefsData() { return Data<IntroductionMessage, char>(); }
|
||||
char* ArgvString() { return Data<IntroductionMessage, char>() + mPrefsLen; }
|
||||
|
||||
const char* PrefsData() const { return Data<IntroductionMessage, char>(); }
|
||||
const char* ArgvString() const { return Data<IntroductionMessage, char>() + mPrefsLen; }
|
||||
|
||||
static IntroductionMessage* New(base::ProcessId aParentPid, char* aPrefs, size_t aPrefsLen,
|
||||
int aArgc, char* aArgv[]) {
|
||||
size_t argsLen = 0;
|
||||
for (int i = 0; i < aArgc; i++) {
|
||||
argsLen += strlen(aArgv[i]) + 1;
|
||||
}
|
||||
|
||||
IntroductionMessage* res =
|
||||
NewWithData<IntroductionMessage, char>(aPrefsLen + argsLen, aParentPid, aPrefsLen, aArgc);
|
||||
|
||||
memcpy(res->PrefsData(), aPrefs, aPrefsLen);
|
||||
|
||||
size_t offset = 0;
|
||||
for (int i = 0; i < aArgc; i++) {
|
||||
memcpy(&res->ArgvString()[offset], aArgv[i], strlen(aArgv[i]) + 1);
|
||||
offset += strlen(aArgv[i]) + 1;
|
||||
}
|
||||
MOZ_RELEASE_ASSERT(offset == argsLen);
|
||||
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
template <MessageType Type>
|
||||
struct EmptyMessage : public Message
|
||||
{
|
||||
EmptyMessage()
|
||||
: Message(Type, sizeof(*this))
|
||||
{}
|
||||
};
|
||||
|
||||
typedef EmptyMessage<MessageType::Terminate> TerminateMessage;
|
||||
typedef EmptyMessage<MessageType::CreateCheckpoint> CreateCheckpointMessage;
|
||||
typedef EmptyMessage<MessageType::FlushRecording> FlushRecordingMessage;
|
||||
|
||||
template <MessageType Type>
|
||||
struct JSONMessage : public Message
|
||||
{
|
||||
explicit JSONMessage(uint32_t aSize)
|
||||
: Message(Type, aSize)
|
||||
{}
|
||||
|
||||
const char16_t* Buffer() const { return Data<JSONMessage<Type>, char16_t>(); }
|
||||
size_t BufferSize() const { return DataSize<JSONMessage<Type>, char16_t>(); }
|
||||
|
||||
static JSONMessage<Type>* New(const char16_t* aBuffer, size_t aBufferSize) {
|
||||
JSONMessage<Type>* res = NewWithData<JSONMessage<Type>, char16_t>(aBufferSize);
|
||||
MOZ_RELEASE_ASSERT(res->BufferSize() == aBufferSize);
|
||||
PodCopy(res->Data<JSONMessage<Type>, char16_t>(), aBuffer, aBufferSize);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
typedef JSONMessage<MessageType::DebuggerRequest> DebuggerRequestMessage;
|
||||
typedef JSONMessage<MessageType::DebuggerResponse> DebuggerResponseMessage;
|
||||
|
||||
struct SetBreakpointMessage : public Message
|
||||
{
|
||||
// ID of the breakpoint to change.
|
||||
size_t mId;
|
||||
|
||||
// New position of the breakpoint. If this is invalid then the breakpoint is
|
||||
// being cleared.
|
||||
JS::replay::ExecutionPosition mPosition;
|
||||
|
||||
SetBreakpointMessage(size_t aId, const JS::replay::ExecutionPosition& aPosition)
|
||||
: Message(MessageType::SetBreakpoint, sizeof(*this))
|
||||
, mId(aId)
|
||||
, mPosition(aPosition)
|
||||
{}
|
||||
};
|
||||
|
||||
struct ResumeMessage : public Message
|
||||
{
|
||||
// Whether to travel forwards or backwards.
|
||||
bool mForward;
|
||||
|
||||
explicit ResumeMessage(bool aForward)
|
||||
: Message(MessageType::Resume, sizeof(*this))
|
||||
, mForward(aForward)
|
||||
{}
|
||||
};
|
||||
|
||||
struct RestoreCheckpointMessage : public Message
|
||||
{
|
||||
// The checkpoint to restore.
|
||||
size_t mCheckpoint;
|
||||
|
||||
explicit RestoreCheckpointMessage(size_t aCheckpoint)
|
||||
: Message(MessageType::RestoreCheckpoint, sizeof(*this))
|
||||
, mCheckpoint(aCheckpoint)
|
||||
{}
|
||||
};
|
||||
|
||||
struct SetIsActiveMessage : public Message
|
||||
{
|
||||
// Whether this is the active child process (see ParentIPC.cpp).
|
||||
bool mActive;
|
||||
|
||||
explicit SetIsActiveMessage(bool aActive)
|
||||
: Message(MessageType::SetIsActive, sizeof(*this))
|
||||
, mActive(aActive)
|
||||
{}
|
||||
};
|
||||
|
||||
struct SetAllowIntentionalCrashesMessage : public Message
|
||||
{
|
||||
// Whether to allow intentional crashes in the future or not.
|
||||
bool mAllowed;
|
||||
|
||||
explicit SetAllowIntentionalCrashesMessage(bool aAllowed)
|
||||
: Message(MessageType::SetAllowIntentionalCrashes, sizeof(*this))
|
||||
, mAllowed(aAllowed)
|
||||
{}
|
||||
};
|
||||
|
||||
struct SetSaveCheckpointMessage : public Message
|
||||
{
|
||||
// The checkpoint in question.
|
||||
size_t mCheckpoint;
|
||||
|
||||
// Whether to save this checkpoint whenever it is encountered.
|
||||
bool mSave;
|
||||
|
||||
SetSaveCheckpointMessage(size_t aCheckpoint, bool aSave)
|
||||
: Message(MessageType::SetSaveCheckpoint, sizeof(*this))
|
||||
, mCheckpoint(aCheckpoint)
|
||||
, mSave(aSave)
|
||||
{}
|
||||
};
|
||||
|
||||
typedef EmptyMessage<MessageType::RecordingFlushed> RecordingFlushedMessage;
|
||||
|
||||
struct FatalErrorMessage : public Message
|
||||
{
|
||||
explicit FatalErrorMessage(uint32_t aSize)
|
||||
: Message(MessageType::FatalError, aSize)
|
||||
{}
|
||||
|
||||
const char* Error() const { return Data<FatalErrorMessage, const char>(); }
|
||||
};
|
||||
|
||||
static const gfx::SurfaceFormat gSurfaceFormat = gfx::SurfaceFormat::B8G8R8X8;
|
||||
|
||||
struct PaintMessage : public Message
|
||||
{
|
||||
uint32_t mWidth;
|
||||
uint32_t mHeight;
|
||||
|
||||
PaintMessage(uint32_t aWidth, uint32_t aHeight)
|
||||
: Message(MessageType::Paint, sizeof(*this))
|
||||
, mWidth(aWidth)
|
||||
, mHeight(aHeight)
|
||||
{}
|
||||
};
|
||||
|
||||
struct HitCheckpointMessage : public Message
|
||||
{
|
||||
uint32_t mCheckpointId;
|
||||
bool mRecordingEndpoint;
|
||||
|
||||
// When recording, the amount of non-idle time taken to get to this
|
||||
// checkpoint from the previous one.
|
||||
double mDurationMicroseconds;
|
||||
|
||||
HitCheckpointMessage(uint32_t aCheckpointId, bool aRecordingEndpoint, double aDurationMicroseconds)
|
||||
: Message(MessageType::HitCheckpoint, sizeof(*this))
|
||||
, mCheckpointId(aCheckpointId)
|
||||
, mRecordingEndpoint(aRecordingEndpoint)
|
||||
, mDurationMicroseconds(aDurationMicroseconds)
|
||||
{}
|
||||
};
|
||||
|
||||
struct HitBreakpointMessage : public Message
|
||||
{
|
||||
bool mRecordingEndpoint;
|
||||
|
||||
HitBreakpointMessage(uint32_t aSize, bool aRecordingEndpoint)
|
||||
: Message(MessageType::HitBreakpoint, aSize)
|
||||
, mRecordingEndpoint(aRecordingEndpoint)
|
||||
{}
|
||||
|
||||
const uint32_t* Breakpoints() const { return Data<HitBreakpointMessage, uint32_t>(); }
|
||||
uint32_t NumBreakpoints() const { return DataSize<HitBreakpointMessage, uint32_t>(); }
|
||||
|
||||
static HitBreakpointMessage* New(bool aRecordingEndpoint,
|
||||
const uint32_t* aBreakpoints, size_t aNumBreakpoints) {
|
||||
HitBreakpointMessage* res =
|
||||
NewWithData<HitBreakpointMessage, uint32_t>(aNumBreakpoints, aRecordingEndpoint);
|
||||
MOZ_RELEASE_ASSERT(res->NumBreakpoints() == aNumBreakpoints);
|
||||
PodCopy(res->Data<HitBreakpointMessage, uint32_t>(), aBreakpoints, aNumBreakpoints);
|
||||
return res;
|
||||
}
|
||||
};
|
||||
|
||||
typedef EmptyMessage<MessageType::AlwaysMarkMajorCheckpoints> AlwaysMarkMajorCheckpointsMessage;
|
||||
|
||||
class Channel
|
||||
{
|
||||
public:
|
||||
// Note: the handler is responsible for freeing its input message. It will be
|
||||
// called on the channel's message thread.
|
||||
typedef std::function<void(Message*)> MessageHandler;
|
||||
|
||||
private:
|
||||
// ID for this channel, unique for the middleman.
|
||||
size_t mId;
|
||||
|
||||
// Callback to invoke off thread on incoming messages.
|
||||
MessageHandler mHandler;
|
||||
|
||||
// Whether the channel is initialized and ready for outgoing messages.
|
||||
Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mInitialized;
|
||||
|
||||
// Descriptor used to accept connections on the parent side.
|
||||
int mConnectionFd;
|
||||
|
||||
// Descriptor used to communicate with the other side.
|
||||
int mFd;
|
||||
|
||||
// For synchronizing initialization of the channel.
|
||||
Monitor mMonitor;
|
||||
|
||||
// Buffer for message data received from the other side of the channel.
|
||||
InfallibleVector<char, 0, AllocPolicy<UntrackedMemoryKind::Generic>> mMessageBuffer;
|
||||
|
||||
// The number of bytes of data already in the message buffer.
|
||||
size_t mMessageBytes;
|
||||
|
||||
// If spew is enabled, print a message and associated info to stderr.
|
||||
void PrintMessage(const char* aPrefix, const Message& aMsg);
|
||||
|
||||
// Block until a complete message is received from the other side of the
|
||||
// channel.
|
||||
Message* WaitForMessage();
|
||||
|
||||
// Main routine for the channel's thread.
|
||||
static void ThreadMain(void* aChannel);
|
||||
|
||||
public:
|
||||
// Initialize this channel, connect to the other side, and spin up a thread
|
||||
// to process incoming messages by calling aHandler.
|
||||
Channel(size_t aId, const MessageHandler& aHandler);
|
||||
|
||||
size_t GetId() { return mId; }
|
||||
|
||||
// Send a message to the other side of the channel. This must be called on
|
||||
// the main thread, except for fatal error messages.
|
||||
void SendMessage(const Message& aMsg);
|
||||
};
|
||||
|
||||
// Command line option used to specify the channel ID for a child process.
|
||||
static const char* gChannelIDOption = "-recordReplayChannelID";
|
||||
|
||||
// ID for the mach message sent when notifying a subprocess about the graphics
|
||||
// shared memory buffer.
|
||||
static const int32_t GraphicsMessageId = 42;
|
||||
|
||||
// Fixed size of the graphics shared memory buffer.
|
||||
static const size_t GraphicsMemorySize = 4096 * 4096 * 4;
|
||||
|
||||
} // namespace recordreplay
|
||||
} // namespace mozilla
|
||||
|
||||
#endif // mozilla_recordreplay_Channel_h
|
Loading…
Reference in New Issue
Block a user