Bug 963588 - asmjscache: place cache entries apps that request AOT compilation in persistent storage (r=janv)

--HG--
extra : rebase_source : 95bc3d02cb1a7f2728d2615e8b992e0a2b2397f1
This commit is contained in:
Luke Wagner 2014-03-05 14:47:10 -06:00
parent a05a9b8736
commit b7edb7cae1
10 changed files with 183 additions and 55 deletions

View File

@ -25,6 +25,7 @@
#include "mozilla/unused.h"
#include "nsIAtom.h"
#include "nsIFile.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIRunnable.h"
#include "nsISimpleEnumerator.h"
@ -489,6 +490,7 @@ public:
mOpenMode(aOpenMode),
mWriteParams(aWriteParams),
mNeedAllowNextSynchronizedOp(false),
mPersistence(quota::PERSISTENCE_TYPE_INVALID),
mState(eInitial)
{
MOZ_ASSERT(IsMainProcess());
@ -501,11 +503,12 @@ public:
}
protected:
// This method is called by the derived class (either on the JS compilation
// thread or the main thread) when a cache entry has been selected to open.
// This method is called by the derived class on the main thread when a
// cache entry has been selected to open.
void
OpenForRead(unsigned aModuleIndex)
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
MOZ_ASSERT(mOpenMode == eOpenForRead);
@ -514,6 +517,31 @@ protected:
DispatchToIOThread();
}
// This method is called by the derived class on the main thread when no cache
// entry was found to open. If we just tried a lookup in persistent storage
// then we might still get a hit in temporary storage (for an asm.js module
// that wasn't compiled at install-time).
void
CacheMiss()
{
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mState == eFailedToReadMetadata ||
mState == eWaitingToOpenCacheFileForRead);
MOZ_ASSERT(mOpenMode == eOpenForRead);
if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
Fail();
return;
}
// Try again with a clean slate. InitOnMainThread will see that mPersistence
// is initialized and switch to temporary storage.
MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
FinishOnMainThread();
mState = eInitial;
NS_DispatchToMainThread(this);
}
// This method is called by the derived class (either on the JS compilation
// thread or the main thread) when the JS engine is finished reading/writing
// the cache entry.
@ -602,6 +630,7 @@ private:
// State initialized during eInitial:
bool mNeedAllowNextSynchronizedOp;
quota::PersistenceType mPersistence;
nsCString mGroup;
nsCString mOrigin;
nsCString mStorageId;
@ -618,6 +647,7 @@ private:
eInitial, // Just created, waiting to be dispatched to main thread
eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
@ -643,10 +673,56 @@ MainProcessRunnable::InitOnMainThread()
&mOrigin, nullptr, nullptr);
NS_ENSURE_SUCCESS(rv, rv);
QuotaManager::GetStorageId(quota::PERSISTENCE_TYPE_TEMPORARY,
mOrigin, quota::Client::ASMJS,
NS_LITERAL_STRING("asmjs"),
mStorageId);
bool isApp = mPrincipal->GetAppStatus() !=
nsIPrincipal::APP_STATUS_NOT_INSTALLED;
if (mOpenMode == eOpenForWrite) {
MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
if (mWriteParams.mInstalled) {
// If we are performing install-time caching of an app, we'd like to store
// the cache entry in persistent storage so the entry is never evicted,
// but we need to verify that the app has unlimited storage permissions
// first. Unlimited storage permissions justify us in skipping all quota
// checks when storing the cache entry and avoids all the issues around
// the persistent quota prompt.
MOZ_ASSERT(isApp);
nsCOMPtr<nsIPermissionManager> pm =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
uint32_t permission;
rv = pm->TestPermissionFromPrincipal(mPrincipal,
PERMISSION_STORAGE_UNLIMITED,
&permission);
NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
// If app doens't have the unlimited storage permission, we can still
// cache in temporary for a likely good first-run experience.
mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
? quota::PERSISTENCE_TYPE_PERSISTENT
: quota::PERSISTENCE_TYPE_TEMPORARY;
} else {
mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
}
} else {
// For the reasons described above, apps may have cache entries in both
// persistent and temporary storage. At lookup time we don't know how and
// where the given script was cached, so start the search in persistent
// storage and, if that fails, search in temporary storage. (Non-apps can
// only be stored in temporary storage.)
if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
: quota::PERSISTENCE_TYPE_TEMPORARY;
} else {
MOZ_ASSERT(isApp);
MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
}
}
QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
NS_LITERAL_STRING("asmjs"), mStorageId);
return NS_OK;
}
@ -660,8 +736,12 @@ MainProcessRunnable::ReadMetadata()
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
nsresult rv = qm->EnsureOriginIsInitialized(quota::PERSISTENCE_TYPE_TEMPORARY,
mGroup, mOrigin, true,
// Only track quota for temporary storage. For persistent storage, we've
// already checked that we have unlimited-storage permissions.
bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
trackQuota,
getter_AddRefs(mDirectory));
NS_ENSURE_SUCCESS(rv, rv);
@ -730,21 +810,26 @@ MainProcessRunnable::OpenCacheFileForWrite()
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
// Create the QuotaObject before all file IO 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 we are allocating in temporary storage, ask the QuotaManager if we're
// within the quota. If we are allocating in persistent storage, we've already
// checked that we have the unlimited-storage permission, so there is nothing
// to check.
if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
// 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(mPersistence, mGroup, mOrigin, file);
NS_ENSURE_STATE(mQuotaObject);
// Let the QuotaManager know we're about to consume more storage. The
// QuotaManager may veto this or evict other storage. If allocation failed, it
// might be because mOrigin is using too much space (MaybeAllocateMoreSpace
// will not evict our origin since it is active). Try to make some space by
// evicting entries until there is enough space.
if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
return NS_ERROR_FAILURE;
// If the request fails, it might be because mOrigin is using too much
// space (MaybeAllocateMoreSpace 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->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
return NS_ERROR_FAILURE;
}
}
}
@ -780,11 +865,13 @@ MainProcessRunnable::OpenCacheFileForRead()
QuotaManager* qm = QuotaManager::Get();
MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
// Create the QuotaObject before all file IO 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 (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
// 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(mPersistence, mGroup, mOrigin, file);
NS_ENSURE_STATE(mQuotaObject);
}
rv = file->GetFileSize(&mFileSize);
NS_ENSURE_SUCCESS(rv, rv);
@ -824,7 +911,8 @@ MainProcessRunnable::FinishOnMainThread()
QuotaManager* qm = QuotaManager::Get();
if (qm) {
qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
Nullable<PersistenceType>(), mStorageId);
Nullable<PersistenceType>(mPersistence),
mStorageId);
}
}
}
@ -849,8 +937,8 @@ MainProcessRunnable::Run()
mState = eWaitingToOpenMetadata;
rv = QuotaManager::Get()->WaitForOpenAllowed(
OriginOrPatternString::FromOrigin(mOrigin),
Nullable<PersistenceType>(), mStorageId,
this);
Nullable<PersistenceType>(mPersistence),
mStorageId, this);
if (NS_FAILED(rv)) {
Fail();
return NS_OK;
@ -873,7 +961,8 @@ MainProcessRunnable::Run()
rv = ReadMetadata();
if (NS_FAILED(rv)) {
Fail();
mState = eFailedToReadMetadata;
NS_DispatchToMainThread(this);
return NS_OK;
}
@ -894,6 +983,13 @@ MainProcessRunnable::Run()
return NS_OK;
}
case eFailedToReadMetadata: {
MOZ_ASSERT(NS_IsMainThread());
CacheMiss();
return NS_OK;
}
case eSendingMetadataForRead: {
MOZ_ASSERT(NS_IsMainThread());
MOZ_ASSERT(mOpenMode == eOpenForRead);
@ -1029,12 +1125,11 @@ private:
OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
{
uint32_t moduleIndex;
if (!FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
MainProcessRunnable::Fail();
return;
if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
MainProcessRunnable::OpenForRead(moduleIndex);
} else {
MainProcessRunnable::CacheMiss();
}
MainProcessRunnable::OpenForRead(moduleIndex);
}
void
@ -1160,6 +1255,13 @@ private:
return true;
}
bool
RecvCacheMiss() MOZ_OVERRIDE
{
MainProcessRunnable::CacheMiss();
return true;
}
void
OnOpenCacheFile() MOZ_OVERRIDE
{
@ -1283,13 +1385,11 @@ private:
MOZ_ASSERT(mState == eOpening);
uint32_t moduleIndex;
if (!FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
Fail();
Send__delete__(this);
return true;
if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
return SendSelectCacheFileToRead(moduleIndex);
}
return SendSelectCacheFileToRead(moduleIndex);
return SendCacheMiss();
}
bool
@ -1541,6 +1641,7 @@ CloseEntryForRead(JS::Handle<JSObject*> global,
bool
OpenEntryForWrite(nsIPrincipal* aPrincipal,
bool aInstalled,
const jschar* aBegin,
const jschar* aEnd,
size_t aSize,
@ -1557,6 +1658,7 @@ OpenEntryForWrite(nsIPrincipal* aPrincipal,
static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
WriteParams writeParams;
writeParams.mInstalled = aInstalled;
writeParams.mSize = aSize;
writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
writeParams.mNumChars = aEnd - aBegin;
@ -1642,6 +1744,9 @@ public:
const nsACString& aOrigin,
UsageInfo* aUsageInfo) MOZ_OVERRIDE
{
if (!aUsageInfo) {
return NS_OK;
}
return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
}
@ -1670,8 +1775,9 @@ public:
rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
NS_ENSURE_SUCCESS(rv, rv);
bool more;
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&more))) && more) {
bool hasMore;
while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
hasMore && !aUsageInfo->Canceled()) {
nsCOMPtr<nsISupports> entry;
rv = entries->GetNext(getter_AddRefs(entry));
NS_ENSURE_SUCCESS(rv, rv);
@ -1689,6 +1795,7 @@ public:
// usage which represents implicit storage allocation.
aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
}
NS_ENSURE_SUCCESS(rv, rv);
return NS_OK;
}
@ -1809,6 +1916,7 @@ ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
WriteParam(aMsg, aParam.mFastHash);
WriteParam(aMsg, aParam.mNumChars);
WriteParam(aMsg, aParam.mFullHash);
WriteParam(aMsg, aParam.mInstalled);
}
bool
@ -1818,7 +1926,8 @@ ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
return ReadParam(aMsg, aIter, &aResult->mSize) &&
ReadParam(aMsg, aIter, &aResult->mFastHash) &&
ReadParam(aMsg, aIter, &aResult->mNumChars) &&
ReadParam(aMsg, aIter, &aResult->mFullHash);
ReadParam(aMsg, aIter, &aResult->mFullHash) &&
ReadParam(aMsg, aIter, &aResult->mInstalled);
}
void
@ -1828,6 +1937,7 @@ ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
LogParam(aParam.mFastHash, aLog);
LogParam(aParam.mNumChars, aLog);
LogParam(aParam.mFullHash, aLog);
LogParam(aParam.mInstalled, aLog);
}
} // namespace IPC

View File

@ -66,12 +66,14 @@ struct WriteParams
int64_t mFastHash;
int64_t mNumChars;
int64_t mFullHash;
bool mInstalled;
WriteParams()
: mSize(0),
mFastHash(0),
mNumChars(0),
mFullHash(0)
mFullHash(0),
mInstalled(false)
{ }
};
@ -113,6 +115,7 @@ CloseEntryForRead(JS::Handle<JSObject*> aGlobal,
intptr_t aHandle);
bool
OpenEntryForWrite(nsIPrincipal* aPrincipal,
bool aInstalled,
const jschar* aBegin,
const jschar* aEnd,
size_t aSize,

View File

@ -21,6 +21,7 @@ child:
OnOpenMetadataForRead(Metadata metadata);
parent:
SelectCacheFileToRead(uint32_t moduleIndex);
CacheMiss();
child:
// Once the cache file has been opened, the child is notified and sent an

View File

@ -2987,6 +2987,7 @@ AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
static bool
AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
bool aInstalled,
const jschar* aBegin,
const jschar* aEnd,
size_t aSize,
@ -2994,8 +2995,8 @@ AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
intptr_t* aHandle)
{
nsIPrincipal* principal = nsContentUtils::GetObjectPrincipal(aGlobal);
return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
aHandle);
return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
aSize, aMemory, aHandle);
}
static void

View File

@ -21,8 +21,6 @@
#include "nsThreadUtils.h"
#include "nsXULAppAPI.h"
#define PERMISSION_INDEXEDDB_UNLIMITED "indexedDB-unlimited"
#define TOPIC_QUOTA_PROMPT "indexedDB-quota-prompt"
#define TOPIC_QUOTA_RESPONSE "indexedDB-quota-response"
#define TOPIC_QUOTA_CANCEL "indexedDB-quota-cancel"
@ -129,7 +127,7 @@ CheckQuotaHelper::GetQuotaPermission(nsIPrincipal* aPrincipal)
uint32_t permission;
nsresult rv = pm->TestPermissionFromPrincipal(aPrincipal,
PERMISSION_INDEXEDDB_UNLIMITED,
PERMISSION_STORAGE_UNLIMITED,
&permission);
NS_ENSURE_SUCCESS(rv, nsIPermissionManager::DENY_ACTION);
@ -167,7 +165,7 @@ CheckQuotaHelper::Run()
NS_ENSURE_STATE(permissionManager);
rv = permissionManager->AddFromPrincipal(sop->GetPrincipal(),
PERMISSION_INDEXEDDB_UNLIMITED,
PERMISSION_STORAGE_UNLIMITED,
mPromptResult,
nsIPermissionManager::EXPIRE_NEVER, 0);
NS_ENSURE_SUCCESS(rv, rv);

View File

@ -21,6 +21,7 @@
using namespace mozilla::dom::quota;
#define DSSTORE_FILE_NAME ".DS_Store"
#define PERMISSION_STORAGE_UNLIMITED "indexedDB-unlimited"
BEGIN_QUOTA_NAMESPACE

View File

@ -742,6 +742,7 @@ AsmJSCacheOpenEntryForRead(JS::Handle<JSObject*> aGlobal,
static bool
AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
bool aInstalled,
const jschar* aBegin,
const jschar* aEnd,
size_t aSize,
@ -753,8 +754,8 @@ AsmJSCacheOpenEntryForWrite(JS::Handle<JSObject*> aGlobal,
return false;
}
return asmjscache::OpenEntryForWrite(principal, aBegin, aEnd, aSize, aMemory,
aHandle);
return asmjscache::OpenEntryForWrite(principal, aInstalled, aBegin, aEnd,
aSize, aMemory, aHandle);
}
struct WorkerThreadRuntimePrivate : public PerThreadAtomCache

View File

@ -1216,10 +1216,13 @@ js::StoreAsmJSModuleInCache(AsmJSParser &parser,
const jschar *begin = parser.tokenStream.rawBase() + ModuleChars::beginOffset(parser);
const jschar *end = parser.tokenStream.rawBase() + ModuleChars::endOffset(parser);
bool installed = parser.options().installedFile;
ScopedCacheEntryOpenedForWrite entry(cx, serializedSize);
if (!open(cx->global(), begin, end, entry.serializedSize, &entry.memory, &entry.handle))
if (!open(cx->global(), installed, begin, end, entry.serializedSize,
&entry.memory, &entry.handle)) {
return false;
}
uint8_t *cursor = entry.memory;
cursor = machineId.serialize(cursor);

View File

@ -3491,6 +3491,7 @@ class JS_FRIEND_API(ReadOnlyCompileOptions)
werrorOption(false),
asmJSOption(false),
forceAsync(false),
installedFile(false),
sourcePolicy(SAVE_SOURCE),
introductionType(nullptr),
introductionLineno(0),
@ -3530,6 +3531,7 @@ class JS_FRIEND_API(ReadOnlyCompileOptions)
bool werrorOption;
bool asmJSOption;
bool forceAsync;
bool installedFile; // 'true' iff pre-compiling js file in packaged app
enum SourcePolicy {
NO_SOURCE,
LAZY_SOURCE,
@ -4884,9 +4886,16 @@ typedef void
* outparams. If the callback returns 'true', the JS engine guarantees a call
* to CloseAsmJSCacheEntryForWriteOp passing the same base address, size and
* handle.
*
* If 'installed' is true, then the cache entry is associated with a permanently
* installed JS file (e.g., in a packaged webapp). This information allows the
* embedding to store the cache entry in a installed location associated with
* the principal of 'global' where it will not be evicted until the associated
* installed JS file is removed.
*/
typedef bool
(* OpenAsmJSCacheEntryForWriteOp)(HandleObject global, const jschar *begin, const jschar *end,
(* OpenAsmJSCacheEntryForWriteOp)(HandleObject global, bool installed,
const jschar *begin, const jschar *end,
size_t size, uint8_t **memory, intptr_t *handle);
typedef void
(* CloseAsmJSCacheEntryForWriteOp)(HandleObject global, size_t size, uint8_t *memory,

View File

@ -5351,7 +5351,8 @@ ShellCloseAsmJSCacheEntryForRead(HandleObject global, size_t serializedSize, con
}
static bool
ShellOpenAsmJSCacheEntryForWrite(HandleObject global, const jschar *begin, const jschar *end,
ShellOpenAsmJSCacheEntryForWrite(HandleObject global, bool installed,
const jschar *begin, const jschar *end,
size_t serializedSize, uint8_t **memoryOut, intptr_t *handleOut)
{
if (!jsCachingEnabled || !jsCacheAsmJSPath)