toolkit/recordreplay/Trigger.cpp
author Daosheng Mu <daoshengmu@gmail.com>
Thu, 06 Sep 2018 20:17:26 +0000
changeset 435154 409f3966645a651c8cb8d6d1e2f3fa995b69c739
parent 431343 305a6de8a4579c3a2546d693a927f143e4c4e906
child 435271 37ebf94c745214cdb0c44891e5cb0c8d9fcf2d89
permissions -rw-r--r--
Bug 1489022 - Avoid be overwritten when WEBGL_MSAA is in the blacklist. r=jgilbert MozReview-Commit-ID: 3HVTu2UQcFX Differential Revision: https://phabricator.services.mozilla.com/D5126

/* -*- 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 "Trigger.h"

#include "ipc/ChildIPC.h"
#include "mozilla/Maybe.h"
#include "mozilla/StaticMutex.h"
#include "mozilla/RecordReplay.h"
#include "InfallibleVector.h"
#include "ProcessRewind.h"
#include "Thread.h"
#include "ValueIndex.h"

namespace mozilla {
namespace recordreplay {

// Information about each trigger.
struct TriggerInfo
{
  // ID of the thread which registered this trigger.
  size_t mThreadId;

  // Callback to execute when the trigger is activated.
  std::function<void()> mCallback;

  // Number of times this trigger has been activated.
  size_t mRegisterCount;

  TriggerInfo(size_t aThreadId, const std::function<void()>& aCallback)
    : mThreadId(aThreadId), mCallback(aCallback), mRegisterCount(1)
  {}
};

// All registered triggers.
static ValueIndex* gTriggers;

typedef std::unordered_map<void*, TriggerInfo> TriggerInfoMap;
static TriggerInfoMap* gTriggerInfoMap;

// Triggers which have been activated. This is protected by the global lock.
static StaticInfallibleVector<size_t> gActivatedTriggers;

static StaticMutexNotRecorded gTriggersMutex;

void
InitializeTriggers()
{
  gTriggers = new ValueIndex();
  gTriggerInfoMap = new TriggerInfoMap();
}

extern "C" {

MOZ_EXPORT void
RecordReplayInterface_RegisterTrigger(void* aObj, const std::function<void()>& aCallback)
{
  MOZ_ASSERT(IsRecordingOrReplaying());
  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
  MOZ_RELEASE_ASSERT(aObj);

  if (HasDivergedFromRecording()) {
    return;
  }

  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());

  size_t threadId = Thread::Current()->Id();

  size_t id;
  {
    AutoOrderedAtomicAccess order;
    StaticMutexAutoLock lock(gTriggersMutex);

    TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
    if (iter != gTriggerInfoMap->end()) {
      id = gTriggers->GetIndex(aObj);
      MOZ_RELEASE_ASSERT(iter->second.mThreadId == threadId);
      iter->second.mCallback = aCallback;
      iter->second.mRegisterCount++;
    } else {
      id = gTriggers->Insert(aObj);
      TriggerInfo info(threadId, aCallback);
      gTriggerInfoMap->insert(TriggerInfoMap::value_type(aObj, info));
    }
  }

  RecordReplayAssert("RegisterTrigger %zu", id);
}

MOZ_EXPORT void
RecordReplayInterface_UnregisterTrigger(void* aObj)
{
  MOZ_ASSERT(IsRecordingOrReplaying());
  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());

  StaticMutexAutoLock lock(gTriggersMutex);

  TriggerInfoMap::iterator iter = gTriggerInfoMap->find(aObj);
  MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
  if (--iter->second.mRegisterCount == 0) {
    gTriggerInfoMap->erase(iter);
    gTriggers->Remove(aObj);
  }
}

MOZ_EXPORT void
RecordReplayInterface_ActivateTrigger(void* aObj)
{
  if (!IsRecording()) {
    return;
  }

  StaticMutexAutoLock lock(gTriggersMutex);

  size_t id = gTriggers->GetIndex(aObj);
  gActivatedTriggers.emplaceBack(id);
}

static void
InvokeTriggerCallback(size_t aId)
{
  void* obj;
  std::function<void()> callback;
  {
    StaticMutexAutoLock lock(gTriggersMutex);
    obj = const_cast<void*>(gTriggers->GetValue(aId));
    TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
    MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
    MOZ_RELEASE_ASSERT(iter->second.mThreadId == Thread::Current()->Id());
    MOZ_RELEASE_ASSERT(iter->second.mRegisterCount);
    MOZ_RELEASE_ASSERT(iter->second.mCallback);
    callback = iter->second.mCallback;
  }

  callback();
}

static Maybe<size_t>
RemoveTriggerCallbackForThreadId(size_t aThreadId)
{
  StaticMutexAutoLock lock(gTriggersMutex);
  for (size_t i = 0; i < gActivatedTriggers.length(); i++) {
    size_t id = gActivatedTriggers[i];
    void* obj = const_cast<void*>(gTriggers->GetValue(id));
    TriggerInfoMap::iterator iter = gTriggerInfoMap->find(obj);
    MOZ_RELEASE_ASSERT(iter != gTriggerInfoMap->end());
    if (iter->second.mThreadId == aThreadId) {
      gActivatedTriggers.erase(&gActivatedTriggers[i]);
      return Some(id);
    }
  }
  return Nothing();
}

MOZ_EXPORT void
RecordReplayInterface_ExecuteTriggers()
{
  MOZ_ASSERT(IsRecordingOrReplaying());
  MOZ_RELEASE_ASSERT(!AreThreadEventsPassedThrough());
  MOZ_RELEASE_ASSERT(!AreThreadEventsDisallowed());

  Thread* thread = Thread::Current();

  RecordReplayAssert("ExecuteTriggers");

  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;
      }

      thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteTrigger);
      thread->Events().WriteScalar(id.ref());
      InvokeTriggerCallback(id.ref());
    }
    thread->Events().RecordOrReplayThreadEvent(ThreadEvent::ExecuteTriggersFinished);
  } else {
    // Execute the same callbacks which were executed at this point while
    // recording.
    while (true) {
      ThreadEvent ev = (ThreadEvent) thread->Events().ReadScalar();
      if (ev != ThreadEvent::ExecuteTrigger) {
        if (ev != ThreadEvent::ExecuteTriggersFinished) {
          child::ReportFatalError(Nothing(), "ExecuteTrigger Mismatch");
          Unreachable();
        }
        break;
      }
      size_t id = thread->Events().ReadScalar();
      InvokeTriggerCallback(id);
    }
  }

  RecordReplayAssert("ExecuteTriggers DONE");
}

} // extern "C"

} // namespace recordreplay
} // namespace mozilla