зеркало из https://github.com/mozilla/gecko-dev.git
278 строки
7.5 KiB
C
278 строки
7.5 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;
|
||
|
|
||
|
inline bool operator == (const StreamChunkLocation& aOther) const {
|
||
|
return mOffset == aOther.mOffset
|
||
|
&& mCompressedSize == aOther.mCompressedSize
|
||
|
&& mDecompressedSize == aOther.mDecompressedSize;
|
||
|
}
|
||
|
};
|
||
|
|
||
|
enum class StreamName
|
||
|
{
|
||
|
Main,
|
||
|
Lock,
|
||
|
Event,
|
||
|
Assert,
|
||
|
Count
|
||
|
};
|
||
|
|
||
|
class File;
|
||
|
|
||
|
class Stream
|
||
|
{
|
||
|
friend class File;
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
// 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;
|
||
|
|
||
|
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)
|
||
|
, mChunkIndex(0)
|
||
|
, mFlushedChunks(0)
|
||
|
{}
|
||
|
|
||
|
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));
|
||
|
}
|
||
|
|
||
|
// Make sure that a value is the same while replaying as it was while
|
||
|
// recording.
|
||
|
void CheckInput(size_t aValue);
|
||
|
|
||
|
// Add a thread event to this file. Each thread event in a file is followed
|
||
|
// by additional data specific to that event. Generally, CheckInput should be
|
||
|
// used while recording or replaying the data for a thread event so that any
|
||
|
// discrepancies with the recording are found immediately.
|
||
|
inline void RecordOrReplayThreadEvent(ThreadEvent aEvent) {
|
||
|
CheckInput((size_t)aEvent);
|
||
|
}
|
||
|
|
||
|
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 Flush(bool aTakeLock);
|
||
|
|
||
|
static size_t BallastMaxSize();
|
||
|
};
|
||
|
|
||
|
class File
|
||
|
{
|
||
|
public:
|
||
|
enum Mode {
|
||
|
WRITE,
|
||
|
READ
|
||
|
};
|
||
|
|
||
|
friend class Stream;
|
||
|
|
||
|
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,
|
||
|
bool aTakeLock);
|
||
|
void ReadChunk(char* aDest, const StreamChunkLocation& aChunk);
|
||
|
};
|
||
|
|
||
|
} // namespace recordreplay
|
||
|
} // namespace mozilla
|
||
|
|
||
|
#endif // mozilla_recordreplay_File_h
|