toolkit/recordreplay/File.h
author Ryan Hunt <rhunt@eqrion.net>
Tue, 16 Apr 2019 00:27:46 +0000
changeset 469682 b3cffdee2f6caadc379280a90a8d85ca80870f3e
parent 448947 6f3709b3878117466168c40affa7bca0b60cf75b
child 488573 eff329b9c6a2dc11bc3f21241032f6ebd3201029
permissions -rw-r--r--
Bug 1544538 - Pref off advanced layers. r=jrmuizel Differential Revision: https://phabricator.services.mozilla.com/D27565

/* -*- 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);

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