зеркало из https://github.com/mozilla/gecko-dev.git
275 строки
8.0 KiB
C++
275 строки
8.0 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/. */
|
|
|
|
#ifndef mozilla_recordreplay_File_h
|
|
#define mozilla_recordreplay_File_h
|
|
|
|
#include "InfallibleVector.h"
|
|
#include "ProcessRecordReplay.h"
|
|
#include "SpinLock.h"
|
|
|
|
#include "mozilla/PodOperations.h"
|
|
#include "mozilla/RecordReplay.h"
|
|
#include "mozilla/UniquePtr.h"
|
|
|
|
namespace mozilla {
|
|
namespace recordreplay {
|
|
|
|
// Structure managing file I/O. Each file contains an index for a set of named
|
|
// streams, whose contents are compressed and interleaved throughout the file.
|
|
// Additionally, we directly manage the file handle and all associated memory.
|
|
// This makes it easier to restore memory snapshots without getting confused
|
|
// about the state of the file handles which the process has opened. Data
|
|
// written and read from files is automatically compressed with LZ4.
|
|
//
|
|
// Files are used internally for any disk accesses which the record/replay
|
|
// infrastructure needs to make. Currently, this is only for accessing the
|
|
// recording file.
|
|
//
|
|
// File is threadsafe for simultaneous read/read and write/write accesses.
|
|
// Stream is not threadsafe.
|
|
|
|
// A location of a chunk of a stream within a file.
|
|
struct StreamChunkLocation {
|
|
// Offset into the file of the start of the chunk.
|
|
uint64_t mOffset;
|
|
|
|
// Compressed (stored) size of the chunk.
|
|
uint32_t mCompressedSize;
|
|
|
|
// Decompressed size of the chunk.
|
|
uint32_t mDecompressedSize;
|
|
|
|
// Hash of the compressed chunk data.
|
|
uint32_t mHash;
|
|
|
|
// Position in the stream of the start of this chunk.
|
|
uint64_t mStreamPos;
|
|
};
|
|
|
|
enum class StreamName { Main, Lock, Event, Count };
|
|
|
|
class File;
|
|
class RecordingEventSection;
|
|
|
|
class Stream {
|
|
friend class File;
|
|
friend class RecordingEventSection;
|
|
|
|
// File this stream belongs to.
|
|
File* mFile;
|
|
|
|
// Prefix name for this stream.
|
|
StreamName mName;
|
|
|
|
// Index which, when combined to mName, uniquely identifies this stream in
|
|
// the file.
|
|
size_t mNameIndex;
|
|
|
|
// When writing, all chunks that have been flushed to disk. When reading, all
|
|
// chunks in the entire stream.
|
|
InfallibleVector<StreamChunkLocation> mChunks;
|
|
|
|
// Data buffer.
|
|
UniquePtr<char[]> mBuffer;
|
|
|
|
// The maximum number of bytes to buffer before compressing and writing to
|
|
// disk, and the maximum number of bytes that can be decompressed at once.
|
|
static const size_t BUFFER_MAX = 1024 * 1024;
|
|
|
|
// The capacity of mBuffer, at most BUFFER_MAX.
|
|
size_t mBufferSize;
|
|
|
|
// During reading, the number of accessible bytes in mBuffer.
|
|
size_t mBufferLength;
|
|
|
|
// The number of bytes read or written from mBuffer.
|
|
size_t mBufferPos;
|
|
|
|
// The number of uncompressed bytes read or written from the stream.
|
|
size_t mStreamPos;
|
|
|
|
// Any buffer available for use when decompressing or compressing data.
|
|
UniquePtr<char[]> mBallast;
|
|
size_t mBallastSize;
|
|
|
|
// Any buffer available to check for input mismatches.
|
|
UniquePtr<char[]> mInputBallast;
|
|
size_t mInputBallastSize;
|
|
|
|
// The last event in this stream, in case of an input mismatch.
|
|
ThreadEvent mLastEvent;
|
|
|
|
// The number of chunks that have been completely read or written. When
|
|
// writing, this equals mChunks.length().
|
|
size_t mChunkIndex;
|
|
|
|
// When writing, the number of chunks in this stream when the file was last
|
|
// flushed.
|
|
size_t mFlushedChunks;
|
|
|
|
// Whether there is a RecordingEventSection instance active for this stream.
|
|
bool mInRecordingEventSection;
|
|
|
|
Stream(File* aFile, StreamName aName, size_t aNameIndex)
|
|
: mFile(aFile),
|
|
mName(aName),
|
|
mNameIndex(aNameIndex),
|
|
mBuffer(nullptr),
|
|
mBufferSize(0),
|
|
mBufferLength(0),
|
|
mBufferPos(0),
|
|
mStreamPos(0),
|
|
mBallast(nullptr),
|
|
mBallastSize(0),
|
|
mInputBallast(nullptr),
|
|
mInputBallastSize(0),
|
|
mLastEvent((ThreadEvent)0),
|
|
mChunkIndex(0),
|
|
mFlushedChunks(0),
|
|
mInRecordingEventSection(false) {}
|
|
|
|
public:
|
|
StreamName Name() const { return mName; }
|
|
size_t NameIndex() const { return mNameIndex; }
|
|
|
|
void ReadBytes(void* aData, size_t aSize);
|
|
void WriteBytes(const void* aData, size_t aSize);
|
|
size_t ReadScalar();
|
|
void WriteScalar(size_t aValue);
|
|
bool AtEnd();
|
|
|
|
inline void RecordOrReplayBytes(void* aData, size_t aSize) {
|
|
if (IsRecording()) {
|
|
WriteBytes(aData, aSize);
|
|
} else {
|
|
ReadBytes(aData, aSize);
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void RecordOrReplayScalar(T* aPtr) {
|
|
if (IsRecording()) {
|
|
WriteScalar((size_t)*aPtr);
|
|
} else {
|
|
*aPtr = (T)ReadScalar();
|
|
}
|
|
}
|
|
|
|
template <typename T>
|
|
inline void RecordOrReplayValue(T* aPtr) {
|
|
RecordOrReplayBytes(aPtr, sizeof(T));
|
|
}
|
|
|
|
// Note a new thread event for this stream, and make sure it is the same
|
|
// while replaying as it was while recording.
|
|
void RecordOrReplayThreadEvent(ThreadEvent aEvent);
|
|
|
|
// Replay a thread event without requiring it to be a specific event.
|
|
ThreadEvent ReplayThreadEvent();
|
|
|
|
// Make sure that a value or buffer is the same while replaying as it was
|
|
// while recording.
|
|
void CheckInput(size_t aValue);
|
|
void CheckInput(const char* aValue);
|
|
void CheckInput(const void* aData, size_t aSize);
|
|
|
|
inline size_t StreamPosition() { return mStreamPos; }
|
|
|
|
private:
|
|
enum ShouldCopy { DontCopyExistingData, CopyExistingData };
|
|
|
|
void EnsureMemory(UniquePtr<char[]>* aBuf, size_t* aSize, size_t aNeededSize,
|
|
size_t aMaxSize, ShouldCopy aCopy);
|
|
void EnsureInputBallast(size_t aSize);
|
|
void Flush(bool aTakeLock);
|
|
const char* ReadInputString();
|
|
|
|
static size_t BallastMaxSize();
|
|
};
|
|
|
|
class File {
|
|
public:
|
|
enum Mode { WRITE, READ };
|
|
|
|
friend class Stream;
|
|
friend class RecordingEventSection;
|
|
|
|
private:
|
|
// Open file handle, or 0 if closed.
|
|
FileHandle mFd;
|
|
|
|
// Whether this file is open for writing or reading.
|
|
Mode mMode;
|
|
|
|
// When writing, the current offset into the file.
|
|
uint64_t mWriteOffset;
|
|
|
|
// The offset of the last index read or written to the file.
|
|
uint64_t mLastIndexOffset;
|
|
|
|
// All streams in this file, indexed by stream name and name index.
|
|
typedef InfallibleVector<UniquePtr<Stream>> StreamVector;
|
|
StreamVector mStreams[(size_t)StreamName::Count];
|
|
|
|
// Lock protecting access to this file.
|
|
SpinLock mLock;
|
|
|
|
// When writing, lock for synchronizing file flushes (writer) with other
|
|
// threads writing to streams in this file (readers).
|
|
ReadWriteSpinLock mStreamLock;
|
|
|
|
void Clear() {
|
|
mFd = 0;
|
|
mMode = READ;
|
|
mWriteOffset = 0;
|
|
mLastIndexOffset = 0;
|
|
for (auto& vector : mStreams) {
|
|
vector.clear();
|
|
}
|
|
PodZero(&mLock);
|
|
PodZero(&mStreamLock);
|
|
}
|
|
|
|
public:
|
|
File() { Clear(); }
|
|
~File() { Close(); }
|
|
|
|
bool Open(const char* aName, Mode aMode);
|
|
void Close();
|
|
|
|
bool OpenForWriting() const { return mFd && mMode == WRITE; }
|
|
bool OpenForReading() const { return mFd && mMode == READ; }
|
|
|
|
Stream* OpenStream(StreamName aName, size_t aNameIndex);
|
|
|
|
// Prevent/allow other threads to write to streams in this file.
|
|
void PreventStreamWrites() { mStreamLock.WriteLock(); }
|
|
void AllowStreamWrites() { mStreamLock.WriteUnlock(); }
|
|
|
|
// Flush any changes since the last Flush() call to disk, returning whether
|
|
// there were such changes.
|
|
bool Flush();
|
|
|
|
enum class ReadIndexResult { InvalidFile, EndOfFile, FoundIndex };
|
|
|
|
// Read any data added to the file by a Flush() call. aUpdatedStreams is
|
|
// optional and filled in with streams whose contents have changed, and may
|
|
// have duplicates.
|
|
ReadIndexResult ReadNextIndex(InfallibleVector<Stream*>* aUpdatedStreams);
|
|
|
|
private:
|
|
StreamChunkLocation WriteChunk(const char* aStart, size_t aCompressedSize,
|
|
size_t aDecompressedSize, uint64_t aStreamPos,
|
|
bool aTakeLock);
|
|
void ReadChunk(char* aDest, const StreamChunkLocation& aChunk);
|
|
};
|
|
|
|
} // namespace recordreplay
|
|
} // namespace mozilla
|
|
|
|
#endif // mozilla_recordreplay_File_h
|