Bumping gaia.json for 2 gaia revision(s) a=gaia-bump
[gecko.git] / dom / asmjscache / AsmJSCache.cpp
blobe1b527ff56102334f28bb19a846efa05c583812f
1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=2 et sw=2 tw=80: */
3 /* This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "AsmJSCache.h"
9 #include <stdio.h>
11 #include "js/RootingAPI.h"
12 #include "jsfriendapi.h"
13 #include "mozilla/Assertions.h"
14 #include "mozilla/CondVar.h"
15 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
16 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
17 #include "mozilla/dom/ContentChild.h"
18 #include "mozilla/dom/PermissionMessageUtils.h"
19 #include "mozilla/dom/quota/Client.h"
20 #include "mozilla/dom/quota/OriginOrPatternString.h"
21 #include "mozilla/dom/quota/QuotaManager.h"
22 #include "mozilla/dom/quota/QuotaObject.h"
23 #include "mozilla/dom/quota/UsageInfo.h"
24 #include "mozilla/HashFunctions.h"
25 #include "mozilla/unused.h"
26 #include "nsIAtom.h"
27 #include "nsIFile.h"
28 #include "nsIPermissionManager.h"
29 #include "nsIPrincipal.h"
30 #include "nsIRunnable.h"
31 #include "nsISimpleEnumerator.h"
32 #include "nsIThread.h"
33 #include "nsIXULAppInfo.h"
34 #include "nsJSPrincipals.h"
35 #include "nsThreadUtils.h"
36 #include "nsXULAppAPI.h"
37 #include "prio.h"
38 #include "private/pprio.h"
39 #include "mozilla/Services.h"
41 #define ASMJSCACHE_METADATA_FILE_NAME "metadata"
42 #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
44 using mozilla::dom::quota::AssertIsOnIOThread;
45 using mozilla::dom::quota::OriginOrPatternString;
46 using mozilla::dom::quota::PersistenceType;
47 using mozilla::dom::quota::QuotaManager;
48 using mozilla::dom::quota::QuotaObject;
49 using mozilla::dom::quota::UsageInfo;
50 using mozilla::unused;
51 using mozilla::HashString;
53 namespace mozilla {
55 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
57 namespace dom {
58 namespace asmjscache {
60 namespace {
62 bool
63 IsMainProcess()
65 return XRE_GetProcessType() == GeckoProcessType_Default;
68 // Anything smaller should compile fast enough that caching will just add
69 // overhead.
70 static const size_t sMinCachedModuleLength = 10000;
72 // The number of characters to hash into the Metadata::Entry::mFastHash.
73 static const unsigned sNumFastHashChars = 4096;
75 nsresult
76 WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata)
78 int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;
80 JS::BuildIdCharVector buildId;
81 bool ok = GetBuildId(&buildId);
82 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
84 ScopedPRFileDesc fd;
85 nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
86 NS_ENSURE_SUCCESS(rv, rv);
88 uint32_t length = buildId.length();
89 int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
90 NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);
92 bytesWritten = PR_Write(fd, buildId.begin(), length);
93 NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);
95 bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
96 NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
98 return NS_OK;
101 nsresult
102 ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata)
104 int32_t openFlags = PR_RDONLY;
106 ScopedPRFileDesc fd;
107 nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
108 NS_ENSURE_SUCCESS(rv, rv);
110 // Read the buildid and check that it matches the current buildid
112 JS::BuildIdCharVector currentBuildId;
113 bool ok = GetBuildId(&currentBuildId);
114 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
116 uint32_t length;
117 int32_t bytesRead = PR_Read(fd, &length, sizeof(length));
118 NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED);
120 NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED);
122 JS::BuildIdCharVector fileBuildId;
123 ok = fileBuildId.resize(length);
124 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
126 bytesRead = PR_Read(fd, fileBuildId.begin(), length);
127 NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED);
129 for (uint32_t i = 0; i < length; i++) {
130 if (currentBuildId[i] != fileBuildId[i]) {
131 return NS_ERROR_FAILURE;
135 // Read the Metadata struct
137 bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata));
138 NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
140 return NS_OK;
143 nsresult
144 GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile)
146 nsCOMPtr<nsIFile> cacheFile;
147 nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
148 NS_ENSURE_SUCCESS(rv, rv);
150 nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
151 cacheFileName.AppendInt(aModuleIndex);
152 rv = cacheFile->Append(cacheFileName);
153 NS_ENSURE_SUCCESS(rv, rv);
155 cacheFile.forget(aCacheFile);
156 return NS_OK;
159 class AutoDecreaseUsageForOrigin
161 const nsACString& mGroup;
162 const nsACString& mOrigin;
164 public:
165 uint64_t mFreed;
167 AutoDecreaseUsageForOrigin(const nsACString& aGroup,
168 const nsACString& aOrigin)
170 : mGroup(aGroup),
171 mOrigin(aOrigin),
172 mFreed(0)
175 ~AutoDecreaseUsageForOrigin()
177 AssertIsOnIOThread();
179 if (!mFreed) {
180 return;
183 QuotaManager* qm = QuotaManager::Get();
184 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
186 qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY,
187 mGroup, mOrigin, mFreed);
191 static void
192 EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
193 const nsACString& aOrigin, uint64_t aNumBytes,
194 Metadata& aMetadata)
196 AssertIsOnIOThread();
198 AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);
200 for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
201 Metadata::Entry& entry = aMetadata.mEntries[i];
202 unsigned moduleIndex = entry.mModuleIndex;
204 nsCOMPtr<nsIFile> file;
205 nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
206 if (NS_WARN_IF(NS_FAILED(rv))) {
207 return;
210 bool exists;
211 rv = file->Exists(&exists);
212 if (NS_WARN_IF(NS_FAILED(rv))) {
213 return;
216 if (exists) {
217 int64_t fileSize;
218 rv = file->GetFileSize(&fileSize);
219 if (NS_WARN_IF(NS_FAILED(rv))) {
220 return;
223 rv = file->Remove(false);
224 if (NS_WARN_IF(NS_FAILED(rv))) {
225 return;
228 usage.mFreed += fileSize;
231 entry.clear();
235 // FileDescriptorHolder owns a file descriptor and its memory mapping.
236 // FileDescriptorHolder is derived by all three runnable classes (that is,
237 // (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds,
238 // FileDescriptorHolder is derived virtually by File and MainProcessRunnable for
239 // the benefit of SingleProcessRunnable, which derives both. Since File and
240 // MainProcessRunnable both need to be runnables, FileDescriptorHolder also
241 // derives nsRunnable.
242 class FileDescriptorHolder : public nsRunnable
244 public:
245 FileDescriptorHolder()
246 : mQuotaObject(nullptr),
247 mFileSize(INT64_MIN),
248 mFileDesc(nullptr),
249 mFileMap(nullptr),
250 mMappedMemory(nullptr)
253 ~FileDescriptorHolder()
255 // These resources should have already been released by Finish().
256 MOZ_ASSERT(!mQuotaObject);
257 MOZ_ASSERT(!mMappedMemory);
258 MOZ_ASSERT(!mFileMap);
259 MOZ_ASSERT(!mFileDesc);
262 size_t
263 FileSize() const
265 MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
266 return mFileSize;
269 PRFileDesc*
270 FileDesc() const
272 MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
273 return mFileDesc;
276 bool
277 MapMemory(OpenMode aOpenMode)
279 MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");
281 PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY
282 : PR_PROT_READWRITE;
284 mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
285 NS_ENSURE_TRUE(mFileMap, false);
287 mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
288 NS_ENSURE_TRUE(mMappedMemory, false);
290 return true;
293 void*
294 MappedMemory() const
296 MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
297 return mMappedMemory;
300 protected:
301 // This method must be called before AllowNextSynchronizedOp (which releases
302 // the lock protecting these resources). It is idempotent, so it is ok to call
303 // multiple times (or before the file has been fully opened).
304 void
305 Finish()
307 if (mMappedMemory) {
308 PR_MemUnmap(mMappedMemory, mFileSize);
309 mMappedMemory = nullptr;
311 if (mFileMap) {
312 PR_CloseFileMap(mFileMap);
313 mFileMap = nullptr;
315 if (mFileDesc) {
316 PR_Close(mFileDesc);
317 mFileDesc = nullptr;
320 // Holding the QuotaObject alive until all the cache files are closed enables
321 // assertions in QuotaManager that the cache entry isn't cleared while we
322 // are working on it.
323 mQuotaObject = nullptr;
326 nsRefPtr<QuotaObject> mQuotaObject;
327 int64_t mFileSize;
328 PRFileDesc* mFileDesc;
329 PRFileMap* mFileMap;
330 void* mMappedMemory;
333 // File is a base class shared by (Single|Client)ProcessEntryRunnable that
334 // presents a single interface to the AsmJSCache ops which need to wait until
335 // the file is open, regardless of whether we are executing in the main process
336 // or not.
337 class File : public virtual FileDescriptorHolder
339 public:
340 class AutoClose
342 File* mFile;
344 public:
345 explicit AutoClose(File* aFile = nullptr)
346 : mFile(aFile)
349 void
350 Init(File* aFile)
352 MOZ_ASSERT(!mFile);
353 mFile = aFile;
356 File*
357 operator->() const MOZ_NO_ADDREF_RELEASE_ON_RETURN
359 MOZ_ASSERT(mFile);
360 return mFile;
363 void
364 Forget(File** aFile)
366 *aFile = mFile;
367 mFile = nullptr;
370 ~AutoClose()
372 if (mFile) {
373 mFile->Close();
378 JS::AsmJSCacheResult
379 BlockUntilOpen(AutoClose* aCloser)
381 MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
382 MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");
384 mWaiting = true;
386 nsresult rv = NS_DispatchToMainThread(this);
387 if (NS_WARN_IF(NS_FAILED(rv))) {
388 return JS::AsmJSCache_InternalError;
392 MutexAutoLock lock(mMutex);
393 while (mWaiting) {
394 mCondVar.Wait();
398 if (!mOpened) {
399 return mResult;
402 // Now that we're open, we're guarnateed a Close() call. However, we are
403 // not guarnateed someone is holding an outstanding reference until the File
404 // is closed, so we do that ourselves and Release() in OnClose().
405 aCloser->Init(this);
406 AddRef();
407 return JS::AsmJSCache_Success;
410 // This method must be called if BlockUntilOpen returns 'true'. AutoClose
411 // mostly takes care of this. A derived class that implements Close() must
412 // guarnatee that OnClose() is called (eventually).
413 virtual void
414 Close() = 0;
416 protected:
417 File()
418 : mMutex("File::mMutex"),
419 mCondVar(mMutex, "File::mCondVar"),
420 mWaiting(false),
421 mOpened(false),
422 mResult(JS::AsmJSCache_InternalError)
425 ~File()
427 MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
428 MOZ_ASSERT(!mOpened, "OnClose() should have been called");
431 void
432 OnOpen()
434 Notify(JS::AsmJSCache_Success);
437 void
438 OnFailure(JS::AsmJSCacheResult aResult)
440 MOZ_ASSERT(aResult != JS::AsmJSCache_Success);
442 FileDescriptorHolder::Finish();
443 Notify(aResult);
446 void
447 OnClose()
449 FileDescriptorHolder::Finish();
451 MOZ_ASSERT(mOpened);
452 mOpened = false;
454 // Match the AddRef in BlockUntilOpen(). The main thread event loop still
455 // holds an outstanding ref which will keep 'this' alive until returning to
456 // the event loop.
457 Release();
460 private:
461 void
462 Notify(JS::AsmJSCacheResult aResult)
464 MOZ_ASSERT(NS_IsMainThread());
466 MutexAutoLock lock(mMutex);
467 MOZ_ASSERT(mWaiting);
469 mWaiting = false;
470 mOpened = aResult == JS::AsmJSCache_Success;
471 mResult = aResult;
472 mCondVar.Notify();
475 Mutex mMutex;
476 CondVar mCondVar;
477 bool mWaiting;
478 bool mOpened;
479 JS::AsmJSCacheResult mResult;
482 // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
483 // that factors out the runnable state machine required to open a cache entry
484 // that runs in the main process.
485 class MainProcessRunnable : public virtual FileDescriptorHolder
487 public:
488 NS_DECL_NSIRUNNABLE
490 // MainProcessRunnable runnable assumes that the derived class ensures
491 // (through ref-counting or preconditions) that aPrincipal is kept alive for
492 // the lifetime of the MainProcessRunnable.
493 MainProcessRunnable(nsIPrincipal* aPrincipal,
494 OpenMode aOpenMode,
495 WriteParams aWriteParams)
496 : mPrincipal(aPrincipal),
497 mOpenMode(aOpenMode),
498 mWriteParams(aWriteParams),
499 mNeedAllowNextSynchronizedOp(false),
500 mPersistence(quota::PERSISTENCE_TYPE_INVALID),
501 mState(eInitial),
502 mResult(JS::AsmJSCache_InternalError),
503 mIsApp(false),
504 mHasUnlimStoragePerm(false),
505 mEnforcingQuota(true)
507 MOZ_ASSERT(IsMainProcess());
510 virtual ~MainProcessRunnable()
512 MOZ_ASSERT(mState == eFinished);
513 MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
516 protected:
517 // This method is called by the derived class on the main thread when a
518 // cache entry has been selected to open.
519 void
520 OpenForRead(unsigned aModuleIndex)
522 MOZ_ASSERT(NS_IsMainThread());
523 MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
524 MOZ_ASSERT(mOpenMode == eOpenForRead);
526 mModuleIndex = aModuleIndex;
527 mState = eReadyToOpenCacheFileForRead;
528 DispatchToIOThread();
531 // This method is called by the derived class on the main thread when no cache
532 // entry was found to open. If we just tried a lookup in persistent storage
533 // then we might still get a hit in temporary storage (for an asm.js module
534 // that wasn't compiled at install-time).
535 void
536 CacheMiss()
538 MOZ_ASSERT(NS_IsMainThread());
539 MOZ_ASSERT(mState == eFailedToReadMetadata ||
540 mState == eWaitingToOpenCacheFileForRead);
541 MOZ_ASSERT(mOpenMode == eOpenForRead);
543 if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
544 Fail();
545 return;
548 // Try again with a clean slate. InitOnMainThread will see that mPersistence
549 // is initialized and switch to temporary storage.
550 MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
551 FinishOnMainThread();
552 mState = eInitial;
553 NS_DispatchToMainThread(this);
556 // This method is called by the derived class (either on the JS compilation
557 // thread or the main thread) when the JS engine is finished reading/writing
558 // the cache entry.
559 void
560 Close()
562 MOZ_ASSERT(mState == eOpened);
563 mState = eClosing;
564 NS_DispatchToMainThread(this);
567 // This method is called both internally and by derived classes upon any
568 // failure that prevents the eventual opening of the cache entry.
569 void
570 Fail()
572 MOZ_ASSERT(mState != eOpened &&
573 mState != eClosing &&
574 mState != eFailing &&
575 mState != eFinished);
577 mState = eFailing;
578 NS_DispatchToMainThread(this);
581 // Called by MainProcessRunnable on the main thread after metadata is open:
582 virtual void
583 OnOpenMetadataForRead(const Metadata& aMetadata) = 0;
585 // Called by MainProcessRunnable on the main thread after the entry is open:
586 virtual void
587 OnOpenCacheFile() = 0;
589 // This method may be overridden, but it must be called from the overrider.
590 // Called by MainProcessRunnable on the main thread after a call to Fail():
591 virtual void
592 OnFailure(JS::AsmJSCacheResult aResult)
594 FinishOnMainThread();
597 // This method may be overridden, but it must be called from the overrider.
598 // Called by MainProcessRunnable on the main thread after a call to Close():
599 virtual void
600 OnClose()
602 FinishOnMainThread();
605 private:
606 void
607 InitPersistenceType();
609 nsresult
610 InitOnMainThread();
612 nsresult
613 ReadMetadata();
615 nsresult
616 OpenCacheFileForWrite();
618 nsresult
619 OpenCacheFileForRead();
621 void
622 FinishOnMainThread();
624 void
625 DispatchToIOThread()
627 // If shutdown just started, the QuotaManager may have been deleted.
628 QuotaManager* qm = QuotaManager::Get();
629 if (!qm) {
630 Fail();
631 return;
634 nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
635 if (NS_FAILED(rv)) {
636 Fail();
637 return;
641 nsIPrincipal* const mPrincipal;
642 const OpenMode mOpenMode;
643 const WriteParams mWriteParams;
645 // State initialized during eInitial:
646 bool mNeedAllowNextSynchronizedOp;
647 quota::PersistenceType mPersistence;
648 nsCString mGroup;
649 nsCString mOrigin;
650 nsCString mStorageId;
652 // State initialized during eReadyToReadMetadata
653 nsCOMPtr<nsIFile> mDirectory;
654 nsCOMPtr<nsIFile> mMetadataFile;
655 Metadata mMetadata;
657 // State initialized during eWaitingToOpenCacheFileForRead
658 unsigned mModuleIndex;
660 enum State {
661 eInitial, // Just created, waiting to be dispatched to main thread
662 eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
663 eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
664 eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
665 eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
666 eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
667 eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
668 eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
669 eOpened, // Finished calling OnOpen, waiting to be closed
670 eClosing, // Waiting to be dispatched to main thread again
671 eFailing, // Just failed, waiting to be dispatched to the main thread
672 eFinished, // Terminal state
674 State mState;
675 JS::AsmJSCacheResult mResult;
677 bool mIsApp;
678 bool mHasUnlimStoragePerm;
679 bool mEnforcingQuota;
682 void
683 MainProcessRunnable::InitPersistenceType()
685 MOZ_ASSERT(NS_IsMainThread());
686 MOZ_ASSERT(mState == eInitial);
688 if (mOpenMode == eOpenForWrite) {
689 MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
691 // If we are performing install-time caching of an app, we'd like to store
692 // the cache entry in persistent storage so the entry is never evicted,
693 // but we need to check that quota is not enforced for the app.
694 // That justifies us in skipping all quota checks when storing the cache
695 // entry and avoids all the issues around the persistent quota prompt.
696 // If quota is enforced for the app, then we can still cache in temporary
697 // for a likely good first-run experience.
699 MOZ_ASSERT_IF(mWriteParams.mInstalled, mIsApp);
701 if (mWriteParams.mInstalled &&
702 !QuotaManager::IsQuotaEnforced(quota::PERSISTENCE_TYPE_PERSISTENT,
703 mOrigin, mIsApp, mHasUnlimStoragePerm)) {
704 mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
705 } else {
706 mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
709 return;
712 // For the reasons described above, apps may have cache entries in both
713 // persistent and temporary storage. At lookup time we don't know how and
714 // where the given script was cached, so start the search in persistent
715 // storage and, if that fails, search in temporary storage. (Non-apps can
716 // only be stored in temporary storage.)
718 MOZ_ASSERT_IF(mPersistence != quota::PERSISTENCE_TYPE_INVALID,
719 mIsApp && mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
721 if (mPersistence == quota::PERSISTENCE_TYPE_INVALID && mIsApp) {
722 mPersistence = quota::PERSISTENCE_TYPE_PERSISTENT;
723 } else {
724 mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
728 nsresult
729 MainProcessRunnable::InitOnMainThread()
731 MOZ_ASSERT(NS_IsMainThread());
732 MOZ_ASSERT(mState == eInitial);
734 QuotaManager* qm = QuotaManager::GetOrCreate();
735 NS_ENSURE_STATE(qm);
737 nsresult rv =
738 QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup, &mOrigin, &mIsApp,
739 &mHasUnlimStoragePerm);
740 NS_ENSURE_SUCCESS(rv, rv);
742 InitPersistenceType();
744 mEnforcingQuota =
745 QuotaManager::IsQuotaEnforced(mPersistence, mOrigin, mIsApp,
746 mHasUnlimStoragePerm);
748 QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
749 NS_LITERAL_STRING("asmjs"), mStorageId);
751 return NS_OK;
754 nsresult
755 MainProcessRunnable::ReadMetadata()
757 AssertIsOnIOThread();
758 MOZ_ASSERT(mState == eReadyToReadMetadata);
760 QuotaManager* qm = QuotaManager::Get();
761 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
763 nsresult rv =
764 qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin, mIsApp,
765 mHasUnlimStoragePerm,
766 getter_AddRefs(mDirectory));
767 NS_ENSURE_SUCCESS(rv, rv);
769 rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
770 NS_ENSURE_SUCCESS(rv, rv);
772 bool exists;
773 rv = mDirectory->Exists(&exists);
774 NS_ENSURE_SUCCESS(rv, rv);
776 if (!exists) {
777 rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
778 NS_ENSURE_SUCCESS(rv, rv);
779 } else {
780 DebugOnly<bool> isDirectory;
781 MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory)));
782 MOZ_ASSERT(isDirectory, "Should have caught this earlier!");
785 rv = mDirectory->Clone(getter_AddRefs(mMetadataFile));
786 NS_ENSURE_SUCCESS(rv, rv);
788 rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME));
789 NS_ENSURE_SUCCESS(rv, rv);
791 rv = mMetadataFile->Exists(&exists);
792 NS_ENSURE_SUCCESS(rv, rv);
794 if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) {
795 exists = false;
798 if (!exists) {
799 // If we are reading, we can't possibly have a cache hit.
800 if (mOpenMode == eOpenForRead) {
801 return NS_ERROR_FILE_NOT_FOUND;
804 // Initialize Metadata with a valid empty state for the LRU cache.
805 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
806 Metadata::Entry& entry = mMetadata.mEntries[i];
807 entry.mModuleIndex = i;
808 entry.clear();
812 return NS_OK;
815 nsresult
816 MainProcessRunnable::OpenCacheFileForWrite()
818 AssertIsOnIOThread();
819 MOZ_ASSERT(mState == eReadyToReadMetadata);
820 MOZ_ASSERT(mOpenMode == eOpenForWrite);
822 mFileSize = mWriteParams.mSize;
824 // Kick out the oldest entry in the LRU queue in the metadata.
825 mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;
827 nsCOMPtr<nsIFile> file;
828 nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
829 NS_ENSURE_SUCCESS(rv, rv);
831 QuotaManager* qm = QuotaManager::Get();
832 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
834 if (mEnforcingQuota) {
835 // Create the QuotaObject before all file IO and keep it alive until caching
836 // completes to get maximum assertion coverage in QuotaManager against
837 // concurrent removal, etc.
838 mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
839 NS_ENSURE_STATE(mQuotaObject);
841 if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
842 // If the request fails, it might be because mOrigin is using too much
843 // space (MaybeAllocateMoreSpace will not evict our own origin since it is
844 // active). Try to make some space by evicting LRU entries until there is
845 // enough space.
846 EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
847 if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
848 mResult = JS::AsmJSCache_QuotaExceeded;
849 return NS_ERROR_FAILURE;
854 int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
855 rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
856 NS_ENSURE_SUCCESS(rv, rv);
858 // Move the mModuleIndex's LRU entry to the recent end of the queue.
859 PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
860 Metadata::Entry& entry = mMetadata.mEntries[0];
861 entry.mFastHash = mWriteParams.mFastHash;
862 entry.mNumChars = mWriteParams.mNumChars;
863 entry.mFullHash = mWriteParams.mFullHash;
864 entry.mModuleIndex = mModuleIndex;
866 rv = WriteMetadataFile(mMetadataFile, mMetadata);
867 NS_ENSURE_SUCCESS(rv, rv);
869 return NS_OK;
872 nsresult
873 MainProcessRunnable::OpenCacheFileForRead()
875 AssertIsOnIOThread();
876 MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
877 MOZ_ASSERT(mOpenMode == eOpenForRead);
879 nsCOMPtr<nsIFile> file;
880 nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
881 NS_ENSURE_SUCCESS(rv, rv);
883 QuotaManager* qm = QuotaManager::Get();
884 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
886 if (mEnforcingQuota) {
887 // Even though it's not strictly necessary, create the QuotaObject before
888 // all file IO and keep it alive until caching completes to get maximum
889 // assertion coverage in QuotaManager against concurrent removal, etc.
890 mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
891 NS_ENSURE_STATE(mQuotaObject);
894 rv = file->GetFileSize(&mFileSize);
895 NS_ENSURE_SUCCESS(rv, rv);
897 int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
898 rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
899 NS_ENSURE_SUCCESS(rv, rv);
901 // Move the mModuleIndex's LRU entry to the recent end of the queue.
902 unsigned lruIndex = 0;
903 while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
904 if (++lruIndex == Metadata::kNumEntries) {
905 return NS_ERROR_UNEXPECTED;
908 Metadata::Entry entry = mMetadata.mEntries[lruIndex];
909 PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
910 mMetadata.mEntries[0] = entry;
912 rv = WriteMetadataFile(mMetadataFile, mMetadata);
913 NS_ENSURE_SUCCESS(rv, rv);
915 return NS_OK;
918 void
919 MainProcessRunnable::FinishOnMainThread()
921 MOZ_ASSERT(NS_IsMainThread());
923 // Per FileDescriptorHolder::Finish()'s comment, call before
924 // AllowNextSynchronizedOp.
925 FileDescriptorHolder::Finish();
927 if (mNeedAllowNextSynchronizedOp) {
928 mNeedAllowNextSynchronizedOp = false;
929 QuotaManager* qm = QuotaManager::Get();
930 if (qm) {
931 qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
932 Nullable<PersistenceType>(mPersistence),
933 mStorageId);
938 NS_IMETHODIMP
939 MainProcessRunnable::Run()
941 nsresult rv;
943 // All success/failure paths must eventually call Finish() to avoid leaving
944 // the parser hanging.
945 switch (mState) {
946 case eInitial: {
947 MOZ_ASSERT(NS_IsMainThread());
949 rv = InitOnMainThread();
950 if (NS_FAILED(rv)) {
951 Fail();
952 return NS_OK;
955 mState = eWaitingToOpenMetadata;
956 rv = QuotaManager::Get()->WaitForOpenAllowed(
957 OriginOrPatternString::FromOrigin(mOrigin),
958 Nullable<PersistenceType>(mPersistence),
959 mStorageId, this);
960 if (NS_FAILED(rv)) {
961 Fail();
962 return NS_OK;
965 mNeedAllowNextSynchronizedOp = true;
966 return NS_OK;
969 case eWaitingToOpenMetadata: {
970 MOZ_ASSERT(NS_IsMainThread());
972 mState = eReadyToReadMetadata;
973 DispatchToIOThread();
974 return NS_OK;
977 case eReadyToReadMetadata: {
978 AssertIsOnIOThread();
980 rv = ReadMetadata();
981 if (NS_FAILED(rv)) {
982 mState = eFailedToReadMetadata;
983 NS_DispatchToMainThread(this);
984 return NS_OK;
987 if (mOpenMode == eOpenForRead) {
988 mState = eSendingMetadataForRead;
989 NS_DispatchToMainThread(this);
990 return NS_OK;
993 rv = OpenCacheFileForWrite();
994 if (NS_FAILED(rv)) {
995 Fail();
996 return NS_OK;
999 mState = eSendingCacheFile;
1000 NS_DispatchToMainThread(this);
1001 return NS_OK;
1004 case eFailedToReadMetadata: {
1005 MOZ_ASSERT(NS_IsMainThread());
1007 CacheMiss();
1008 return NS_OK;
1011 case eSendingMetadataForRead: {
1012 MOZ_ASSERT(NS_IsMainThread());
1013 MOZ_ASSERT(mOpenMode == eOpenForRead);
1015 mState = eWaitingToOpenCacheFileForRead;
1016 OnOpenMetadataForRead(mMetadata);
1017 return NS_OK;
1020 case eReadyToOpenCacheFileForRead: {
1021 AssertIsOnIOThread();
1022 MOZ_ASSERT(mOpenMode == eOpenForRead);
1024 rv = OpenCacheFileForRead();
1025 if (NS_FAILED(rv)) {
1026 Fail();
1027 return NS_OK;
1030 mState = eSendingCacheFile;
1031 NS_DispatchToMainThread(this);
1032 return NS_OK;
1035 case eSendingCacheFile: {
1036 MOZ_ASSERT(NS_IsMainThread());
1038 mState = eOpened;
1039 OnOpenCacheFile();
1040 return NS_OK;
1043 case eFailing: {
1044 MOZ_ASSERT(NS_IsMainThread());
1046 mState = eFinished;
1047 OnFailure(mResult);
1048 return NS_OK;
1051 case eClosing: {
1052 MOZ_ASSERT(NS_IsMainThread());
1054 mState = eFinished;
1055 OnClose();
1056 return NS_OK;
1059 case eWaitingToOpenCacheFileForRead:
1060 case eOpened:
1061 case eFinished: {
1062 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
1066 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
1067 return NS_OK;
1070 bool
1071 FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
1072 unsigned* aModuleIndex)
1074 // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
1075 // also stores an mFastHash of its first sNumFastHashChars so this gives us a
1076 // fast way to probabilistically determine whether we have a cache hit. We
1077 // still do a full hash of all the chars before returning the cache file to
1078 // the engine to avoid penalizing the case where there are multiple cached
1079 // asm.js modules where the first sNumFastHashChars are the same. The
1080 // mFullHash of each cache entry can have a different mNumChars so the fast
1081 // hash allows us to avoid performing up to Metadata::kNumEntries separate
1082 // full hashes.
1083 uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
1084 MOZ_ASSERT(numChars > sNumFastHashChars);
1085 uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);
1087 for (unsigned i = 0; i < Metadata::kNumEntries ; i++) {
1088 // Compare the "fast hash" first to see whether it is worthwhile to
1089 // hash all the chars.
1090 Metadata::Entry entry = aMetadata.mEntries[i];
1091 if (entry.mFastHash != fastHash) {
1092 continue;
1095 // Assuming we have enough characters, hash all the chars it would take
1096 // to match this cache entry and compare to the cache entry. If we get a
1097 // hit we'll still do a full source match later (in the JS engine), but
1098 // the full hash match means this is probably the cache entry we want.
1099 if (numChars < entry.mNumChars) {
1100 continue;
1102 uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
1103 if (entry.mFullHash != fullHash) {
1104 continue;
1107 *aModuleIndex = entry.mModuleIndex;
1108 return true;
1111 return false;
1114 // A runnable that executes for a cache access originating in the main process.
1115 class SingleProcessRunnable MOZ_FINAL : public File,
1116 private MainProcessRunnable
1118 public:
1119 // In the single-process case, the calling JS compilation thread holds the
1120 // nsIPrincipal alive indirectly (via the global object -> compartment ->
1121 // principal) so we don't have to ref-count it here. This is fortunate since
1122 // we are off the main thread and nsIPrincipals can only be ref-counted on
1123 // the main thread.
1124 SingleProcessRunnable(nsIPrincipal* aPrincipal,
1125 OpenMode aOpenMode,
1126 WriteParams aWriteParams,
1127 ReadParams aReadParams)
1128 : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
1129 mReadParams(aReadParams)
1131 MOZ_ASSERT(IsMainProcess());
1132 MOZ_ASSERT(!NS_IsMainThread());
1133 MOZ_COUNT_CTOR(SingleProcessRunnable);
1136 protected:
1137 ~SingleProcessRunnable()
1139 MOZ_COUNT_DTOR(SingleProcessRunnable);
1142 private:
1143 void
1144 OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
1146 uint32_t moduleIndex;
1147 if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
1148 MainProcessRunnable::OpenForRead(moduleIndex);
1149 } else {
1150 MainProcessRunnable::CacheMiss();
1154 void
1155 OnOpenCacheFile() MOZ_OVERRIDE
1157 File::OnOpen();
1160 void
1161 Close() MOZ_OVERRIDE MOZ_FINAL
1163 MainProcessRunnable::Close();
1166 void
1167 OnFailure(JS::AsmJSCacheResult aResult) MOZ_OVERRIDE
1169 MainProcessRunnable::OnFailure(aResult);
1170 File::OnFailure(aResult);
1173 void
1174 OnClose() MOZ_OVERRIDE MOZ_FINAL
1176 MainProcessRunnable::OnClose();
1177 File::OnClose();
1180 // Avoid MSVC 'dominance' warning by having clear Run() override.
1181 NS_IMETHODIMP
1182 Run() MOZ_OVERRIDE
1184 return MainProcessRunnable::Run();
1187 ReadParams mReadParams;
1190 // A runnable that executes in a parent process for a cache access originating
1191 // in the content process. This runnable gets registered as an IPDL subprotocol
1192 // actor so that it can communicate with the corresponding ChildProcessRunnable.
1193 class ParentProcessRunnable MOZ_FINAL : public PAsmJSCacheEntryParent,
1194 public MainProcessRunnable
1196 public:
1197 // The given principal comes from an IPC::Principal which will be dec-refed
1198 // at the end of the message, so we must ref-count it here. Fortunately, we
1199 // are on the main thread (where PContent messages are delivered).
1200 ParentProcessRunnable(nsIPrincipal* aPrincipal,
1201 OpenMode aOpenMode,
1202 WriteParams aWriteParams)
1203 : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
1204 mPrincipalHolder(aPrincipal),
1205 mActorDestroyed(false),
1206 mOpened(false),
1207 mFinished(false)
1209 MOZ_ASSERT(IsMainProcess());
1210 MOZ_ASSERT(NS_IsMainThread());
1211 MOZ_COUNT_CTOR(ParentProcessRunnable);
1214 private:
1215 ~ParentProcessRunnable()
1217 MOZ_ASSERT(!mPrincipalHolder, "Should have already been released");
1218 MOZ_ASSERT(mActorDestroyed);
1219 MOZ_ASSERT(mFinished);
1220 MOZ_COUNT_DTOR(ParentProcessRunnable);
1223 bool
1224 Recv__delete__(const JS::AsmJSCacheResult& aResult) MOZ_OVERRIDE
1226 MOZ_ASSERT(!mFinished);
1227 mFinished = true;
1229 if (mOpened) {
1230 MainProcessRunnable::Close();
1231 } else {
1232 MainProcessRunnable::Fail();
1235 return true;
1238 void
1239 ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
1241 MOZ_ASSERT(!mActorDestroyed);
1242 mActorDestroyed = true;
1244 // Assume ActorDestroy can happen at any time, so probe the current state to
1245 // determine what needs to happen.
1247 if (mFinished) {
1248 return;
1251 mFinished = true;
1253 if (mOpened) {
1254 MainProcessRunnable::Close();
1255 } else {
1256 MainProcessRunnable::Fail();
1260 void
1261 OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
1263 MOZ_ASSERT(NS_IsMainThread());
1265 if (!SendOnOpenMetadataForRead(aMetadata)) {
1266 unused << Send__delete__(this, JS::AsmJSCache_InternalError);
1270 bool
1271 RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE
1273 MainProcessRunnable::OpenForRead(aModuleIndex);
1274 return true;
1277 bool
1278 RecvCacheMiss() MOZ_OVERRIDE
1280 MainProcessRunnable::CacheMiss();
1281 return true;
1284 void
1285 OnOpenCacheFile() MOZ_OVERRIDE
1287 MOZ_ASSERT(NS_IsMainThread());
1289 MOZ_ASSERT(!mOpened);
1290 mOpened = true;
1292 FileDescriptor::PlatformHandleType handle =
1293 FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc));
1294 if (!SendOnOpenCacheFile(mFileSize, FileDescriptor(handle))) {
1295 unused << Send__delete__(this, JS::AsmJSCache_InternalError);
1299 void
1300 OnClose() MOZ_OVERRIDE MOZ_FINAL
1302 MOZ_ASSERT(NS_IsMainThread());
1303 MOZ_ASSERT(mOpened);
1305 mFinished = true;
1307 MainProcessRunnable::OnClose();
1309 MOZ_ASSERT(mActorDestroyed);
1311 mPrincipalHolder = nullptr;
1314 void
1315 OnFailure(JS::AsmJSCacheResult aResult) MOZ_OVERRIDE
1317 MOZ_ASSERT(NS_IsMainThread());
1318 MOZ_ASSERT(!mOpened);
1320 mFinished = true;
1322 MainProcessRunnable::OnFailure(aResult);
1324 if (!mActorDestroyed) {
1325 unused << Send__delete__(this, aResult);
1328 mPrincipalHolder = nullptr;
1331 nsCOMPtr<nsIPrincipal> mPrincipalHolder;
1332 bool mActorDestroyed;
1333 bool mOpened;
1334 bool mFinished;
1337 } // unnamed namespace
1339 PAsmJSCacheEntryParent*
1340 AllocEntryParent(OpenMode aOpenMode,
1341 WriteParams aWriteParams,
1342 nsIPrincipal* aPrincipal)
1344 nsRefPtr<ParentProcessRunnable> runnable =
1345 new ParentProcessRunnable(aPrincipal, aOpenMode, aWriteParams);
1347 nsresult rv = NS_DispatchToMainThread(runnable);
1348 NS_ENSURE_SUCCESS(rv, nullptr);
1350 // Transfer ownership to IPDL.
1351 return runnable.forget().take();
1354 void
1355 DeallocEntryParent(PAsmJSCacheEntryParent* aActor)
1357 // Transfer ownership back from IPDL.
1358 nsRefPtr<ParentProcessRunnable> op =
1359 dont_AddRef(static_cast<ParentProcessRunnable*>(aActor));
1362 namespace {
1364 class ChildProcessRunnable MOZ_FINAL : public File,
1365 public PAsmJSCacheEntryChild
1367 public:
1368 NS_DECL_NSIRUNNABLE
1370 // In the single-process case, the calling JS compilation thread holds the
1371 // nsIPrincipal alive indirectly (via the global object -> compartment ->
1372 // principal) so we don't have to ref-count it here. This is fortunate since
1373 // we are off the main thread and nsIPrincipals can only be ref-counted on
1374 // the main thread.
1375 ChildProcessRunnable(nsIPrincipal* aPrincipal,
1376 OpenMode aOpenMode,
1377 WriteParams aWriteParams,
1378 ReadParams aReadParams)
1379 : mPrincipal(aPrincipal),
1380 mOpenMode(aOpenMode),
1381 mWriteParams(aWriteParams),
1382 mReadParams(aReadParams),
1383 mActorDestroyed(false),
1384 mState(eInitial)
1386 MOZ_ASSERT(!IsMainProcess());
1387 MOZ_ASSERT(!NS_IsMainThread());
1388 MOZ_COUNT_CTOR(ChildProcessRunnable);
1391 protected:
1392 ~ChildProcessRunnable()
1394 MOZ_ASSERT(mState == eFinished);
1395 MOZ_ASSERT(mActorDestroyed);
1396 MOZ_COUNT_DTOR(ChildProcessRunnable);
1399 private:
1400 bool
1401 RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
1403 MOZ_ASSERT(NS_IsMainThread());
1404 MOZ_ASSERT(mState == eOpening);
1406 uint32_t moduleIndex;
1407 if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
1408 return SendSelectCacheFileToRead(moduleIndex);
1411 return SendCacheMiss();
1414 bool
1415 RecvOnOpenCacheFile(const int64_t& aFileSize,
1416 const FileDescriptor& aFileDesc) MOZ_OVERRIDE
1418 MOZ_ASSERT(NS_IsMainThread());
1419 MOZ_ASSERT(mState == eOpening);
1421 mFileSize = aFileSize;
1423 mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
1424 if (!mFileDesc) {
1425 return false;
1428 mState = eOpened;
1429 File::OnOpen();
1430 return true;
1433 bool
1434 Recv__delete__(const JS::AsmJSCacheResult& aResult) MOZ_OVERRIDE
1436 MOZ_ASSERT(NS_IsMainThread());
1437 MOZ_ASSERT(mState == eOpening);
1439 Fail(aResult);
1440 return true;
1443 void
1444 ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
1446 MOZ_ASSERT(NS_IsMainThread());
1447 mActorDestroyed = true;
1450 void
1451 Close() MOZ_OVERRIDE MOZ_FINAL
1453 MOZ_ASSERT(mState == eOpened);
1455 mState = eClosing;
1456 NS_DispatchToMainThread(this);
1459 private:
1460 void
1461 Fail(JS::AsmJSCacheResult aResult)
1463 MOZ_ASSERT(NS_IsMainThread());
1464 MOZ_ASSERT(mState == eInitial || mState == eOpening);
1466 mState = eFinished;
1467 File::OnFailure(aResult);
1470 nsIPrincipal* const mPrincipal;
1471 const OpenMode mOpenMode;
1472 WriteParams mWriteParams;
1473 ReadParams mReadParams;
1474 bool mActorDestroyed;
1476 enum State {
1477 eInitial, // Just created, waiting to dispatched to the main thread
1478 eOpening, // Waiting for the parent process to respond
1479 eOpened, // Parent process opened the entry and sent it back
1480 eClosing, // Waiting to be dispatched to the main thread to Send__delete__
1481 eFinished // Terminal state
1483 State mState;
1486 NS_IMETHODIMP
1487 ChildProcessRunnable::Run()
1489 switch (mState) {
1490 case eInitial: {
1491 MOZ_ASSERT(NS_IsMainThread());
1493 // AddRef to keep this runnable alive until IPDL deallocates the
1494 // subprotocol (DeallocEntryChild).
1495 AddRef();
1497 if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor(
1498 this, mOpenMode, mWriteParams, IPC::Principal(mPrincipal)))
1500 // On failure, undo the AddRef (since DeallocEntryChild will not be
1501 // called) and unblock the parsing thread with a failure. The main
1502 // thread event loop still holds an outstanding ref which will keep
1503 // 'this' alive until returning to the event loop.
1504 Release();
1506 Fail(JS::AsmJSCache_InternalError);
1507 return NS_OK;
1510 mState = eOpening;
1511 return NS_OK;
1514 case eClosing: {
1515 MOZ_ASSERT(NS_IsMainThread());
1517 // Per FileDescriptorHolder::Finish()'s comment, call before
1518 // AllowNextSynchronizedOp (which happens in the parent upon receipt of
1519 // the Send__delete__ message).
1520 File::OnClose();
1522 if (!mActorDestroyed) {
1523 unused << Send__delete__(this, JS::AsmJSCache_Success);
1526 mState = eFinished;
1527 return NS_OK;
1530 case eOpening:
1531 case eOpened:
1532 case eFinished: {
1533 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Shouldn't Run() in this state");
1537 MOZ_MAKE_COMPILER_ASSUME_IS_UNREACHABLE("Corrupt state");
1538 return NS_OK;
1541 } // unnamed namespace
1543 void
1544 DeallocEntryChild(PAsmJSCacheEntryChild* aActor)
1546 // Match the AddRef before SendPAsmJSCacheEntryConstructor.
1547 static_cast<ChildProcessRunnable*>(aActor)->Release();
1550 namespace {
1552 JS::AsmJSCacheResult
1553 OpenFile(nsIPrincipal* aPrincipal,
1554 OpenMode aOpenMode,
1555 WriteParams aWriteParams,
1556 ReadParams aReadParams,
1557 File::AutoClose* aFile)
1559 MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
1560 MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);
1562 // There are three reasons we don't attempt caching from the main thread:
1563 // 1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
1564 // synchronous waiting on the main thread requiring a runnable to be
1565 // dispatched to the main thread.
1566 // 2. In the child process: the IPDL PContent messages we need to
1567 // synchronously wait on are dispatched to the main thread.
1568 // 3. While a cache lookup *should* be much faster than compilation, IO
1569 // operations can be unpredictably slow and we'd like to avoid the
1570 // occasional janks on the main thread.
1571 // We could use a nested event loop to address 1 and 2, but we're potentially
1572 // in the middle of running JS (eval()) and nested event loops can be
1573 // semantically observable.
1574 if (NS_IsMainThread()) {
1575 return JS::AsmJSCache_SynchronousScript;
1578 // If we are in a child process, we need to synchronously call into the
1579 // parent process to open the file and interact with the QuotaManager. The
1580 // child can then map the file into its address space to perform I/O.
1581 nsRefPtr<File> file;
1582 if (IsMainProcess()) {
1583 file = new SingleProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
1584 aReadParams);
1585 } else {
1586 file = new ChildProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
1587 aReadParams);
1590 JS::AsmJSCacheResult openResult = file->BlockUntilOpen(aFile);
1591 if (openResult != JS::AsmJSCache_Success) {
1592 return openResult;
1595 if (!file->MapMemory(aOpenMode)) {
1596 return JS::AsmJSCache_InternalError;
1599 return JS::AsmJSCache_Success;
1602 } // anonymous namespace
1604 typedef uint32_t AsmJSCookieType;
1605 static const uint32_t sAsmJSCookie = 0x600d600d;
1607 bool
1608 OpenEntryForRead(nsIPrincipal* aPrincipal,
1609 const char16_t* aBegin,
1610 const char16_t* aLimit,
1611 size_t* aSize,
1612 const uint8_t** aMemory,
1613 intptr_t* aFile)
1615 if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
1616 return false;
1619 ReadParams readParams;
1620 readParams.mBegin = aBegin;
1621 readParams.mLimit = aLimit;
1623 File::AutoClose file;
1624 WriteParams notAWrite;
1625 JS::AsmJSCacheResult openResult =
1626 OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &file);
1627 if (openResult != JS::AsmJSCache_Success) {
1628 return false;
1631 // Although we trust that the stored cache files have not been arbitrarily
1632 // corrupted, it is possible that a previous execution aborted in the middle
1633 // of writing a cache file (crash, OOM-killer, etc). To protect against
1634 // partially-written cache files, we use the following scheme:
1635 // - Allocate an extra word at the beginning of every cache file which
1636 // starts out 0 (OpenFile opens with PR_TRUNCATE).
1637 // - After the asm.js serialization is complete, PR_SyncMemMap to write
1638 // everything to disk and then store a non-zero value (sAsmJSCookie)
1639 // in the first word.
1640 // - When attempting to read a cache file, check whether the first word is
1641 // sAsmJSCookie.
1642 if (file->FileSize() < sizeof(AsmJSCookieType) ||
1643 *(AsmJSCookieType*)file->MappedMemory() != sAsmJSCookie) {
1644 return false;
1647 *aSize = file->FileSize() - sizeof(AsmJSCookieType);
1648 *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
1650 // The caller guarnatees a call to CloseEntryForRead (on success or
1651 // failure) at which point the file will be closed.
1652 file.Forget(reinterpret_cast<File**>(aFile));
1653 return true;
1656 void
1657 CloseEntryForRead(size_t aSize,
1658 const uint8_t* aMemory,
1659 intptr_t aFile)
1661 File::AutoClose file(reinterpret_cast<File*>(aFile));
1663 MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
1664 MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
1667 JS::AsmJSCacheResult
1668 OpenEntryForWrite(nsIPrincipal* aPrincipal,
1669 bool aInstalled,
1670 const char16_t* aBegin,
1671 const char16_t* aEnd,
1672 size_t aSize,
1673 uint8_t** aMemory,
1674 intptr_t* aFile)
1676 if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
1677 return JS::AsmJSCache_ModuleTooSmall;
1680 // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
1681 aSize += sizeof(AsmJSCookieType);
1683 static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
1685 WriteParams writeParams;
1686 writeParams.mInstalled = aInstalled;
1687 writeParams.mSize = aSize;
1688 writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
1689 writeParams.mNumChars = aEnd - aBegin;
1690 writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
1692 File::AutoClose file;
1693 ReadParams notARead;
1694 JS::AsmJSCacheResult openResult =
1695 OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file);
1696 if (openResult != JS::AsmJSCache_Success) {
1697 return openResult;
1700 // Strip off the AsmJSCookieType from the buffer returned to the caller,
1701 // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
1702 // with a cookie.
1703 *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
1705 // The caller guarnatees a call to CloseEntryForWrite (on success or
1706 // failure) at which point the file will be closed
1707 file.Forget(reinterpret_cast<File**>(aFile));
1708 return JS::AsmJSCache_Success;
1711 void
1712 CloseEntryForWrite(size_t aSize,
1713 uint8_t* aMemory,
1714 intptr_t aFile)
1716 File::AutoClose file(reinterpret_cast<File*>(aFile));
1718 MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
1719 MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
1721 // Flush to disk before writing the cookie (see OpenEntryForRead).
1722 if (PR_SyncMemMap(file->FileDesc(),
1723 file->MappedMemory(),
1724 file->FileSize()) == PR_SUCCESS) {
1725 *(AsmJSCookieType*)file->MappedMemory() = sAsmJSCookie;
1729 bool
1730 GetBuildId(JS::BuildIdCharVector* aBuildID)
1732 nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
1733 if (!info) {
1734 return false;
1737 nsCString buildID;
1738 nsresult rv = info->GetPlatformBuildID(buildID);
1739 NS_ENSURE_SUCCESS(rv, false);
1741 if (!aBuildID->resize(buildID.Length())) {
1742 return false;
1745 for (size_t i = 0; i < buildID.Length(); i++) {
1746 (*aBuildID)[i] = buildID[i];
1749 return true;
1752 class Client : public quota::Client
1754 ~Client() {}
1756 public:
1757 NS_IMETHOD_(MozExternalRefCountType)
1758 AddRef() MOZ_OVERRIDE;
1760 NS_IMETHOD_(MozExternalRefCountType)
1761 Release() MOZ_OVERRIDE;
1763 virtual Type
1764 GetType() MOZ_OVERRIDE
1766 return ASMJS;
1769 virtual nsresult
1770 InitOrigin(PersistenceType aPersistenceType,
1771 const nsACString& aGroup,
1772 const nsACString& aOrigin,
1773 UsageInfo* aUsageInfo) MOZ_OVERRIDE
1775 if (!aUsageInfo) {
1776 return NS_OK;
1778 return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
1781 virtual nsresult
1782 GetUsageForOrigin(PersistenceType aPersistenceType,
1783 const nsACString& aGroup,
1784 const nsACString& aOrigin,
1785 UsageInfo* aUsageInfo) MOZ_OVERRIDE
1787 QuotaManager* qm = QuotaManager::Get();
1788 MOZ_ASSERT(qm, "We were being called by the QuotaManager");
1790 nsCOMPtr<nsIFile> directory;
1791 nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
1792 getter_AddRefs(directory));
1793 NS_ENSURE_SUCCESS(rv, rv);
1794 MOZ_ASSERT(directory, "We're here because the origin directory exists");
1796 rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
1797 NS_ENSURE_SUCCESS(rv, rv);
1799 DebugOnly<bool> exists;
1800 MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
1802 nsCOMPtr<nsISimpleEnumerator> entries;
1803 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
1804 NS_ENSURE_SUCCESS(rv, rv);
1806 bool hasMore;
1807 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
1808 hasMore && !aUsageInfo->Canceled()) {
1809 nsCOMPtr<nsISupports> entry;
1810 rv = entries->GetNext(getter_AddRefs(entry));
1811 NS_ENSURE_SUCCESS(rv, rv);
1813 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
1814 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
1816 int64_t fileSize;
1817 rv = file->GetFileSize(&fileSize);
1818 NS_ENSURE_SUCCESS(rv, rv);
1820 MOZ_ASSERT(fileSize >= 0, "Negative size?!");
1822 // Since the client is not explicitly storing files, append to database
1823 // usage which represents implicit storage allocation.
1824 aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
1826 NS_ENSURE_SUCCESS(rv, rv);
1828 return NS_OK;
1831 virtual void
1832 OnOriginClearCompleted(PersistenceType aPersistenceType,
1833 const nsACString& aOrigin)
1834 MOZ_OVERRIDE
1837 virtual void
1838 ReleaseIOThreadObjects() MOZ_OVERRIDE
1841 virtual bool
1842 IsFileServiceUtilized() MOZ_OVERRIDE
1844 return false;
1847 virtual bool
1848 IsTransactionServiceActivated() MOZ_OVERRIDE
1850 return false;
1853 virtual void
1854 WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
1855 nsIRunnable* aCallback) MOZ_OVERRIDE
1857 MOZ_ASSERT_UNREACHABLE("There are no storages");
1860 virtual void
1861 ShutdownTransactionService() MOZ_OVERRIDE
1864 private:
1865 nsAutoRefCnt mRefCnt;
1866 NS_DECL_OWNINGTHREAD
1869 NS_IMPL_ADDREF(asmjscache::Client)
1870 NS_IMPL_RELEASE(asmjscache::Client)
1872 quota::Client*
1873 CreateClient()
1875 return new Client();
1878 } // namespace asmjscache
1879 } // namespace dom
1880 } // namespace mozilla
1882 namespace IPC {
1884 using mozilla::dom::asmjscache::Metadata;
1885 using mozilla::dom::asmjscache::WriteParams;
1887 void
1888 ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam)
1890 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
1891 const Metadata::Entry& entry = aParam.mEntries[i];
1892 WriteParam(aMsg, entry.mFastHash);
1893 WriteParam(aMsg, entry.mNumChars);
1894 WriteParam(aMsg, entry.mFullHash);
1895 WriteParam(aMsg, entry.mModuleIndex);
1899 bool
1900 ParamTraits<Metadata>::Read(const Message* aMsg, void** aIter,
1901 paramType* aResult)
1903 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
1904 Metadata::Entry& entry = aResult->mEntries[i];
1905 if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
1906 !ReadParam(aMsg, aIter, &entry.mNumChars) ||
1907 !ReadParam(aMsg, aIter, &entry.mFullHash) ||
1908 !ReadParam(aMsg, aIter, &entry.mModuleIndex))
1910 return false;
1913 return true;
1916 void
1917 ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog)
1919 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
1920 const Metadata::Entry& entry = aParam.mEntries[i];
1921 LogParam(entry.mFastHash, aLog);
1922 LogParam(entry.mNumChars, aLog);
1923 LogParam(entry.mFullHash, aLog);
1924 LogParam(entry.mModuleIndex, aLog);
1928 void
1929 ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
1931 WriteParam(aMsg, aParam.mSize);
1932 WriteParam(aMsg, aParam.mFastHash);
1933 WriteParam(aMsg, aParam.mNumChars);
1934 WriteParam(aMsg, aParam.mFullHash);
1935 WriteParam(aMsg, aParam.mInstalled);
1938 bool
1939 ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
1940 paramType* aResult)
1942 return ReadParam(aMsg, aIter, &aResult->mSize) &&
1943 ReadParam(aMsg, aIter, &aResult->mFastHash) &&
1944 ReadParam(aMsg, aIter, &aResult->mNumChars) &&
1945 ReadParam(aMsg, aIter, &aResult->mFullHash) &&
1946 ReadParam(aMsg, aIter, &aResult->mInstalled);
1949 void
1950 ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
1952 LogParam(aParam.mSize, aLog);
1953 LogParam(aParam.mFastHash, aLog);
1954 LogParam(aParam.mNumChars, aLog);
1955 LogParam(aParam.mFullHash, aLog);
1956 LogParam(aParam.mInstalled, aLog);
1959 } // namespace IPC