зеркало из https://github.com/mozilla/gecko-dev.git
1815 строки
53 KiB
C++
1815 строки
53 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/. */
|
|
|
|
#include "AsmJSCache.h"
|
|
|
|
#include <stdio.h>
|
|
|
|
#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/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/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(¤tBuildId);
|
|
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;
|
|
};
|
|
|
|
// 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(¤t)) &&
|
|
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 GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aCanceled,
|
|
aUsageInfo);
|
|
}
|
|
|
|
nsresult Client::GetUsageForOrigin(PersistenceType aPersistenceType,
|
|
const nsACString& aGroup,
|
|
const nsACString& aOrigin,
|
|
const AtomicBool& aCanceled,
|
|
UsageInfo* aUsageInfo) {
|
|
QuotaManager* qm = QuotaManager::Get();
|
|
MOZ_ASSERT(qm, "We were being called by the QuotaManager");
|
|
|
|
nsCOMPtr<nsIFile> directory;
|
|
nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
|
|
getter_AddRefs(directory));
|
|
if (NS_WARN_IF(NS_FAILED(rv))) {
|
|
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))) {
|
|
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))) {
|
|
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))) {
|
|
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))) {
|
|
return rv;
|
|
}
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
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; }));
|
|
}
|
|
}
|
|
|
|
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
|