dom/asmjscache/AsmJSCache.cpp
author Kris Maglione <maglione.k@gmail.com>
Thu, 14 Feb 2019 17:54:00 -0800
changeset 517406 7436c0f5b8b1583d20d5ea2d1d9d3b2c665bdf33
parent 511402 c0db84bf3f57f80a4911b83ab6c13525786ad69e
child 520817 d6d4cb60bb71b533714f0269dc4af54cd9506ee3
permissions -rw-r--r--
Fix botched backout (bug 1524687). r=bustage

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

#include <stdio.h>

#include "js/BuildId.h"  // JS::BuildIdCharVector
#include "js/RootingAPI.h"
#include "jsfriendapi.h"
#include "mozilla/Assertions.h"
#include "mozilla/CondVar.h"
#include "mozilla/CycleCollectedJSRuntime.h"
#include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
#include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
#include "mozilla/dom/PermissionMessageUtils.h"
#include "mozilla/dom/quota/Client.h"
#include "mozilla/dom/quota/QuotaCommon.h"
#include "mozilla/dom/quota/QuotaManager.h"
#include "mozilla/dom/quota/QuotaObject.h"
#include "mozilla/dom/quota/UsageInfo.h"
#include "mozilla/HashFunctions.h"
#include "mozilla/ipc/BackgroundChild.h"
#include "mozilla/ipc/BackgroundParent.h"
#include "mozilla/ipc/BackgroundUtils.h"
#include "mozilla/ipc/PBackgroundChild.h"
#include "mozilla/Telemetry.h"
#include "mozilla/Unused.h"
#include "nsAutoPtr.h"
#include "nsAtom.h"
#include "nsIFile.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsISimpleEnumerator.h"
#include "nsIThread.h"
#include "nsJSPrincipals.h"
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#include "prio.h"
#include "private/pprio.h"

#define ASMJSCACHE_METADATA_FILE_NAME "metadata"
#define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"

using mozilla::HashString;
using mozilla::Unused;
using mozilla::dom::quota::AssertIsOnIOThread;
using mozilla::dom::quota::DirectoryLock;
using mozilla::dom::quota::PersistenceType;
using mozilla::dom::quota::QuotaManager;
using mozilla::dom::quota::QuotaObject;
using mozilla::dom::quota::UsageInfo;
using mozilla::ipc::AssertIsOnBackgroundThread;
using mozilla::ipc::BackgroundChild;
using mozilla::ipc::IsOnBackgroundThread;
using mozilla::ipc::PBackgroundChild;
using mozilla::ipc::PrincipalInfo;

namespace mozilla {

MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc,
                                          PR_Close);

namespace dom {
namespace asmjscache {

namespace {

class ParentRunnable;

// Anything smaller should compile fast enough that caching will just add
// overhead.
static const size_t sMinCachedModuleLength = 10000;

// The number of characters to hash into the Metadata::Entry::mFastHash.
static const unsigned sNumFastHashChars = 4096;

// Track all live parent actors.
typedef nsTArray<const ParentRunnable*> ParentActorArray;
StaticAutoPtr<ParentActorArray> sLiveParentActors;

nsresult WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata) {
  int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;

  JS::BuildIdCharVector buildId;
  bool ok = GetBuildId(&buildId);
  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);

  ScopedPRFileDesc fd;
  nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
  NS_ENSURE_SUCCESS(rv, rv);

  uint32_t length = buildId.length();
  int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
  NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);

  bytesWritten = PR_Write(fd, buildId.begin(), length);
  NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);

  bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
  NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);

  return NS_OK;
}

nsresult ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata) {
  int32_t openFlags = PR_RDONLY;

  ScopedPRFileDesc fd;
  nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
  NS_ENSURE_SUCCESS(rv, rv);

  // Read the buildid and check that it matches the current buildid

  JS::BuildIdCharVector currentBuildId;
  bool ok = GetBuildId(&currentBuildId);
  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);

  uint32_t length;
  int32_t bytesRead = PR_Read(fd, &length, sizeof(length));
  NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED);

  NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED);

  JS::BuildIdCharVector fileBuildId;
  ok = fileBuildId.resize(length);
  NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);

  bytesRead = PR_Read(fd, fileBuildId.begin(), length);
  NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED);

  for (uint32_t i = 0; i < length; i++) {
    if (currentBuildId[i] != fileBuildId[i]) {
      return NS_ERROR_FAILURE;
    }
  }

  // Read the Metadata struct

  bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata));
  NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED);

  return NS_OK;
}

nsresult GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex,
                      nsIFile** aCacheFile) {
  nsCOMPtr<nsIFile> cacheFile;
  nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
  NS_ENSURE_SUCCESS(rv, rv);

  nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
  cacheFileName.AppendInt(aModuleIndex);
  rv = cacheFile->Append(cacheFileName);
  NS_ENSURE_SUCCESS(rv, rv);

  cacheFile.forget(aCacheFile);
  return NS_OK;
}

class AutoDecreaseUsageForOrigin {
  const nsACString& mGroup;
  const nsACString& mOrigin;

 public:
  uint64_t mFreed;

  AutoDecreaseUsageForOrigin(const nsACString& aGroup,
                             const nsACString& aOrigin)

      : mGroup(aGroup), mOrigin(aOrigin), mFreed(0) {}

  ~AutoDecreaseUsageForOrigin() {
    AssertIsOnIOThread();

    if (!mFreed) {
      return;
    }

    QuotaManager* qm = QuotaManager::Get();
    MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");

    qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
                               mOrigin, mFreed);
  }
};

static void EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
                         const nsACString& aOrigin, uint64_t aNumBytes,
                         Metadata& aMetadata) {
  AssertIsOnIOThread();

  AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);

  for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
    Metadata::Entry& entry = aMetadata.mEntries[i];
    unsigned moduleIndex = entry.mModuleIndex;

    nsCOMPtr<nsIFile> file;
    nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }

    bool exists;
    rv = file->Exists(&exists);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return;
    }

    if (exists) {
      int64_t fileSize;
      rv = file->GetFileSize(&fileSize);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return;
      }

      rv = file->Remove(false);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        return;
      }

      usage.mFreed += fileSize;
    }

    entry.clear();
  }
}

/*******************************************************************************
 * Client
 ******************************************************************************/

class Client : public quota::Client {
  static Client* sInstance;

  bool mShutdownRequested;

 public:
  Client();

  static bool IsShuttingDownOnBackgroundThread() {
    AssertIsOnBackgroundThread();

    if (sInstance) {
      return sInstance->IsShuttingDown();
    }

    return QuotaManager::IsShuttingDown();
  }

  static bool IsShuttingDownOnNonBackgroundThread() {
    MOZ_ASSERT(!IsOnBackgroundThread());

    return QuotaManager::IsShuttingDown();
  }

  bool IsShuttingDown() const {
    AssertIsOnBackgroundThread();

    return mShutdownRequested;
  }

  NS_INLINE_DECL_REFCOUNTING(Client, override)

  Type GetType() override;

  nsresult InitOrigin(PersistenceType aPersistenceType,
                      const nsACString& aGroup, const nsACString& aOrigin,
                      const AtomicBool& aCanceled,
                      UsageInfo* aUsageInfo) override;

  nsresult GetUsageForOrigin(PersistenceType aPersistenceType,
                             const nsACString& aGroup,
                             const nsACString& aOrigin,
                             const AtomicBool& aCanceled,
                             UsageInfo* aUsageInfo) override;

  void OnOriginClearCompleted(PersistenceType aPersistenceType,
                              const nsACString& aOrigin) override;

  void ReleaseIOThreadObjects() override;

  void AbortOperations(const nsACString& aOrigin) override;

  void AbortOperationsForProcess(ContentParentId aContentParentId) override;

  void StartIdleMaintenance() override;

  void StopIdleMaintenance() override;

  void ShutdownWorkThreads() override;

 private:
  ~Client() override;

  nsresult GetUsageForOriginInternal(PersistenceType aPersistenceType,
                                     const nsACString& aGroup,
                                     const nsACString& aOrigin,
                                     const AtomicBool& aCanceled,
                                     UsageInfo* aUsageInfo,
                                     const bool aInitializing);
};

// FileDescriptorHolder owns a file descriptor and its memory mapping.
// FileDescriptorHolder is derived by two runnable classes (that is,
// (Parent|Child)Runnable.
class FileDescriptorHolder : public Runnable {
 public:
  FileDescriptorHolder()
      : Runnable("dom::asmjscache::FileDescriptorHolder"),
        mQuotaObject(nullptr),
        mFileSize(INT64_MIN),
        mFileDesc(nullptr),
        mFileMap(nullptr),
        mMappedMemory(nullptr) {}

  ~FileDescriptorHolder() override {
    // These resources should have already been released by Finish().
    MOZ_ASSERT(!mQuotaObject);
    MOZ_ASSERT(!mMappedMemory);
    MOZ_ASSERT(!mFileMap);
    MOZ_ASSERT(!mFileDesc);
  }

  size_t FileSize() const {
    MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
    return mFileSize;
  }

  PRFileDesc* FileDesc() const {
    MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
    return mFileDesc;
  }

  bool MapMemory(OpenMode aOpenMode) {
    MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");

    PRFileMapProtect mapFlags =
        aOpenMode == eOpenForRead ? PR_PROT_READONLY : PR_PROT_READWRITE;

    mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
    NS_ENSURE_TRUE(mFileMap, false);

    mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
    NS_ENSURE_TRUE(mMappedMemory, false);

    return true;
  }

  void* MappedMemory() const {
    MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
    return mMappedMemory;
  }

 protected:
  // This method must be called before the directory lock is released (the lock
  // is protecting these resources). It is idempotent, so it is ok to call
  // multiple times (or before the file has been fully opened).
  void Finish() {
    if (mMappedMemory) {
      PR_MemUnmap(mMappedMemory, mFileSize);
      mMappedMemory = nullptr;
    }
    if (mFileMap) {
      PR_CloseFileMap(mFileMap);
      mFileMap = nullptr;
    }
    if (mFileDesc) {
      PR_Close(mFileDesc);
      mFileDesc = nullptr;
    }

    // Holding the QuotaObject alive until all the cache files are closed
    // enables assertions in QuotaManager that the cache entry isn't cleared
    // while we are working on it.
    mQuotaObject = nullptr;
  }

  RefPtr<QuotaObject> mQuotaObject;
  int64_t mFileSize;
  PRFileDesc* mFileDesc;
  PRFileMap* mFileMap;
  void* mMappedMemory;
};

// A runnable that implements a state machine required to open a cache entry.
// It executes in the parent for a cache access originating in the child.
// This runnable gets registered as an IPDL subprotocol actor so that it
// can communicate with the corresponding ChildRunnable.
class ParentRunnable final : public FileDescriptorHolder,
                             public quota::OpenDirectoryListener,
                             public PAsmJSCacheEntryParent {
 public:
  // We need to always declare refcounting because
  // OpenDirectoryListener has pure-virtual refcounting.
  NS_DECL_ISUPPORTS_INHERITED
  NS_DECL_NSIRUNNABLE

  ParentRunnable(const PrincipalInfo& aPrincipalInfo, OpenMode aOpenMode,
                 const WriteParams& aWriteParams)
      : mOwningEventTarget(GetCurrentThreadEventTarget()),
        mPrincipalInfo(aPrincipalInfo),
        mOpenMode(aOpenMode),
        mWriteParams(aWriteParams),
        mOperationMayProceed(true),
        mModuleIndex(0),
        mState(eInitial),
        mResult(JS::AsmJSCache_InternalError),
        mActorDestroyed(false),
        mOpened(false) {
    MOZ_ASSERT(XRE_IsParentProcess());
    AssertIsOnOwningThread();
  }

 private:
  ~ParentRunnable() override {
    MOZ_ASSERT(mState == eFinished);
    MOZ_ASSERT(!mDirectoryLock);
    MOZ_ASSERT(mActorDestroyed);
  }

#ifdef DEBUG
  bool IsOnOwningThread() const {
    MOZ_ASSERT(mOwningEventTarget);

    bool current;
    return NS_SUCCEEDED(mOwningEventTarget->IsOnCurrentThread(&current)) &&
           current;
  }
#endif

  void AssertIsOnOwningThread() const {
    MOZ_ASSERT(IsOnBackgroundThread());
    MOZ_ASSERT(IsOnOwningThread());
  }

  void AssertIsOnNonOwningThread() const {
    MOZ_ASSERT(!IsOnBackgroundThread());
    MOZ_ASSERT(!IsOnOwningThread());
  }

  bool IsActorDestroyed() const {
    AssertIsOnOwningThread();

    return mActorDestroyed;
  }

  // May be called on any thread, but you should call IsActorDestroyed() if
  // you know you're on the background thread because it is slightly faster.
  bool OperationMayProceed() const { return mOperationMayProceed; }

  // This method is called on the owning thread when the JS engine is finished
  // reading/writing the cache entry.
  void Close() {
    AssertIsOnOwningThread();
    MOZ_ASSERT(mState == eOpened);
    MOZ_ASSERT(mResult == JS::AsmJSCache_Success);

    mState = eFinished;

    MOZ_ASSERT(mOpened);
    mOpened = false;

    FinishOnOwningThread();

    if (!mActorDestroyed) {
      Unused << Send__delete__(this, mResult);
    }
  }

  // This method is called upon any failure that prevents the eventual opening
  // of the cache entry.
  void Fail() {
    AssertIsOnOwningThread();
    MOZ_ASSERT(mState != eFinished);
    MOZ_ASSERT(mResult != JS::AsmJSCache_Success);

    mState = eFinished;

    MOZ_ASSERT(!mOpened);

    FinishOnOwningThread();

    if (!mActorDestroyed) {
      Unused << Send__delete__(this, mResult);
    }
  }

  // The same as method above but is intended to be called off the owning
  // thread.
  void FailOnNonOwningThread() {
    AssertIsOnNonOwningThread();
    MOZ_ASSERT(mState != eOpened && mState != eFailing && mState != eFinished);

    mState = eFailing;
    MOZ_ALWAYS_SUCCEEDS(mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
  }

  nsresult InitOnMainThread();

  void OpenDirectory();

  nsresult ReadMetadata();

  nsresult OpenCacheFileForWrite();

  nsresult OpenCacheFileForRead();

  void FinishOnOwningThread();

  void DispatchToIOThread() {
    AssertIsOnOwningThread();

    if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
        IsActorDestroyed()) {
      Fail();
      return;
    }

    QuotaManager* qm = QuotaManager::Get();
    MOZ_ASSERT(qm);

    nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
    if (NS_FAILED(rv)) {
      Fail();
      return;
    }
  }

  // OpenDirectoryListener overrides.
  void DirectoryLockAcquired(DirectoryLock* aLock) override;

  void DirectoryLockFailed() override;

  // IPDL methods.
  void ActorDestroy(ActorDestroyReason why) override {
    AssertIsOnOwningThread();
    MOZ_ASSERT(!mActorDestroyed);
    MOZ_ASSERT(mOperationMayProceed);

    mActorDestroyed = true;
    mOperationMayProceed = false;

    // Assume ActorDestroy can happen at any time, so we can't probe the
    // current state since mState can be modified on any thread (only one
    // thread at a time based on the state machine).
    // However we can use mOpened which is only touched on the owning thread.
    // If mOpened is true, we can also modify mState since we are guaranteed
    // that there are no pending runnables which would probe mState to decide
    // what code needs to run (there shouldn't be any running runnables on
    // other threads either).

    if (mOpened) {
      Close();

      MOZ_ASSERT(mState == eFinished);
    }

    // We don't have to call Fail() if mOpened is not true since it means that
    // either nothing has been initialized yet, so nothing to cleanup or there
    // are pending runnables that will detect that the actor has been destroyed
    // and call Fail().
  }

  mozilla::ipc::IPCResult RecvSelectCacheFileToRead(
      const OpenMetadataForReadResponse& aResponse) override {
    AssertIsOnOwningThread();
    MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
    MOZ_ASSERT(mOpenMode == eOpenForRead);
    MOZ_ASSERT(!mOpened);

    if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) {
      Fail();
      return IPC_OK();
    }

    switch (aResponse.type()) {
      case OpenMetadataForReadResponse::TAsmJSCacheResult: {
        MOZ_ASSERT(aResponse.get_AsmJSCacheResult() != JS::AsmJSCache_Success);

        mResult = aResponse.get_AsmJSCacheResult();

        // This ParentRunnable can only be held alive by the IPDL. Fail()
        // clears that last reference. So we need to add a self reference here.
        RefPtr<ParentRunnable> kungFuDeathGrip = this;

        Fail();

        break;
      }

      case OpenMetadataForReadResponse::Tuint32_t:
        // A cache entry has been selected to open.
        mModuleIndex = aResponse.get_uint32_t();

        mState = eReadyToOpenCacheFileForRead;

        DispatchToIOThread();

        break;

      default:
        MOZ_CRASH("Should never get here!");
    }

    return IPC_OK();
  }

  mozilla::ipc::IPCResult RecvClose() override {
    AssertIsOnOwningThread();
    MOZ_ASSERT(mState == eOpened);

    // This ParentRunnable can only be held alive by the IPDL. Close() clears
    // that last reference. So we need to add a self reference here.
    RefPtr<ParentRunnable> kungFuDeathGrip = this;

    Close();

    MOZ_ASSERT(mState == eFinished);

    return IPC_OK();
  }

  nsCOMPtr<nsIEventTarget> mOwningEventTarget;
  const PrincipalInfo mPrincipalInfo;
  const OpenMode mOpenMode;
  const WriteParams mWriteParams;

  // State initialized during eInitial:
  nsCString mSuffix;
  nsCString mGroup;
  nsCString mOrigin;
  RefPtr<DirectoryLock> mDirectoryLock;

  // State initialized during eReadyToReadMetadata
  nsCOMPtr<nsIFile> mDirectory;
  nsCOMPtr<nsIFile> mMetadataFile;
  Metadata mMetadata;

  Atomic<bool> mOperationMayProceed;

  // State initialized during eWaitingToOpenCacheFileForRead
  unsigned mModuleIndex;

  enum State {
    eInitial,  // Just created, waiting to be dispatched to main thread
    eWaitingToFinishInit,     // Waiting to finish initialization
    eWaitingToOpenDirectory,  // Waiting to open directory
    eWaitingToOpenMetadata,   // Waiting to be called back from OpenDirectory
    eReadyToReadMetadata,  // Waiting to read the metadata file on the IO thread
    eSendingMetadataForRead,         // Waiting to send OnOpenMetadataForRead
    eWaitingToOpenCacheFileForRead,  // Waiting to hear back from child
    eReadyToOpenCacheFileForRead,    // Waiting to open cache file for read
    eSendingCacheFile,  // Waiting to send OnOpenCacheFile on the owning thread
    eOpened,    // Finished calling OnOpenCacheFile, waiting to be closed
    eFailing,   // Just failed, waiting to be dispatched to the owning thread
    eFinished,  // Terminal state
  };
  State mState;
  JS::AsmJSCacheResult mResult;

  bool mActorDestroyed;
  bool mOpened;
};

nsresult ParentRunnable::InitOnMainThread() {
  MOZ_ASSERT(NS_IsMainThread());
  MOZ_ASSERT(mState == eInitial);
  MOZ_ASSERT(mPrincipalInfo.type() != PrincipalInfo::TNullPrincipalInfo);

  nsresult rv;
  nsCOMPtr<nsIPrincipal> principal =
      PrincipalInfoToPrincipal(mPrincipalInfo, &rv);
  if (NS_WARN_IF(NS_FAILED(rv))) {
    return rv;
  }

  rv = QuotaManager::GetInfoFromPrincipal(principal, &mSuffix, &mGroup,
                                          &mOrigin);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

void ParentRunnable::OpenDirectory() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mState == eWaitingToFinishInit ||
             mState == eWaitingToOpenDirectory);
  MOZ_ASSERT(QuotaManager::Get());

  mState = eWaitingToOpenMetadata;

  // XXX The exclusive lock shouldn't be needed for read operations.
  QuotaManager::Get()->OpenDirectory(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
                                     mOrigin, quota::Client::ASMJS,
                                     /* aExclusive */ true, this);
}

nsresult ParentRunnable::ReadMetadata() {
  AssertIsOnIOThread();
  MOZ_ASSERT(mState == eReadyToReadMetadata);

  QuotaManager* qm = QuotaManager::Get();
  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");

  nsresult rv = qm->EnsureOriginIsInitialized(
      quota::PERSISTENCE_TYPE_TEMPORARY, mSuffix, mGroup, mOrigin,
      /* aCreateIfNotExists */ true, getter_AddRefs(mDirectory));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    mResult = JS::AsmJSCache_StorageInitFailure;
    return rv;
  }

  rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
  NS_ENSURE_SUCCESS(rv, rv);

  bool exists;
  rv = mDirectory->Exists(&exists);
  NS_ENSURE_SUCCESS(rv, rv);

  if (!exists) {
    rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
    NS_ENSURE_SUCCESS(rv, rv);
  } else {
    DebugOnly<bool> isDirectory;
    MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory)));
    MOZ_ASSERT(isDirectory, "Should have caught this earlier!");
  }

  rv = mDirectory->Clone(getter_AddRefs(mMetadataFile));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME));
  NS_ENSURE_SUCCESS(rv, rv);

  rv = mMetadataFile->Exists(&exists);
  NS_ENSURE_SUCCESS(rv, rv);

  if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) {
    exists = false;
  }

  if (!exists) {
    // If we are reading, we can't possibly have a cache hit.
    if (mOpenMode == eOpenForRead) {
      return NS_ERROR_FILE_NOT_FOUND;
    }

    // Initialize Metadata with a valid empty state for the LRU cache.
    for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
      Metadata::Entry& entry = mMetadata.mEntries[i];
      entry.mModuleIndex = i;
      entry.clear();
    }
  }

  return NS_OK;
}

nsresult ParentRunnable::OpenCacheFileForWrite() {
  AssertIsOnIOThread();
  MOZ_ASSERT(mState == eReadyToReadMetadata);
  MOZ_ASSERT(mOpenMode == eOpenForWrite);

  mFileSize = mWriteParams.mSize;

  // Kick out the oldest entry in the LRU queue in the metadata.
  mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;

  nsCOMPtr<nsIFile> file;
  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, rv);

  QuotaManager* qm = QuotaManager::Get();
  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");

  // Create the QuotaObject before all file IO and keep it alive until caching
  // completes to get maximum assertion coverage in QuotaManager against
  // concurrent removal, etc.
  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
                                    mOrigin, file);
  NS_ENSURE_STATE(mQuotaObject);

  if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
                                     /* aTruncate */ false)) {
    // If the request fails, it might be because mOrigin is using too much
    // space (MaybeUpdateSize will not evict our own origin since it is
    // active). Try to make some space by evicting LRU entries until there is
    // enough space.
    EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
    if (!mQuotaObject->MaybeUpdateSize(mWriteParams.mSize,
                                       /* aTruncate */ false)) {
      mResult = JS::AsmJSCache_QuotaExceeded;
      return NS_ERROR_FAILURE;
    }
  }

  int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
  NS_ENSURE_SUCCESS(rv, rv);

  // Move the mModuleIndex's LRU entry to the recent end of the queue.
  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
  Metadata::Entry& entry = mMetadata.mEntries[0];
  entry.mFastHash = mWriteParams.mFastHash;
  entry.mNumChars = mWriteParams.mNumChars;
  entry.mFullHash = mWriteParams.mFullHash;
  entry.mModuleIndex = mModuleIndex;

  rv = WriteMetadataFile(mMetadataFile, mMetadata);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

nsresult ParentRunnable::OpenCacheFileForRead() {
  AssertIsOnIOThread();
  MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
  MOZ_ASSERT(mOpenMode == eOpenForRead);

  nsCOMPtr<nsIFile> file;
  nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
  NS_ENSURE_SUCCESS(rv, rv);

  QuotaManager* qm = QuotaManager::Get();
  MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");

  // Even though it's not strictly necessary, create the QuotaObject before all
  // file IO and keep it alive until caching completes to get maximum assertion
  // coverage in QuotaManager against concurrent removal, etc.
  mQuotaObject = qm->GetQuotaObject(quota::PERSISTENCE_TYPE_TEMPORARY, mGroup,
                                    mOrigin, file);
  NS_ENSURE_STATE(mQuotaObject);

  rv = file->GetFileSize(&mFileSize);
  NS_ENSURE_SUCCESS(rv, rv);

  int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
  rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
  NS_ENSURE_SUCCESS(rv, rv);

  // Move the mModuleIndex's LRU entry to the recent end of the queue.
  unsigned lruIndex = 0;
  while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
    if (++lruIndex == Metadata::kNumEntries) {
      return NS_ERROR_UNEXPECTED;
    }
  }
  Metadata::Entry entry = mMetadata.mEntries[lruIndex];
  PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
  mMetadata.mEntries[0] = entry;

  rv = WriteMetadataFile(mMetadataFile, mMetadata);
  NS_ENSURE_SUCCESS(rv, rv);

  return NS_OK;
}

void ParentRunnable::FinishOnOwningThread() {
  AssertIsOnOwningThread();

  // Per FileDescriptorHolder::Finish()'s comment, call before
  // releasing the directory lock.
  FileDescriptorHolder::Finish();

  mDirectoryLock = nullptr;

  MOZ_ASSERT(sLiveParentActors);
  sLiveParentActors->RemoveElement(this);

  if (sLiveParentActors->IsEmpty()) {
    sLiveParentActors = nullptr;
  }
}

NS_IMETHODIMP
ParentRunnable::Run() {
  nsresult rv;

  // All success/failure paths must eventually call Finish() to avoid leaving
  // the parser hanging.
  switch (mState) {
    case eInitial: {
      MOZ_ASSERT(NS_IsMainThread());

      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
          !OperationMayProceed()) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      rv = InitOnMainThread();
      if (NS_FAILED(rv)) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      mState = eWaitingToFinishInit;
      MOZ_ALWAYS_SUCCEEDS(
          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));

      return NS_OK;
    }

    case eWaitingToFinishInit: {
      AssertIsOnOwningThread();

      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
          IsActorDestroyed()) {
        Fail();
        return NS_OK;
      }

      if (QuotaManager::Get()) {
        OpenDirectory();
        return NS_OK;
      }

      mState = eWaitingToOpenDirectory;
      QuotaManager::GetOrCreate(this);

      return NS_OK;
    }

    case eWaitingToOpenDirectory: {
      AssertIsOnOwningThread();

      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
          IsActorDestroyed()) {
        Fail();
        return NS_OK;
      }

      if (NS_WARN_IF(!QuotaManager::Get())) {
        Fail();
        return NS_OK;
      }

      OpenDirectory();
      return NS_OK;
    }

    case eReadyToReadMetadata: {
      AssertIsOnIOThread();

      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
          !OperationMayProceed()) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      rv = ReadMetadata();
      if (NS_FAILED(rv)) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      if (mOpenMode == eOpenForRead) {
        mState = eSendingMetadataForRead;
        MOZ_ALWAYS_SUCCEEDS(
            mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));

        return NS_OK;
      }

      rv = OpenCacheFileForWrite();
      if (NS_FAILED(rv)) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      mState = eSendingCacheFile;
      MOZ_ALWAYS_SUCCEEDS(
          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
      return NS_OK;
    }

    case eSendingMetadataForRead: {
      AssertIsOnOwningThread();
      MOZ_ASSERT(mOpenMode == eOpenForRead);

      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
          IsActorDestroyed()) {
        Fail();
        return NS_OK;
      }

      mState = eWaitingToOpenCacheFileForRead;

      // Metadata is now open.
      if (!SendOnOpenMetadataForRead(mMetadata)) {
        Fail();
        return NS_OK;
      }

      return NS_OK;
    }

    case eReadyToOpenCacheFileForRead: {
      AssertIsOnIOThread();
      MOZ_ASSERT(mOpenMode == eOpenForRead);

      if (NS_WARN_IF(Client::IsShuttingDownOnNonBackgroundThread()) ||
          !OperationMayProceed()) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      rv = OpenCacheFileForRead();
      if (NS_FAILED(rv)) {
        FailOnNonOwningThread();
        return NS_OK;
      }

      mState = eSendingCacheFile;
      MOZ_ALWAYS_SUCCEEDS(
          mOwningEventTarget->Dispatch(this, NS_DISPATCH_NORMAL));
      return NS_OK;
    }

    case eSendingCacheFile: {
      AssertIsOnOwningThread();

      if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread()) ||
          IsActorDestroyed()) {
        Fail();
        return NS_OK;
      }

      mState = eOpened;

      FileDescriptor::PlatformHandleType handle =
          FileDescriptor::PlatformHandleType(
              PR_FileDesc2NativeHandle(mFileDesc));
      if (!SendOnOpenCacheFile(mFileSize, FileDescriptor(handle))) {
        Fail();
        return NS_OK;
      }

      // The entry is now open.
      MOZ_ASSERT(!mOpened);
      mOpened = true;

      mResult = JS::AsmJSCache_Success;

      return NS_OK;
    }

    case eFailing: {
      AssertIsOnOwningThread();

      Fail();

      return NS_OK;
    }

    case eWaitingToOpenMetadata:
    case eWaitingToOpenCacheFileForRead:
    case eOpened:
    case eFinished: {
      MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
    }
  }

  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
  return NS_OK;
}

void ParentRunnable::DirectoryLockAcquired(DirectoryLock* aLock) {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
  MOZ_ASSERT(!mDirectoryLock);

  mDirectoryLock = aLock;

  mState = eReadyToReadMetadata;
  DispatchToIOThread();
}

void ParentRunnable::DirectoryLockFailed() {
  AssertIsOnOwningThread();
  MOZ_ASSERT(mState == eWaitingToOpenMetadata);
  MOZ_ASSERT(!mDirectoryLock);

  Fail();
}

NS_IMPL_ISUPPORTS_INHERITED0(ParentRunnable, FileDescriptorHolder)

bool FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
                   unsigned* aModuleIndex) {
  // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
  // also stores an mFastHash of its first sNumFastHashChars so this gives us a
  // fast way to probabilistically determine whether we have a cache hit. We
  // still do a full hash of all the chars before returning the cache file to
  // the engine to avoid penalizing the case where there are multiple cached
  // asm.js modules where the first sNumFastHashChars are the same. The
  // mFullHash of each cache entry can have a different mNumChars so the fast
  // hash allows us to avoid performing up to Metadata::kNumEntries separate
  // full hashes.
  uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
  MOZ_ASSERT(numChars > sNumFastHashChars);
  uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);

  for (auto entry : aMetadata.mEntries) {
    // Compare the "fast hash" first to see whether it is worthwhile to
    // hash all the chars.
    if (entry.mFastHash != fastHash) {
      continue;
    }

    // Assuming we have enough characters, hash all the chars it would take
    // to match this cache entry and compare to the cache entry. If we get a
    // hit we'll still do a full source match later (in the JS engine), but
    // the full hash match means this is probably the cache entry we want.
    if (numChars < entry.mNumChars) {
      continue;
    }
    uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
    if (entry.mFullHash != fullHash) {
      continue;
    }

    *aModuleIndex = entry.mModuleIndex;
    return true;
  }

  return false;
}

}  // unnamed namespace

PAsmJSCacheEntryParent* AllocEntryParent(OpenMode aOpenMode,
                                         WriteParams aWriteParams,
                                         const PrincipalInfo& aPrincipalInfo) {
  AssertIsOnBackgroundThread();

  if (NS_WARN_IF(Client::IsShuttingDownOnBackgroundThread())) {
    return nullptr;
  }

  if (NS_WARN_IF(aPrincipalInfo.type() == PrincipalInfo::TNullPrincipalInfo)) {
    MOZ_ASSERT(false);
    return nullptr;
  }

  RefPtr<ParentRunnable> runnable =
      new ParentRunnable(aPrincipalInfo, aOpenMode, aWriteParams);

  if (!sLiveParentActors) {
    sLiveParentActors = new ParentActorArray();
  }

  sLiveParentActors->AppendElement(runnable);

  nsresult rv = NS_DispatchToMainThread(runnable);
  NS_ENSURE_SUCCESS(rv, nullptr);

  // Transfer ownership to IPDL.
  return runnable.forget().take();
}

void DeallocEntryParent(PAsmJSCacheEntryParent* aActor) {
  // Transfer ownership back from IPDL.
  RefPtr<ParentRunnable> op = dont_AddRef(static_cast<ParentRunnable*>(aActor));
}

namespace {

// A runnable that presents a single interface to the AsmJSCache ops which need
// to wait until the file is open.
class ChildRunnable final : public FileDescriptorHolder,
                            public PAsmJSCacheEntryChild {
  typedef mozilla::ipc::PBackgroundChild PBackgroundChild;

 public:
  class AutoClose {
    ChildRunnable* mChildRunnable;

   public:
    explicit AutoClose(ChildRunnable* aChildRunnable = nullptr)
        : mChildRunnable(aChildRunnable) {}

    void Init(ChildRunnable* aChildRunnable) {
      MOZ_ASSERT(!mChildRunnable);
      mChildRunnable = aChildRunnable;
    }

    ChildRunnable* operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN {
      MOZ_ASSERT(mChildRunnable);
      return mChildRunnable;
    }

    void Forget(ChildRunnable** aChildRunnable) {
      *aChildRunnable = mChildRunnable;
      mChildRunnable = nullptr;
    }

    ~AutoClose() {
      if (mChildRunnable) {
        mChildRunnable->Close();
      }
    }
  };

  NS_DECL_NSIRUNNABLE

  ChildRunnable(nsIPrincipal* aPrincipal, OpenMode aOpenMode,
                const WriteParams& aWriteParams, ReadParams aReadParams)
      : mPrincipal(aPrincipal),
        mWriteParams(aWriteParams),
        mReadParams(aReadParams),
        mMutex("ChildRunnable::mMutex"),
        mCondVar(mMutex, "ChildRunnable::mCondVar"),
        mOpenMode(aOpenMode),
        mState(eInitial),
        mResult(JS::AsmJSCache_InternalError),
        mActorDestroyed(false),
        mWaiting(false),
        mOpened(false) {
    MOZ_ASSERT(!NS_IsMainThread());
  }

  JS::AsmJSCacheResult BlockUntilOpen(AutoClose* aCloser) {
    MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
    MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");

    mWaiting = true;

    nsresult rv = NS_DispatchToMainThread(this);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      return JS::AsmJSCache_InternalError;
    }

    {
      MutexAutoLock lock(mMutex);
      while (mWaiting) {
        mCondVar.Wait();
      }
    }

    if (!mOpened) {
      return mResult;
    }

    // Now that we're open, we're guaranteed a Close() call. However, we are
    // not guaranteed someone is holding an outstanding reference until the File
    // is closed, so we do that ourselves and Release() in OnClose().
    aCloser->Init(this);
    AddRef();
    return JS::AsmJSCache_Success;
  }

  void Cleanup() {
#ifdef DEBUG
    NoteActorDestroyed();
#endif
  }

 private:
  ~ChildRunnable() override {
    MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
    MOZ_ASSERT(!mOpened);
    MOZ_ASSERT(mState == eFinished);
    MOZ_ASSERT(mActorDestroyed);
  }

  // IPDL methods.
  mozilla::ipc::IPCResult RecvOnOpenMetadataForRead(
      const Metadata& aMetadata) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mState == eOpening);

    uint32_t moduleIndex;
    bool ok;
    if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
      ok = SendSelectCacheFileToRead(moduleIndex);
    } else {
      ok = SendSelectCacheFileToRead(JS::AsmJSCache_InternalError);
    }
    if (!ok) {
      return IPC_FAIL_NO_REASON(this);
    }

    return IPC_OK();
  }

  mozilla::ipc::IPCResult RecvOnOpenCacheFile(
      const int64_t& aFileSize, const FileDescriptor& aFileDesc) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mState == eOpening);

    mFileSize = aFileSize;

    auto rawFD = aFileDesc.ClonePlatformHandle();
    mFileDesc = PR_ImportFile(PROsfd(rawFD.release()));
    if (!mFileDesc) {
      return IPC_FAIL_NO_REASON(this);
    }

    mState = eOpened;
    Notify(JS::AsmJSCache_Success);
    return IPC_OK();
  }

  mozilla::ipc::IPCResult Recv__delete__(
      const JS::AsmJSCacheResult& aResult) override {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mState == eOpening || mState == eFinishing);
    MOZ_ASSERT_IF(mState == eOpening, aResult != JS::AsmJSCache_Success);
    MOZ_ASSERT_IF(mState == eFinishing, aResult == JS::AsmJSCache_Success);

    if (mState == eOpening) {
      Fail(aResult);
    } else {
      // Match the AddRef in BlockUntilOpen(). The IPDL still holds an
      // outstanding ref which will keep 'this' alive until ActorDestroy()
      // is executed.
      Release();

      mState = eFinished;
    }
    return IPC_OK();
  }

  void ActorDestroy(ActorDestroyReason why) override {
    MOZ_ASSERT(NS_IsMainThread());
    NoteActorDestroyed();
  }

  void Close() {
    MOZ_ASSERT(mState == eOpened);

    mState = eClosing;
    NS_DispatchToMainThread(this);
  }

  void Fail(JS::AsmJSCacheResult aResult) {
    MOZ_ASSERT(NS_IsMainThread());
    MOZ_ASSERT(mState == eInitial || mState == eOpening);
    MOZ_ASSERT(aResult != JS::AsmJSCache_Success);

    mState = eFinished;

    FileDescriptorHolder::Finish();
    Notify(aResult);
  }

  void Notify(JS::AsmJSCacheResult aResult) {
    MOZ_ASSERT(NS_IsMainThread());

    MutexAutoLock lock(mMutex);
    MOZ_ASSERT(mWaiting);

    mWaiting = false;
    mOpened = aResult == JS::AsmJSCache_Success;
    mResult = aResult;
    mCondVar.Notify();
  }

  void NoteActorDestroyed() { mActorDestroyed = true; }

  nsIPrincipal* const mPrincipal;
  nsAutoPtr<PrincipalInfo> mPrincipalInfo;
  WriteParams mWriteParams;
  ReadParams mReadParams;
  Mutex mMutex;
  CondVar mCondVar;

  // Couple enums and bools together
  const OpenMode mOpenMode;
  enum State {
    eInitial,  // Just created, waiting to be dispatched to the main thread
    eOpening,  // Waiting for the parent process to respond
    eOpened,   // Parent process opened the entry and sent it back
    eClosing,  // Waiting to be dispatched to the main thread to Send__delete__
    eFinishing,  // Waiting for the parent process to close
    eFinished    // Terminal state
  };
  State mState;
  JS::AsmJSCacheResult mResult;

  bool mActorDestroyed;
  bool mWaiting;
  bool mOpened;
};

NS_IMETHODIMP
ChildRunnable::Run() {
  switch (mState) {
    case eInitial: {
      MOZ_ASSERT(NS_IsMainThread());

      if (mPrincipal->GetIsNullPrincipal()) {
        NS_WARNING("AsmsJSCache not supported on null principal.");
        Fail(JS::AsmJSCache_InternalError);
        return NS_OK;
      }

      nsAutoPtr<PrincipalInfo> principalInfo(new PrincipalInfo());
      nsresult rv = PrincipalToPrincipalInfo(mPrincipal, principalInfo);
      if (NS_WARN_IF(NS_FAILED(rv))) {
        Fail(JS::AsmJSCache_InternalError);
        return NS_OK;
      }

      mPrincipalInfo = std::move(principalInfo);

      PBackgroundChild* actor = BackgroundChild::GetOrCreateForCurrentThread();
      if (NS_WARN_IF(!actor)) {
        Fail(JS::AsmJSCache_InternalError);
        return NS_OK;
      }

      if (!actor->SendPAsmJSCacheEntryConstructor(this, mOpenMode, mWriteParams,
                                                  *mPrincipalInfo)) {
        // Unblock the parsing thread with a failure.

        Fail(JS::AsmJSCache_InternalError);
        return NS_OK;
      }

      // AddRef to keep this runnable alive until IPDL deallocates the
      // subprotocol (DeallocEntryChild).
      AddRef();

      mState = eOpening;
      return NS_OK;
    }

    case eClosing: {
      MOZ_ASSERT(NS_IsMainThread());

      // Per FileDescriptorHolder::Finish()'s comment, call before
      // releasing the directory lock (which happens in the parent upon receipt
      // of the Close message).
      FileDescriptorHolder::Finish();

      MOZ_ASSERT(mOpened);
      mOpened = false;

      if (mActorDestroyed) {
        // Match the AddRef in BlockUntilOpen(). The main thread event loop
        // still holds an outstanding ref which will keep 'this' alive until
        // returning to the event loop.
        Release();

        mState = eFinished;
      } else {
        Unused << SendClose();

        mState = eFinishing;
      }

      return NS_OK;
    }

    case eOpening:
    case eOpened:
    case eFinishing:
    case eFinished: {
      MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
    }
  }

  MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
  return NS_OK;
}

}  // unnamed namespace

void DeallocEntryChild(PAsmJSCacheEntryChild* aActor) {
  // Match the AddRef before SendPAsmJSCacheEntryConstructor.
  static_cast<ChildRunnable*>(aActor)->Release();
}

namespace {

JS::AsmJSCacheResult OpenFile(nsIPrincipal* aPrincipal, OpenMode aOpenMode,
                              WriteParams aWriteParams, ReadParams aReadParams,
                              ChildRunnable::AutoClose* aChildRunnable) {
  MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
  MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);

  // There are three reasons we don't attempt caching from the main thread:
  //  1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
  //     synchronous waiting on the main thread requiring a runnable to be
  //     dispatched to the main thread.
  //  2. In the child process: the IPDL PContent messages we need to
  //     synchronously wait on are dispatched to the main thread.
  //  3. While a cache lookup *should* be much faster than compilation, IO
  //     operations can be unpredictably slow and we'd like to avoid the
  //     occasional janks on the main thread.
  // We could use a nested event loop to address 1 and 2, but we're potentially
  // in the middle of running JS (eval()) and nested event loops can be
  // semantically observable.
  if (NS_IsMainThread()) {
    return JS::AsmJSCache_SynchronousScript;
  }

  // Check to see whether the principal reflects a private browsing session.
  // Since AsmJSCache requires disk access at the moment, caching should be
  // disabled in private browsing situations. Failing here will cause later
  // read/write requests to also fail.
  uint32_t pbId;
  if (NS_WARN_IF(NS_FAILED(aPrincipal->GetPrivateBrowsingId(&pbId)))) {
    return JS::AsmJSCache_InternalError;
  }

  if (pbId > 0) {
    return JS::AsmJSCache_Disabled_PrivateBrowsing;
  }

  // We need to synchronously call into the parent to open the file and
  // interact with the QuotaManager. The child can then map the file into its
  // address space to perform I/O.
  RefPtr<ChildRunnable> childRunnable =
      new ChildRunnable(aPrincipal, aOpenMode, aWriteParams, aReadParams);

  JS::AsmJSCacheResult openResult =
      childRunnable->BlockUntilOpen(aChildRunnable);
  if (openResult != JS::AsmJSCache_Success) {
    childRunnable->Cleanup();
    return openResult;
  }

  if (!childRunnable->MapMemory(aOpenMode)) {
    return JS::AsmJSCache_InternalError;
  }

  return JS::AsmJSCache_Success;
}

}  // namespace

typedef uint32_t AsmJSCookieType;
static const uint32_t sAsmJSCookie = 0x600d600d;

bool OpenEntryForRead(nsIPrincipal* aPrincipal, const char16_t* aBegin,
                      const char16_t* aLimit, size_t* aSize,
                      const uint8_t** aMemory, intptr_t* aHandle) {
  if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
    return false;
  }

  ReadParams readParams;
  readParams.mBegin = aBegin;
  readParams.mLimit = aLimit;

  ChildRunnable::AutoClose childRunnable;
  WriteParams notAWrite;
  JS::AsmJSCacheResult openResult =
      OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &childRunnable);
  if (openResult != JS::AsmJSCache_Success) {
    return false;
  }

  // Although we trust that the stored cache files have not been arbitrarily
  // corrupted, it is possible that a previous execution aborted in the middle
  // of writing a cache file (crash, OOM-killer, etc). To protect against
  // partially-written cache files, we use the following scheme:
  //  - Allocate an extra word at the beginning of every cache file which
  //    starts out 0 (OpenFile opens with PR_TRUNCATE).
  //  - After the asm.js serialization is complete, PR_SyncMemMap to write
  //    everything to disk and then store a non-zero value (sAsmJSCookie)
  //    in the first word.
  //  - When attempting to read a cache file, check whether the first word is
  //    sAsmJSCookie.
  if (childRunnable->FileSize() < sizeof(AsmJSCookieType) ||
      *(AsmJSCookieType*)childRunnable->MappedMemory() != sAsmJSCookie) {
    return false;
  }

  *aSize = childRunnable->FileSize() - sizeof(AsmJSCookieType);
  *aMemory = (uint8_t*)childRunnable->MappedMemory() + sizeof(AsmJSCookieType);

  // The caller guarnatees a call to CloseEntryForRead (on success or
  // failure) at which point the file will be closed.
  childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle));
  return true;
}

void CloseEntryForRead(size_t aSize, const uint8_t* aMemory, intptr_t aHandle) {
  ChildRunnable::AutoClose childRunnable(
      reinterpret_cast<ChildRunnable*>(aHandle));

  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize());
  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) ==
             childRunnable->MappedMemory());
}

JS::AsmJSCacheResult OpenEntryForWrite(nsIPrincipal* aPrincipal,
                                       const char16_t* aBegin,
                                       const char16_t* aEnd, size_t aSize,
                                       uint8_t** aMemory, intptr_t* aHandle) {
  if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
    return JS::AsmJSCache_ModuleTooSmall;
  }

  // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
  aSize += sizeof(AsmJSCookieType);

  static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");

  WriteParams writeParams;
  writeParams.mSize = aSize;
  writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
  writeParams.mNumChars = aEnd - aBegin;
  writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);

  ChildRunnable::AutoClose childRunnable;
  ReadParams notARead;
  JS::AsmJSCacheResult openResult = OpenFile(
      aPrincipal, eOpenForWrite, writeParams, notARead, &childRunnable);
  if (openResult != JS::AsmJSCache_Success) {
    return openResult;
  }

  // Strip off the AsmJSCookieType from the buffer returned to the caller,
  // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
  // with a cookie.
  *aMemory = (uint8_t*)childRunnable->MappedMemory() + sizeof(AsmJSCookieType);

  // The caller guarnatees a call to CloseEntryForWrite (on success or
  // failure) at which point the file will be closed
  childRunnable.Forget(reinterpret_cast<ChildRunnable**>(aHandle));
  return JS::AsmJSCache_Success;
}

void CloseEntryForWrite(size_t aSize, uint8_t* aMemory, intptr_t aHandle) {
  ChildRunnable::AutoClose childRunnable(
      reinterpret_cast<ChildRunnable*>(aHandle));

  MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == childRunnable->FileSize());
  MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) ==
             childRunnable->MappedMemory());

  // Flush to disk before writing the cookie (see OpenEntryForRead).
  if (PR_SyncMemMap(childRunnable->FileDesc(), childRunnable->MappedMemory(),
                    childRunnable->FileSize()) == PR_SUCCESS) {
    *(AsmJSCookieType*)childRunnable->MappedMemory() = sAsmJSCookie;
  }
}

/*******************************************************************************
 * Client
 ******************************************************************************/

Client* Client::sInstance = nullptr;

Client::Client() : mShutdownRequested(false) {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(!sInstance, "We expect this to be a singleton!");

  sInstance = this;
}

Client::~Client() {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(sInstance == this, "We expect this to be a singleton!");

  sInstance = nullptr;
}

Client::Type Client::GetType() { return ASMJS; }

nsresult Client::InitOrigin(PersistenceType aPersistenceType,
                            const nsACString& aGroup, const nsACString& aOrigin,
                            const AtomicBool& aCanceled,
                            UsageInfo* aUsageInfo) {
  if (!aUsageInfo) {
    return NS_OK;
  }
  return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin, aCanceled,
                                   aUsageInfo, /* aInitializing */ true);
}

nsresult Client::GetUsageForOrigin(PersistenceType aPersistenceType,
                                   const nsACString& aGroup,
                                   const nsACString& aOrigin,
                                   const AtomicBool& aCanceled,
                                   UsageInfo* aUsageInfo) {
  return GetUsageForOriginInternal(aPersistenceType, aGroup, aOrigin, aCanceled,
                                   aUsageInfo, /* aInitializing */ false);
}

void Client::OnOriginClearCompleted(PersistenceType aPersistenceType,
                                    const nsACString& aOrigin) {}

void Client::ReleaseIOThreadObjects() {}

void Client::AbortOperations(const nsACString& aOrigin) {}

void Client::AbortOperationsForProcess(ContentParentId aContentParentId) {}

void Client::StartIdleMaintenance() {}

void Client::StopIdleMaintenance() {}

void Client::ShutdownWorkThreads() {
  AssertIsOnBackgroundThread();
  MOZ_ASSERT(!mShutdownRequested);

  mShutdownRequested = true;

  if (sLiveParentActors) {
    MOZ_ALWAYS_TRUE(SpinEventLoopUntil([&]() { return !sLiveParentActors; }));
  }
}

nsresult Client::GetUsageForOriginInternal(PersistenceType aPersistenceType,
                                           const nsACString& aGroup,
                                           const nsACString& aOrigin,
                                           const AtomicBool& aCanceled,
                                           UsageInfo* aUsageInfo,
                                           const bool aInitializing) {
  QuotaManager* qm = QuotaManager::Get();
  MOZ_ASSERT(qm, "We were being called by the QuotaManager");
#ifndef NIGHTLY_BUILD
  Unused << aInitializing;
#endif

  nsCOMPtr<nsIFile> directory;
  nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
                                          getter_AddRefs(directory));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
                                 Asm_GetDirForOrigin);
    return rv;
  }

  MOZ_ASSERT(directory, "We're here because the origin directory exists");

  rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError, Asm_Append);
    return rv;
  }

  DebugOnly<bool> exists;
  MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);

  nsCOMPtr<nsIDirectoryEnumerator> entries;
  rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
  if (NS_WARN_IF(NS_FAILED(rv))) {
    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
                                 Asm_GetDirEntries);
    return rv;
  }

  nsCOMPtr<nsIFile> file;
  while (NS_SUCCEEDED((rv = entries->GetNextFile(getter_AddRefs(file)))) &&
         file && !aCanceled) {
    int64_t fileSize;
    rv = file->GetFileSize(&fileSize);
    if (NS_WARN_IF(NS_FAILED(rv))) {
      REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
                                   Asm_GetFileSize);
      return rv;
    }

    MOZ_ASSERT(fileSize >= 0, "Negative size?!");

    // Since the client is not explicitly storing files, append to database
    // usage which represents implicit storage allocation.
    aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
  }
  if (NS_WARN_IF(NS_FAILED(rv))) {
    REPORT_TELEMETRY_ERR_IN_INIT(aInitializing, kExternalError,
                                 Asm_GetNextFile);
    return rv;
  }

  return NS_OK;
}

quota::Client* CreateClient() { return new Client(); }

}  // namespace asmjscache
}  // namespace dom
}  // namespace mozilla

namespace IPC {

using mozilla::dom::asmjscache::Metadata;
using mozilla::dom::asmjscache::WriteParams;

void ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam) {
  for (auto entry : aParam.mEntries) {
    WriteParam(aMsg, entry.mFastHash);
    WriteParam(aMsg, entry.mNumChars);
    WriteParam(aMsg, entry.mFullHash);
    WriteParam(aMsg, entry.mModuleIndex);
  }
}

bool ParamTraits<Metadata>::Read(const Message* aMsg, PickleIterator* aIter,
                                 paramType* aResult) {
  for (auto& entry : aResult->mEntries) {
    if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
        !ReadParam(aMsg, aIter, &entry.mNumChars) ||
        !ReadParam(aMsg, aIter, &entry.mFullHash) ||
        !ReadParam(aMsg, aIter, &entry.mModuleIndex)) {
      return false;
    }
  }
  return true;
}

void ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog) {
  for (auto entry : aParam.mEntries) {
    LogParam(entry.mFastHash, aLog);
    LogParam(entry.mNumChars, aLog);
    LogParam(entry.mFullHash, aLog);
    LogParam(entry.mModuleIndex, aLog);
  }
}

void ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam) {
  WriteParam(aMsg, aParam.mSize);
  WriteParam(aMsg, aParam.mFastHash);
  WriteParam(aMsg, aParam.mNumChars);
  WriteParam(aMsg, aParam.mFullHash);
}

bool ParamTraits<WriteParams>::Read(const Message* aMsg, PickleIterator* aIter,
                                    paramType* aResult) {
  return ReadParam(aMsg, aIter, &aResult->mSize) &&
         ReadParam(aMsg, aIter, &aResult->mFastHash) &&
         ReadParam(aMsg, aIter, &aResult->mNumChars) &&
         ReadParam(aMsg, aIter, &aResult->mFullHash);
}

void ParamTraits<WriteParams>::Log(const paramType& aParam,
                                   std::wstring* aLog) {
  LogParam(aParam.mSize, aLog);
  LogParam(aParam.mFastHash, aLog);
  LogParam(aParam.mNumChars, aLog);
  LogParam(aParam.mFullHash, aLog);
}

}  // namespace IPC