author | Brian Hackett <bhackett1024@gmail.com> |
Wed, 17 Oct 2018 10:04:29 -0600 | |
changeset 484425 | 0ce807f9d1f679c848341e4858df243a5c127bdf |
parent 484424 | 0130de663e9f9006da9381d89967758af9974224 |
child 484426 | baaaaa544bb0da29752973de7ce72a7d8d5ee84a |
push id | unknown |
push user | unknown |
push date | unknown |
reviewers | froydnj |
bugs | 1488808 |
milestone | 64.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/toolkit/recordreplay/Callback.cpp +++ b/toolkit/recordreplay/Callback.cpp @@ -44,16 +44,19 @@ BeginCallback(size_t aCallbackId) MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed()); Thread* thread = Thread::Current(); if (thread->IsMainThread()) { child::EndIdleTime(); } thread->SetPassThrough(false); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); + thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteCallback); thread->Events().WriteScalar(aCallbackId); } void EndCallback() { MOZ_RELEASE_ASSERT(IsRecording()); @@ -68,17 +71,18 @@ EndCallback() } void SaveOrRestoreCallbackData(void** aData) { MOZ_RELEASE_ASSERT(gCallbackData); Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RestoreCallbackData); size_t index = 0; if (IsRecording() && *aData) { StaticMutexAutoLock lock(gCallbackMutex); index = gCallbackData->GetIndex(*aData); } @@ -97,17 +101,18 @@ RemoveCallbackData(void* aData) StaticMutexAutoLock lock(gCallbackMutex); gCallbackData->Remove(aData); } void PassThroughThreadEventsAllowCallbacks(const std::function<void()>& aFn) { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); if (IsRecording()) { if (thread->IsMainThread()) { child::BeginIdleTime(); } thread->SetPassThrough(true); aFn(); if (thread->IsMainThread()) {
--- a/toolkit/recordreplay/File.cpp +++ b/toolkit/recordreplay/File.cpp @@ -42,23 +42,17 @@ Stream::ReadBytes(void* aData, size_t aS totalRead += bufRead; aSize -= bufRead; if (!aSize) { return; } MOZ_RELEASE_ASSERT(mBufferPos == mBufferLength); - - // If we try to read off the end of a stream then we must have hit the end - // of the replay for this thread. - while (mChunkIndex == mChunks.length()) { - MOZ_RELEASE_ASSERT(mName == StreamName::Event); - HitEndOfRecording(); - } + MOZ_RELEASE_ASSERT(mChunkIndex < mChunks.length()); const StreamChunkLocation& chunk = mChunks[mChunkIndex++]; EnsureMemory(&mBallast, &mBallastSize, chunk.mCompressedSize, BallastMaxSize(), DontCopyExistingData); mFile->ReadChunk(mBallast.get(), chunk); EnsureMemory(&mBuffer, &mBufferSize, chunk.mDecompressedSize, BUFFER_MAX, @@ -84,16 +78,17 @@ Stream::AtEnd() return mBufferPos == mBufferLength && mChunkIndex == mChunks.length(); } void Stream::WriteBytes(const void* aData, size_t aSize) { MOZ_RELEASE_ASSERT(mFile->OpenForWriting()); + MOZ_RELEASE_ASSERT(mName != StreamName::Event || mInRecordingEventSection); // Prevent the entire file from being flushed while we write this data. AutoReadSpinLock streamLock(mFile->mStreamLock); while (true) { // Fill up the data buffer first. MOZ_RELEASE_ASSERT(mBufferPos <= mBufferSize); size_t bufAvailable = mBufferSize - mBufferPos;
--- a/toolkit/recordreplay/File.h +++ b/toolkit/recordreplay/File.h @@ -55,20 +55,22 @@ 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 @@ -112,32 +114,36 @@ class Stream // 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); @@ -199,16 +205,17 @@ 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;
--- a/toolkit/recordreplay/Lock.cpp +++ b/toolkit/recordreplay/Lock.cpp @@ -1,18 +1,16 @@ /* -*- 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 "Lock.h" -#include "mozilla/StaticMutex.h" - #include "ChunkAllocator.h" #include "InfallibleVector.h" #include "SpinLock.h" #include "Thread.h" #include <unordered_map> namespace mozilla { @@ -64,23 +62,22 @@ static ChunkAllocator<LockAcquires> gLoc typedef std::unordered_map<void*, Lock*> LockMap; static LockMap* gLocks; static ReadWriteSpinLock gLocksLock; /* static */ void Lock::New(void* aNativeLock) { Thread* thread = Thread::Current(); - if (!thread || thread->PassThroughEvents() || HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { Destroy(aNativeLock); // Clean up any old lock, as below. return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); - thread->Events().RecordOrReplayThreadEvent(ThreadEvent::CreateLock); size_t id; if (IsRecording()) { id = gNumLocks++; } thread->Events().RecordOrReplayScalar(&id); @@ -147,55 +144,59 @@ Lock::Find(void* aNativeLock) return nullptr; } void Lock::Enter() { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + return; + } // Include an event in each thread's record when a lock acquire begins. This // is not required by the replay but is used to check that lock acquire order // is consistent with the recording and that we will fail explicitly instead // of deadlocking. thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Lock); thread->Events().CheckInput(mId); LockAcquires* acquires = gLockAcquires.Get(mId); if (IsRecording()) { acquires->mAcquires->WriteScalar(thread->Id()); } else { - // Wait until this thread is next in line to acquire the lock. - while (thread->Id() != acquires->mNextOwner) { + // Wait until this thread is next in line to acquire the lock, or until it + // has been instructed to diverge from the recording. + while (thread->Id() != acquires->mNextOwner && !thread->MaybeDivergeFromRecording()) { Thread::Wait(); } } } void Lock::Exit() { - if (IsReplaying()) { + Thread* thread = Thread::Current(); + if (IsReplaying() && !thread->HasDivergedFromRecording()) { // Notify the next owner before releasing the lock. LockAcquires* acquires = gLockAcquires.Get(mId); - acquires->ReadAndNotifyNextOwner(Thread::Current()); + acquires->ReadAndNotifyNextOwner(thread); } } struct AtomicLock : public detail::MutexImpl { using detail::MutexImpl::lock; using detail::MutexImpl::unlock; }; -// Lock which is held during code sections that run atomically. This is a -// PRLock instead of an OffTheBooksMutex because the latter performs atomic -// operations during initialization. +// Lock which is held during code sections that run atomically. static AtomicLock* gAtomicLock = nullptr; /* static */ void Lock::InitializeLocks() { gNumLocks = gAtomicLockId; gAtomicLock = new AtomicLock();
--- a/toolkit/recordreplay/ProcessRecordReplay.cpp +++ b/toolkit/recordreplay/ProcessRecordReplay.cpp @@ -161,37 +161,35 @@ RecordReplayInterface_Initialize(int aAr gInitialized = true; } MOZ_EXPORT size_t RecordReplayInterface_InternalRecordReplayValue(size_t aValue) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return aValue; } - EnsureNotDivergedFromRecording(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Value); thread->Events().RecordOrReplayValue(&aValue); return aValue; } MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayBytes(void* aData, size_t aSize) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - EnsureNotDivergedFromRecording(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Bytes); thread->Events().CheckInput(aSize); thread->Events().RecordOrReplayBytes(aData, aSize); } MOZ_EXPORT void RecordReplayInterface_InternalInvalidateRecording(const char* aWhy) { @@ -342,37 +340,37 @@ GetRecordingPid() /////////////////////////////////////////////////////////////////////////////// extern "C" { MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssert(const char* aFormat, va_list aArgs) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); // Add the asserted string to the recording. char text[1024]; VsprintfLiteral(text, aFormat, aArgs); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::Assert); thread->Events().CheckInput(text); } MOZ_EXPORT void RecordReplayInterface_InternalRecordReplayAssertBytes(const void* aData, size_t aSize) { Thread* thread = Thread::Current(); - if (thread->PassThroughEvents() || thread->HasDivergedFromRecording()) { + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { return; } - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); thread->Events().RecordOrReplayThreadEvent(ThreadEvent::AssertBytes); thread->Events().CheckInput(aData, aSize); } } // extern "C" static ValueIndex* gGenericThings;
--- a/toolkit/recordreplay/ProcessRedirect.h +++ b/toolkit/recordreplay/ProcessRedirect.h @@ -521,25 +521,16 @@ RR_WriteBufferViaRval(Stream& aEvents, C auto& count = aArguments->Arg<CountArg, size_t>(); aEvents.CheckInput(count); auto& rval = aArguments->Rval<size_t>(); MOZ_RELEASE_ASSERT(rval + Offset <= count); aEvents.RecordOrReplayBytes(buf, rval + Offset); } -// Insert an atomic access while recording/replaying so that calls to this -// function replay in the same order they occurred in while recording. This is -// used for functions that are used in inter-thread synchronization. -static inline void -RR_OrderCall(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) -{ - AutoOrderedAtomicAccess(); -} - // Record/replay a scalar return value. static inline void RR_ScalarRval(Stream& aEvents, CallArguments* aArguments, ErrorType* aError) { aEvents.RecordOrReplayValue(&aArguments->Rval<size_t>()); } // Record/replay a complex scalar return value that fits in two registers.
--- a/toolkit/recordreplay/Thread.cpp +++ b/toolkit/recordreplay/Thread.cpp @@ -229,20 +229,22 @@ Thread::SpawnThread(Thread* aThread) { DirectSpawnThread(ThreadMain, aThread); WaitUntilInitialized(aThread); } /* static */ NativeThreadId Thread::StartThread(Callback aStart, void* aArgument, bool aNeedsJoin) { - EnsureNotDivergedFromRecording(); - Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + EnsureNotDivergedFromRecording(); + Unreachable(); + } MonitorAutoLock lock(*gMonitor); size_t id = 0; if (IsRecording()) { // Look for an idle thread. for (id = MainThreadId + 1; id <= MaxRecordedThreadId; id++) { Thread* targetThread = Thread::GetById(id);
--- a/toolkit/recordreplay/Thread.h +++ b/toolkit/recordreplay/Thread.h @@ -93,16 +93,20 @@ private: // Whether to crash if we try to record/replay thread events. This is only // used by the associated thread. size_t mDisallowEvents; // Whether execution has diverged from the recording and the thread's // recorded events cannot be accessed. bool mDivergedFromRecording; + // Whether this thread should diverge from the recording at the next + // opportunity. This can be set from any thread. + Atomic<bool, SequentiallyConsistent, Behavior::DontPreserve> mShouldDivergeFromRecording; + // Start routine and argument which the thread is currently executing. This // is cleared after the routine finishes and another start routine may be // assigned to the thread. mNeedsJoin specifies whether the thread must be // joined before it is completely dead and can be reused. This is protected // by the thread monitor. Callback mStart; void* mStartArg; bool mNeedsJoin; @@ -174,16 +178,36 @@ public: // flag is clear. void DivergeFromRecording() { mDivergedFromRecording = true; } bool HasDivergedFromRecording() const { return mDivergedFromRecording; } + // Mark this thread as needing to diverge from the recording soon, and wake + // it up in case it can make progress now. The mShouldDivergeFromRecording + // flag is separate from mDivergedFromRecording so that the thread can only + // begin diverging from the recording at calls to MaybeDivergeFromRecording. + void SetShouldDivergeFromRecording() { + MOZ_RELEASE_ASSERT(CurrentIsMainThread()); + mShouldDivergeFromRecording = true; + Notify(mId); + } + bool WillDivergeFromRecordingSoon() { + MOZ_RELEASE_ASSERT(CurrentIsMainThread()); + return mShouldDivergeFromRecording; + } + bool MaybeDivergeFromRecording() { + if (mShouldDivergeFromRecording) { + mDivergedFromRecording = true; + } + return mDivergedFromRecording; + } + // Return whether this thread may read or write to its recorded event stream. bool CanAccessRecording() const { return !PassThroughEvents() && !AreEventsDisallowed() && !HasDivergedFromRecording(); } // The actual start routine at the root of all recorded threads, and of all // threads when replaying. static void ThreadMain(void* aArgument); @@ -285,12 +309,66 @@ public: ~AutoEnsurePassThroughThreadEventsUseStackPointer() { if (!mPassedThrough) { mThread->SetPassThrough(false); } } }; +// Mark a region of code where a thread's event stream can be accessed. +// This class has several properties: +// +// - When recording, all writes to the thread's event stream occur atomically +// within the class: the end of the stream cannot be hit at an intermediate +// point. +// +// - When replaying, this checks for the end of the stream, and blocks the +// thread if necessary. +// +// - When replaying, this is a point where the thread can begin diverging from +// the recording. Checks for divergence should occur after the constructor +// finishes. +class MOZ_RAII RecordingEventSection +{ + Thread* mThread; + +public: + explicit RecordingEventSection(Thread* aThread) + : mThread(aThread) + { + if (!aThread || !aThread->CanAccessRecording()) { + return; + } + if (IsRecording()) { + MOZ_RELEASE_ASSERT(!aThread->Events().mInRecordingEventSection); + aThread->Events().mFile->mStreamLock.ReadLock(); + aThread->Events().mInRecordingEventSection = true; + } else { + while (!aThread->MaybeDivergeFromRecording() && aThread->Events().AtEnd()) { + HitEndOfRecording(); + } + } + } + + ~RecordingEventSection() { + if (!mThread || !mThread->CanAccessRecording()) { + return; + } + if (IsRecording()) { + mThread->Events().mFile->mStreamLock.ReadUnlock(); + mThread->Events().mInRecordingEventSection = false; + } + } + + bool CanAccessEvents() { + if (!mThread || mThread->PassThroughEvents() || mThread->HasDivergedFromRecording()) { + return false; + } + MOZ_RELEASE_ASSERT(mThread->CanAccessRecording()); + return true; + } +}; + } // namespace recordreplay } // namespace mozilla #endif // mozilla_recordreplay_Thread_h
--- a/toolkit/recordreplay/Trigger.cpp +++ b/toolkit/recordreplay/Trigger.cpp @@ -54,16 +54,17 @@ InitializeTriggers() } extern "C" { MOZ_EXPORT void RecordReplayInterface_RegisterTrigger(void* aObj, const std::function<void()>& aCallback) { MOZ_RELEASE_ASSERT(aObj); + MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough()); Thread* thread = Thread::Current(); if (thread->HasDivergedFromRecording()) { return; } MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); size_t id; @@ -79,16 +80,19 @@ RecordReplayInterface_RegisterTrigger(vo iter->second.mRegisterCount++; } else { id = gTriggers->Insert(aObj); TriggerInfo info(thread->Id(), aCallback); gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info)); } } + RecordingEventSection res(thread); + MOZ_RELEASE_ASSERT(res.CanAccessEvents()); + thread->Events().RecordOrReplayThreadEvent(ThreadEvent::RegisterTrigger); thread->Events().CheckInput(id); } MOZ_EXPORT void RecordReplayInterface_UnregisterTrigger(void* aObj) { MOZ_ASSERT(IsRecordingOrReplaying()); @@ -152,17 +156,20 @@ RemoveTriggerCallbackForThreadId(size_t } return Nothing(); } MOZ_EXPORT void RecordReplayInterface_ExecuteTriggers() { Thread* thread = Thread::Current(); - MOZ_RELEASE_ASSERT(thread->CanAccessRecording()); + RecordingEventSection res(thread); + if (!res.CanAccessEvents()) { + return; + } if (IsRecording()) { // Invoke the callbacks for any triggers waiting for execution, including // any whose callbacks are triggered by earlier callback invocations. while (true) { Maybe<size_t> id = RemoveTriggerCallbackForThreadId(thread->Id()); if (id.isNothing()) { break;