dom/asmjscache/AsmJSCache.cpp

Wed, 31 Dec 2014 06:09:35 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 06:09:35 +0100
changeset 0
6474c204b198
permissions
-rw-r--r--

Cloned upstream origin tor-browser at tor-browser-31.3.0esr-4.5-1-build1
revision ID fc1c9ff7c1b2defdbc039f12214767608f46423f for hacking purpose.

michael@0 1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
michael@0 2 /* vim: set ts=2 et sw=2 tw=80: */
michael@0 3 /* This Source Code Form is subject to the terms of the Mozilla Public
michael@0 4 * License, v. 2.0. If a copy of the MPL was not distributed with this file,
michael@0 5 * You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "AsmJSCache.h"
michael@0 8
michael@0 9 #include <stdio.h>
michael@0 10
michael@0 11 #include "js/RootingAPI.h"
michael@0 12 #include "jsfriendapi.h"
michael@0 13 #include "mozilla/Assertions.h"
michael@0 14 #include "mozilla/CondVar.h"
michael@0 15 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryChild.h"
michael@0 16 #include "mozilla/dom/asmjscache/PAsmJSCacheEntryParent.h"
michael@0 17 #include "mozilla/dom/ContentChild.h"
michael@0 18 #include "mozilla/dom/PermissionMessageUtils.h"
michael@0 19 #include "mozilla/dom/quota/Client.h"
michael@0 20 #include "mozilla/dom/quota/OriginOrPatternString.h"
michael@0 21 #include "mozilla/dom/quota/QuotaManager.h"
michael@0 22 #include "mozilla/dom/quota/QuotaObject.h"
michael@0 23 #include "mozilla/dom/quota/UsageInfo.h"
michael@0 24 #include "mozilla/HashFunctions.h"
michael@0 25 #include "mozilla/unused.h"
michael@0 26 #include "nsIAtom.h"
michael@0 27 #include "nsIFile.h"
michael@0 28 #include "nsIPermissionManager.h"
michael@0 29 #include "nsIPrincipal.h"
michael@0 30 #include "nsIRunnable.h"
michael@0 31 #include "nsISimpleEnumerator.h"
michael@0 32 #include "nsIThread.h"
michael@0 33 #include "nsIXULAppInfo.h"
michael@0 34 #include "nsJSPrincipals.h"
michael@0 35 #include "nsThreadUtils.h"
michael@0 36 #include "nsXULAppAPI.h"
michael@0 37 #include "prio.h"
michael@0 38 #include "private/pprio.h"
michael@0 39
michael@0 40 #define ASMJSCACHE_METADATA_FILE_NAME "metadata"
michael@0 41 #define ASMJSCACHE_ENTRY_FILE_NAME_BASE "module"
michael@0 42
michael@0 43 using mozilla::dom::quota::AssertIsOnIOThread;
michael@0 44 using mozilla::dom::quota::OriginOrPatternString;
michael@0 45 using mozilla::dom::quota::PersistenceType;
michael@0 46 using mozilla::dom::quota::QuotaManager;
michael@0 47 using mozilla::dom::quota::QuotaObject;
michael@0 48 using mozilla::dom::quota::UsageInfo;
michael@0 49 using mozilla::unused;
michael@0 50 using mozilla::HashString;
michael@0 51
michael@0 52 namespace mozilla {
michael@0 53
michael@0 54 MOZ_TYPE_SPECIFIC_SCOPED_POINTER_TEMPLATE(ScopedPRFileDesc, PRFileDesc, PR_Close);
michael@0 55
michael@0 56 namespace dom {
michael@0 57 namespace asmjscache {
michael@0 58
michael@0 59 namespace {
michael@0 60
michael@0 61 bool
michael@0 62 IsMainProcess()
michael@0 63 {
michael@0 64 return XRE_GetProcessType() == GeckoProcessType_Default;
michael@0 65 }
michael@0 66
michael@0 67 // Anything smaller should compile fast enough that caching will just add
michael@0 68 // overhead.
michael@0 69 static const size_t sMinCachedModuleLength = 10000;
michael@0 70
michael@0 71 // The number of characters to hash into the Metadata::Entry::mFastHash.
michael@0 72 static const unsigned sNumFastHashChars = 4096;
michael@0 73
michael@0 74 nsresult
michael@0 75 WriteMetadataFile(nsIFile* aMetadataFile, const Metadata& aMetadata)
michael@0 76 {
michael@0 77 int32_t openFlags = PR_WRONLY | PR_TRUNCATE | PR_CREATE_FILE;
michael@0 78
michael@0 79 JS::BuildIdCharVector buildId;
michael@0 80 bool ok = GetBuildId(&buildId);
michael@0 81 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
michael@0 82
michael@0 83 ScopedPRFileDesc fd;
michael@0 84 nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
michael@0 85 NS_ENSURE_SUCCESS(rv, rv);
michael@0 86
michael@0 87 uint32_t length = buildId.length();
michael@0 88 int32_t bytesWritten = PR_Write(fd, &length, sizeof(length));
michael@0 89 NS_ENSURE_TRUE(bytesWritten == sizeof(length), NS_ERROR_UNEXPECTED);
michael@0 90
michael@0 91 bytesWritten = PR_Write(fd, buildId.begin(), length);
michael@0 92 NS_ENSURE_TRUE(bytesWritten == int32_t(length), NS_ERROR_UNEXPECTED);
michael@0 93
michael@0 94 bytesWritten = PR_Write(fd, &aMetadata, sizeof(aMetadata));
michael@0 95 NS_ENSURE_TRUE(bytesWritten == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
michael@0 96
michael@0 97 return NS_OK;
michael@0 98 }
michael@0 99
michael@0 100 nsresult
michael@0 101 ReadMetadataFile(nsIFile* aMetadataFile, Metadata& aMetadata)
michael@0 102 {
michael@0 103 int32_t openFlags = PR_RDONLY;
michael@0 104
michael@0 105 ScopedPRFileDesc fd;
michael@0 106 nsresult rv = aMetadataFile->OpenNSPRFileDesc(openFlags, 0644, &fd.rwget());
michael@0 107 NS_ENSURE_SUCCESS(rv, rv);
michael@0 108
michael@0 109 // Read the buildid and check that it matches the current buildid
michael@0 110
michael@0 111 JS::BuildIdCharVector currentBuildId;
michael@0 112 bool ok = GetBuildId(&currentBuildId);
michael@0 113 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
michael@0 114
michael@0 115 uint32_t length;
michael@0 116 int32_t bytesRead = PR_Read(fd, &length, sizeof(length));
michael@0 117 NS_ENSURE_TRUE(bytesRead == sizeof(length), NS_ERROR_UNEXPECTED);
michael@0 118
michael@0 119 NS_ENSURE_TRUE(currentBuildId.length() == length, NS_ERROR_UNEXPECTED);
michael@0 120
michael@0 121 JS::BuildIdCharVector fileBuildId;
michael@0 122 ok = fileBuildId.resize(length);
michael@0 123 NS_ENSURE_TRUE(ok, NS_ERROR_OUT_OF_MEMORY);
michael@0 124
michael@0 125 bytesRead = PR_Read(fd, fileBuildId.begin(), length);
michael@0 126 NS_ENSURE_TRUE(bytesRead == int32_t(length), NS_ERROR_UNEXPECTED);
michael@0 127
michael@0 128 for (uint32_t i = 0; i < length; i++) {
michael@0 129 if (currentBuildId[i] != fileBuildId[i]) {
michael@0 130 return NS_ERROR_FAILURE;
michael@0 131 }
michael@0 132 }
michael@0 133
michael@0 134 // Read the Metadata struct
michael@0 135
michael@0 136 bytesRead = PR_Read(fd, &aMetadata, sizeof(aMetadata));
michael@0 137 NS_ENSURE_TRUE(bytesRead == sizeof(aMetadata), NS_ERROR_UNEXPECTED);
michael@0 138
michael@0 139 return NS_OK;
michael@0 140 }
michael@0 141
michael@0 142 nsresult
michael@0 143 GetCacheFile(nsIFile* aDirectory, unsigned aModuleIndex, nsIFile** aCacheFile)
michael@0 144 {
michael@0 145 nsCOMPtr<nsIFile> cacheFile;
michael@0 146 nsresult rv = aDirectory->Clone(getter_AddRefs(cacheFile));
michael@0 147 NS_ENSURE_SUCCESS(rv, rv);
michael@0 148
michael@0 149 nsString cacheFileName = NS_LITERAL_STRING(ASMJSCACHE_ENTRY_FILE_NAME_BASE);
michael@0 150 cacheFileName.AppendInt(aModuleIndex);
michael@0 151 rv = cacheFile->Append(cacheFileName);
michael@0 152 NS_ENSURE_SUCCESS(rv, rv);
michael@0 153
michael@0 154 cacheFile.forget(aCacheFile);
michael@0 155 return NS_OK;
michael@0 156 }
michael@0 157
michael@0 158 class AutoDecreaseUsageForOrigin
michael@0 159 {
michael@0 160 const nsACString& mGroup;
michael@0 161 const nsACString& mOrigin;
michael@0 162
michael@0 163 public:
michael@0 164 uint64_t mFreed;
michael@0 165
michael@0 166 AutoDecreaseUsageForOrigin(const nsACString& aGroup,
michael@0 167 const nsACString& aOrigin)
michael@0 168
michael@0 169 : mGroup(aGroup),
michael@0 170 mOrigin(aOrigin),
michael@0 171 mFreed(0)
michael@0 172 { }
michael@0 173
michael@0 174 ~AutoDecreaseUsageForOrigin()
michael@0 175 {
michael@0 176 AssertIsOnIOThread();
michael@0 177
michael@0 178 if (!mFreed) {
michael@0 179 return;
michael@0 180 }
michael@0 181
michael@0 182 QuotaManager* qm = QuotaManager::Get();
michael@0 183 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
michael@0 184
michael@0 185 qm->DecreaseUsageForOrigin(quota::PERSISTENCE_TYPE_TEMPORARY,
michael@0 186 mGroup, mOrigin, mFreed);
michael@0 187 }
michael@0 188 };
michael@0 189
michael@0 190 static void
michael@0 191 EvictEntries(nsIFile* aDirectory, const nsACString& aGroup,
michael@0 192 const nsACString& aOrigin, uint64_t aNumBytes,
michael@0 193 Metadata& aMetadata)
michael@0 194 {
michael@0 195 AssertIsOnIOThread();
michael@0 196
michael@0 197 AutoDecreaseUsageForOrigin usage(aGroup, aOrigin);
michael@0 198
michael@0 199 for (int i = Metadata::kLastEntry; i >= 0 && usage.mFreed < aNumBytes; i--) {
michael@0 200 Metadata::Entry& entry = aMetadata.mEntries[i];
michael@0 201 unsigned moduleIndex = entry.mModuleIndex;
michael@0 202
michael@0 203 nsCOMPtr<nsIFile> file;
michael@0 204 nsresult rv = GetCacheFile(aDirectory, moduleIndex, getter_AddRefs(file));
michael@0 205 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 206 return;
michael@0 207 }
michael@0 208
michael@0 209 bool exists;
michael@0 210 rv = file->Exists(&exists);
michael@0 211 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 212 return;
michael@0 213 }
michael@0 214
michael@0 215 if (exists) {
michael@0 216 int64_t fileSize;
michael@0 217 rv = file->GetFileSize(&fileSize);
michael@0 218 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 219 return;
michael@0 220 }
michael@0 221
michael@0 222 rv = file->Remove(false);
michael@0 223 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 224 return;
michael@0 225 }
michael@0 226
michael@0 227 usage.mFreed += fileSize;
michael@0 228 }
michael@0 229
michael@0 230 entry.clear();
michael@0 231 }
michael@0 232 }
michael@0 233
michael@0 234 // FileDescriptorHolder owns a file descriptor and its memory mapping.
michael@0 235 // FileDescriptorHolder is derived by all three runnable classes (that is,
michael@0 236 // (Single|Parent|Child)ProcessRunnable. To avoid awkward workarouds,
michael@0 237 // FileDescriptorHolder is derived virtually by File and MainProcessRunnable for
michael@0 238 // the benefit of SingleProcessRunnable, which derives both. Since File and
michael@0 239 // MainProcessRunnable both need to be runnables, FileDescriptorHolder also
michael@0 240 // derives nsRunnable.
michael@0 241 class FileDescriptorHolder : public nsRunnable
michael@0 242 {
michael@0 243 public:
michael@0 244 FileDescriptorHolder()
michael@0 245 : mQuotaObject(nullptr),
michael@0 246 mFileSize(INT64_MIN),
michael@0 247 mFileDesc(nullptr),
michael@0 248 mFileMap(nullptr),
michael@0 249 mMappedMemory(nullptr)
michael@0 250 { }
michael@0 251
michael@0 252 ~FileDescriptorHolder()
michael@0 253 {
michael@0 254 // These resources should have already been released by Finish().
michael@0 255 MOZ_ASSERT(!mQuotaObject);
michael@0 256 MOZ_ASSERT(!mMappedMemory);
michael@0 257 MOZ_ASSERT(!mFileMap);
michael@0 258 MOZ_ASSERT(!mFileDesc);
michael@0 259 }
michael@0 260
michael@0 261 size_t
michael@0 262 FileSize() const
michael@0 263 {
michael@0 264 MOZ_ASSERT(mFileSize >= 0, "Accessing FileSize of unopened file");
michael@0 265 return mFileSize;
michael@0 266 }
michael@0 267
michael@0 268 PRFileDesc*
michael@0 269 FileDesc() const
michael@0 270 {
michael@0 271 MOZ_ASSERT(mFileDesc, "Accessing FileDesc of unopened file");
michael@0 272 return mFileDesc;
michael@0 273 }
michael@0 274
michael@0 275 bool
michael@0 276 MapMemory(OpenMode aOpenMode)
michael@0 277 {
michael@0 278 MOZ_ASSERT(!mFileMap, "Cannot call MapMemory twice");
michael@0 279
michael@0 280 PRFileMapProtect mapFlags = aOpenMode == eOpenForRead ? PR_PROT_READONLY
michael@0 281 : PR_PROT_READWRITE;
michael@0 282
michael@0 283 mFileMap = PR_CreateFileMap(mFileDesc, mFileSize, mapFlags);
michael@0 284 NS_ENSURE_TRUE(mFileMap, false);
michael@0 285
michael@0 286 mMappedMemory = PR_MemMap(mFileMap, 0, mFileSize);
michael@0 287 NS_ENSURE_TRUE(mMappedMemory, false);
michael@0 288
michael@0 289 return true;
michael@0 290 }
michael@0 291
michael@0 292 void*
michael@0 293 MappedMemory() const
michael@0 294 {
michael@0 295 MOZ_ASSERT(mMappedMemory, "Accessing MappedMemory of un-mapped file");
michael@0 296 return mMappedMemory;
michael@0 297 }
michael@0 298
michael@0 299 protected:
michael@0 300 // This method must be called before AllowNextSynchronizedOp (which releases
michael@0 301 // the lock protecting these resources). It is idempotent, so it is ok to call
michael@0 302 // multiple times (or before the file has been fully opened).
michael@0 303 void
michael@0 304 Finish()
michael@0 305 {
michael@0 306 if (mMappedMemory) {
michael@0 307 PR_MemUnmap(mMappedMemory, mFileSize);
michael@0 308 mMappedMemory = nullptr;
michael@0 309 }
michael@0 310 if (mFileMap) {
michael@0 311 PR_CloseFileMap(mFileMap);
michael@0 312 mFileMap = nullptr;
michael@0 313 }
michael@0 314 if (mFileDesc) {
michael@0 315 PR_Close(mFileDesc);
michael@0 316 mFileDesc = nullptr;
michael@0 317 }
michael@0 318
michael@0 319 // Holding the QuotaObject alive until all the cache files are closed enables
michael@0 320 // assertions in QuotaManager that the cache entry isn't cleared while we
michael@0 321 // are working on it.
michael@0 322 mQuotaObject = nullptr;
michael@0 323 }
michael@0 324
michael@0 325 nsRefPtr<QuotaObject> mQuotaObject;
michael@0 326 int64_t mFileSize;
michael@0 327 PRFileDesc* mFileDesc;
michael@0 328 PRFileMap* mFileMap;
michael@0 329 void* mMappedMemory;
michael@0 330 };
michael@0 331
michael@0 332 // File is a base class shared by (Single|Client)ProcessEntryRunnable that
michael@0 333 // presents a single interface to the AsmJSCache ops which need to wait until
michael@0 334 // the file is open, regardless of whether we are executing in the main process
michael@0 335 // or not.
michael@0 336 class File : public virtual FileDescriptorHolder
michael@0 337 {
michael@0 338 public:
michael@0 339 class AutoClose
michael@0 340 {
michael@0 341 File* mFile;
michael@0 342
michael@0 343 public:
michael@0 344 explicit AutoClose(File* aFile = nullptr)
michael@0 345 : mFile(aFile)
michael@0 346 { }
michael@0 347
michael@0 348 void
michael@0 349 Init(File* aFile)
michael@0 350 {
michael@0 351 MOZ_ASSERT(!mFile);
michael@0 352 mFile = aFile;
michael@0 353 }
michael@0 354
michael@0 355 File*
michael@0 356 operator->() const
michael@0 357 {
michael@0 358 MOZ_ASSERT(mFile);
michael@0 359 return mFile;
michael@0 360 }
michael@0 361
michael@0 362 void
michael@0 363 Forget(File** aFile)
michael@0 364 {
michael@0 365 *aFile = mFile;
michael@0 366 mFile = nullptr;
michael@0 367 }
michael@0 368
michael@0 369 ~AutoClose()
michael@0 370 {
michael@0 371 if (mFile) {
michael@0 372 mFile->Close();
michael@0 373 }
michael@0 374 }
michael@0 375 };
michael@0 376
michael@0 377 bool
michael@0 378 BlockUntilOpen(AutoClose* aCloser)
michael@0 379 {
michael@0 380 MOZ_ASSERT(!mWaiting, "Can only call BlockUntilOpen once");
michael@0 381 MOZ_ASSERT(!mOpened, "Can only call BlockUntilOpen once");
michael@0 382
michael@0 383 mWaiting = true;
michael@0 384
michael@0 385 nsresult rv = NS_DispatchToMainThread(this);
michael@0 386 NS_ENSURE_SUCCESS(rv, false);
michael@0 387
michael@0 388 {
michael@0 389 MutexAutoLock lock(mMutex);
michael@0 390 while (mWaiting) {
michael@0 391 mCondVar.Wait();
michael@0 392 }
michael@0 393 }
michael@0 394
michael@0 395 if (!mOpened) {
michael@0 396 return false;
michael@0 397 }
michael@0 398
michael@0 399 // Now that we're open, we're guarnateed a Close() call. However, we are
michael@0 400 // not guarnateed someone is holding an outstanding reference until the File
michael@0 401 // is closed, so we do that ourselves and Release() in OnClose().
michael@0 402 aCloser->Init(this);
michael@0 403 AddRef();
michael@0 404 return true;
michael@0 405 }
michael@0 406
michael@0 407 // This method must be called if BlockUntilOpen returns 'true'. AutoClose
michael@0 408 // mostly takes care of this. A derived class that implements Close() must
michael@0 409 // guarnatee that OnClose() is called (eventually).
michael@0 410 virtual void
michael@0 411 Close() = 0;
michael@0 412
michael@0 413 protected:
michael@0 414 File()
michael@0 415 : mMutex("File::mMutex"),
michael@0 416 mCondVar(mMutex, "File::mCondVar"),
michael@0 417 mWaiting(false),
michael@0 418 mOpened(false)
michael@0 419 { }
michael@0 420
michael@0 421 ~File()
michael@0 422 {
michael@0 423 MOZ_ASSERT(!mWaiting, "Shouldn't be destroyed while thread is waiting");
michael@0 424 MOZ_ASSERT(!mOpened, "OnClose() should have been called");
michael@0 425 }
michael@0 426
michael@0 427 void
michael@0 428 OnOpen()
michael@0 429 {
michael@0 430 Notify(true);
michael@0 431 }
michael@0 432
michael@0 433 void
michael@0 434 OnFailure()
michael@0 435 {
michael@0 436 FileDescriptorHolder::Finish();
michael@0 437
michael@0 438 Notify(false);
michael@0 439 }
michael@0 440
michael@0 441 void
michael@0 442 OnClose()
michael@0 443 {
michael@0 444 FileDescriptorHolder::Finish();
michael@0 445
michael@0 446 MOZ_ASSERT(mOpened);
michael@0 447 mOpened = false;
michael@0 448
michael@0 449 // Match the AddRef in BlockUntilOpen(). The main thread event loop still
michael@0 450 // holds an outstanding ref which will keep 'this' alive until returning to
michael@0 451 // the event loop.
michael@0 452 Release();
michael@0 453 }
michael@0 454
michael@0 455 private:
michael@0 456 void
michael@0 457 Notify(bool aSuccess)
michael@0 458 {
michael@0 459 MOZ_ASSERT(NS_IsMainThread());
michael@0 460
michael@0 461 MutexAutoLock lock(mMutex);
michael@0 462 MOZ_ASSERT(mWaiting);
michael@0 463
michael@0 464 mWaiting = false;
michael@0 465 mOpened = aSuccess;
michael@0 466 mCondVar.Notify();
michael@0 467 }
michael@0 468
michael@0 469 Mutex mMutex;
michael@0 470 CondVar mCondVar;
michael@0 471 bool mWaiting;
michael@0 472 bool mOpened;
michael@0 473 };
michael@0 474
michael@0 475 // MainProcessRunnable is a base class shared by (Single|Parent)ProcessRunnable
michael@0 476 // that factors out the runnable state machine required to open a cache entry
michael@0 477 // that runs in the main process.
michael@0 478 class MainProcessRunnable : public virtual FileDescriptorHolder
michael@0 479 {
michael@0 480 public:
michael@0 481 NS_DECL_NSIRUNNABLE
michael@0 482
michael@0 483 // MainProcessRunnable runnable assumes that the derived class ensures
michael@0 484 // (through ref-counting or preconditions) that aPrincipal is kept alive for
michael@0 485 // the lifetime of the MainProcessRunnable.
michael@0 486 MainProcessRunnable(nsIPrincipal* aPrincipal,
michael@0 487 OpenMode aOpenMode,
michael@0 488 WriteParams aWriteParams)
michael@0 489 : mPrincipal(aPrincipal),
michael@0 490 mOpenMode(aOpenMode),
michael@0 491 mWriteParams(aWriteParams),
michael@0 492 mNeedAllowNextSynchronizedOp(false),
michael@0 493 mPersistence(quota::PERSISTENCE_TYPE_INVALID),
michael@0 494 mState(eInitial)
michael@0 495 {
michael@0 496 MOZ_ASSERT(IsMainProcess());
michael@0 497 }
michael@0 498
michael@0 499 virtual ~MainProcessRunnable()
michael@0 500 {
michael@0 501 MOZ_ASSERT(mState == eFinished);
michael@0 502 MOZ_ASSERT(!mNeedAllowNextSynchronizedOp);
michael@0 503 }
michael@0 504
michael@0 505 protected:
michael@0 506 // This method is called by the derived class on the main thread when a
michael@0 507 // cache entry has been selected to open.
michael@0 508 void
michael@0 509 OpenForRead(unsigned aModuleIndex)
michael@0 510 {
michael@0 511 MOZ_ASSERT(NS_IsMainThread());
michael@0 512 MOZ_ASSERT(mState == eWaitingToOpenCacheFileForRead);
michael@0 513 MOZ_ASSERT(mOpenMode == eOpenForRead);
michael@0 514
michael@0 515 mModuleIndex = aModuleIndex;
michael@0 516 mState = eReadyToOpenCacheFileForRead;
michael@0 517 DispatchToIOThread();
michael@0 518 }
michael@0 519
michael@0 520 // This method is called by the derived class on the main thread when no cache
michael@0 521 // entry was found to open. If we just tried a lookup in persistent storage
michael@0 522 // then we might still get a hit in temporary storage (for an asm.js module
michael@0 523 // that wasn't compiled at install-time).
michael@0 524 void
michael@0 525 CacheMiss()
michael@0 526 {
michael@0 527 MOZ_ASSERT(NS_IsMainThread());
michael@0 528 MOZ_ASSERT(mState == eFailedToReadMetadata ||
michael@0 529 mState == eWaitingToOpenCacheFileForRead);
michael@0 530 MOZ_ASSERT(mOpenMode == eOpenForRead);
michael@0 531
michael@0 532 if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
michael@0 533 Fail();
michael@0 534 return;
michael@0 535 }
michael@0 536
michael@0 537 // Try again with a clean slate. InitOnMainThread will see that mPersistence
michael@0 538 // is initialized and switch to temporary storage.
michael@0 539 MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
michael@0 540 FinishOnMainThread();
michael@0 541 mState = eInitial;
michael@0 542 NS_DispatchToMainThread(this);
michael@0 543 }
michael@0 544
michael@0 545 // This method is called by the derived class (either on the JS compilation
michael@0 546 // thread or the main thread) when the JS engine is finished reading/writing
michael@0 547 // the cache entry.
michael@0 548 void
michael@0 549 Close()
michael@0 550 {
michael@0 551 MOZ_ASSERT(mState == eOpened);
michael@0 552 mState = eClosing;
michael@0 553 NS_DispatchToMainThread(this);
michael@0 554 }
michael@0 555
michael@0 556 // This method is called both internally and by derived classes upon any
michael@0 557 // failure that prevents the eventual opening of the cache entry.
michael@0 558 void
michael@0 559 Fail()
michael@0 560 {
michael@0 561 MOZ_ASSERT(mState != eOpened &&
michael@0 562 mState != eClosing &&
michael@0 563 mState != eFailing &&
michael@0 564 mState != eFinished);
michael@0 565
michael@0 566 mState = eFailing;
michael@0 567 NS_DispatchToMainThread(this);
michael@0 568 }
michael@0 569
michael@0 570 // Called by MainProcessRunnable on the main thread after metadata is open:
michael@0 571 virtual void
michael@0 572 OnOpenMetadataForRead(const Metadata& aMetadata) = 0;
michael@0 573
michael@0 574 // Called by MainProcessRunnable on the main thread after the entry is open:
michael@0 575 virtual void
michael@0 576 OnOpenCacheFile() = 0;
michael@0 577
michael@0 578 // This method may be overridden, but it must be called from the overrider.
michael@0 579 // Called by MainProcessRunnable on the main thread after a call to Fail():
michael@0 580 virtual void
michael@0 581 OnFailure()
michael@0 582 {
michael@0 583 FinishOnMainThread();
michael@0 584 }
michael@0 585
michael@0 586 // This method may be overridden, but it must be called from the overrider.
michael@0 587 // Called by MainProcessRunnable on the main thread after a call to Close():
michael@0 588 virtual void
michael@0 589 OnClose()
michael@0 590 {
michael@0 591 FinishOnMainThread();
michael@0 592 }
michael@0 593
michael@0 594 private:
michael@0 595 nsresult
michael@0 596 InitOnMainThread();
michael@0 597
michael@0 598 nsresult
michael@0 599 ReadMetadata();
michael@0 600
michael@0 601 nsresult
michael@0 602 OpenCacheFileForWrite();
michael@0 603
michael@0 604 nsresult
michael@0 605 OpenCacheFileForRead();
michael@0 606
michael@0 607 void
michael@0 608 FinishOnMainThread();
michael@0 609
michael@0 610 void
michael@0 611 DispatchToIOThread()
michael@0 612 {
michael@0 613 // If shutdown just started, the QuotaManager may have been deleted.
michael@0 614 QuotaManager* qm = QuotaManager::Get();
michael@0 615 if (!qm) {
michael@0 616 Fail();
michael@0 617 return;
michael@0 618 }
michael@0 619
michael@0 620 nsresult rv = qm->IOThread()->Dispatch(this, NS_DISPATCH_NORMAL);
michael@0 621 if (NS_FAILED(rv)) {
michael@0 622 Fail();
michael@0 623 return;
michael@0 624 }
michael@0 625 }
michael@0 626
michael@0 627 nsIPrincipal* const mPrincipal;
michael@0 628 const OpenMode mOpenMode;
michael@0 629 const WriteParams mWriteParams;
michael@0 630
michael@0 631 // State initialized during eInitial:
michael@0 632 bool mNeedAllowNextSynchronizedOp;
michael@0 633 quota::PersistenceType mPersistence;
michael@0 634 nsCString mGroup;
michael@0 635 nsCString mOrigin;
michael@0 636 nsCString mStorageId;
michael@0 637
michael@0 638 // State initialized during eReadyToReadMetadata
michael@0 639 nsCOMPtr<nsIFile> mDirectory;
michael@0 640 nsCOMPtr<nsIFile> mMetadataFile;
michael@0 641 Metadata mMetadata;
michael@0 642
michael@0 643 // State initialized during eWaitingToOpenCacheFileForRead
michael@0 644 unsigned mModuleIndex;
michael@0 645
michael@0 646 enum State {
michael@0 647 eInitial, // Just created, waiting to be dispatched to main thread
michael@0 648 eWaitingToOpenMetadata, // Waiting to be called back from WaitForOpenAllowed
michael@0 649 eReadyToReadMetadata, // Waiting to read the metadata file on the IO thread
michael@0 650 eFailedToReadMetadata, // Waiting to be dispatched to main thread after fail
michael@0 651 eSendingMetadataForRead, // Waiting to send OnOpenMetadataForRead
michael@0 652 eWaitingToOpenCacheFileForRead, // Waiting to hear back from child
michael@0 653 eReadyToOpenCacheFileForRead, // Waiting to open cache file for read
michael@0 654 eSendingCacheFile, // Waiting to send OnOpenCacheFile on the main thread
michael@0 655 eOpened, // Finished calling OnOpen, waiting to be closed
michael@0 656 eClosing, // Waiting to be dispatched to main thread again
michael@0 657 eFailing, // Just failed, waiting to be dispatched to the main thread
michael@0 658 eFinished, // Terminal state
michael@0 659 };
michael@0 660 State mState;
michael@0 661 };
michael@0 662
michael@0 663 nsresult
michael@0 664 MainProcessRunnable::InitOnMainThread()
michael@0 665 {
michael@0 666 MOZ_ASSERT(NS_IsMainThread());
michael@0 667 MOZ_ASSERT(mState == eInitial);
michael@0 668
michael@0 669 QuotaManager* qm = QuotaManager::GetOrCreate();
michael@0 670 NS_ENSURE_STATE(qm);
michael@0 671
michael@0 672 nsresult rv = QuotaManager::GetInfoFromPrincipal(mPrincipal, &mGroup,
michael@0 673 &mOrigin, nullptr, nullptr);
michael@0 674 NS_ENSURE_SUCCESS(rv, rv);
michael@0 675
michael@0 676 bool isApp = mPrincipal->GetAppStatus() !=
michael@0 677 nsIPrincipal::APP_STATUS_NOT_INSTALLED;
michael@0 678
michael@0 679 if (mOpenMode == eOpenForWrite) {
michael@0 680 MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_INVALID);
michael@0 681 if (mWriteParams.mInstalled) {
michael@0 682 // If we are performing install-time caching of an app, we'd like to store
michael@0 683 // the cache entry in persistent storage so the entry is never evicted,
michael@0 684 // but we need to verify that the app has unlimited storage permissions
michael@0 685 // first. Unlimited storage permissions justify us in skipping all quota
michael@0 686 // checks when storing the cache entry and avoids all the issues around
michael@0 687 // the persistent quota prompt.
michael@0 688 MOZ_ASSERT(isApp);
michael@0 689
michael@0 690 nsCOMPtr<nsIPermissionManager> pm =
michael@0 691 do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
michael@0 692 NS_ENSURE_TRUE(pm, NS_ERROR_UNEXPECTED);
michael@0 693
michael@0 694 uint32_t permission;
michael@0 695 rv = pm->TestPermissionFromPrincipal(mPrincipal,
michael@0 696 PERMISSION_STORAGE_UNLIMITED,
michael@0 697 &permission);
michael@0 698 NS_ENSURE_SUCCESS(rv, NS_ERROR_UNEXPECTED);
michael@0 699
michael@0 700 // If app doens't have the unlimited storage permission, we can still
michael@0 701 // cache in temporary for a likely good first-run experience.
michael@0 702 mPersistence = permission == nsIPermissionManager::ALLOW_ACTION
michael@0 703 ? quota::PERSISTENCE_TYPE_PERSISTENT
michael@0 704 : quota::PERSISTENCE_TYPE_TEMPORARY;
michael@0 705 } else {
michael@0 706 mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
michael@0 707 }
michael@0 708 } else {
michael@0 709 // For the reasons described above, apps may have cache entries in both
michael@0 710 // persistent and temporary storage. At lookup time we don't know how and
michael@0 711 // where the given script was cached, so start the search in persistent
michael@0 712 // storage and, if that fails, search in temporary storage. (Non-apps can
michael@0 713 // only be stored in temporary storage.)
michael@0 714 if (mPersistence == quota::PERSISTENCE_TYPE_INVALID) {
michael@0 715 mPersistence = isApp ? quota::PERSISTENCE_TYPE_PERSISTENT
michael@0 716 : quota::PERSISTENCE_TYPE_TEMPORARY;
michael@0 717 } else {
michael@0 718 MOZ_ASSERT(isApp);
michael@0 719 MOZ_ASSERT(mPersistence == quota::PERSISTENCE_TYPE_PERSISTENT);
michael@0 720 mPersistence = quota::PERSISTENCE_TYPE_TEMPORARY;
michael@0 721 }
michael@0 722 }
michael@0 723
michael@0 724 QuotaManager::GetStorageId(mPersistence, mOrigin, quota::Client::ASMJS,
michael@0 725 NS_LITERAL_STRING("asmjs"), mStorageId);
michael@0 726
michael@0 727 return NS_OK;
michael@0 728 }
michael@0 729
michael@0 730 nsresult
michael@0 731 MainProcessRunnable::ReadMetadata()
michael@0 732 {
michael@0 733 AssertIsOnIOThread();
michael@0 734 MOZ_ASSERT(mState == eReadyToReadMetadata);
michael@0 735
michael@0 736 QuotaManager* qm = QuotaManager::Get();
michael@0 737 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
michael@0 738
michael@0 739 // Only track quota for temporary storage. For persistent storage, we've
michael@0 740 // already checked that we have unlimited-storage permissions.
michael@0 741 bool trackQuota = mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY;
michael@0 742
michael@0 743 nsresult rv = qm->EnsureOriginIsInitialized(mPersistence, mGroup, mOrigin,
michael@0 744 trackQuota,
michael@0 745 getter_AddRefs(mDirectory));
michael@0 746 NS_ENSURE_SUCCESS(rv, rv);
michael@0 747
michael@0 748 rv = mDirectory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
michael@0 749 NS_ENSURE_SUCCESS(rv, rv);
michael@0 750
michael@0 751 bool exists;
michael@0 752 rv = mDirectory->Exists(&exists);
michael@0 753 NS_ENSURE_SUCCESS(rv, rv);
michael@0 754
michael@0 755 if (!exists) {
michael@0 756 rv = mDirectory->Create(nsIFile::DIRECTORY_TYPE, 0755);
michael@0 757 NS_ENSURE_SUCCESS(rv, rv);
michael@0 758 } else {
michael@0 759 DebugOnly<bool> isDirectory;
michael@0 760 MOZ_ASSERT(NS_SUCCEEDED(mDirectory->IsDirectory(&isDirectory)));
michael@0 761 MOZ_ASSERT(isDirectory, "Should have caught this earlier!");
michael@0 762 }
michael@0 763
michael@0 764 rv = mDirectory->Clone(getter_AddRefs(mMetadataFile));
michael@0 765 NS_ENSURE_SUCCESS(rv, rv);
michael@0 766
michael@0 767 rv = mMetadataFile->Append(NS_LITERAL_STRING(ASMJSCACHE_METADATA_FILE_NAME));
michael@0 768 NS_ENSURE_SUCCESS(rv, rv);
michael@0 769
michael@0 770 rv = mMetadataFile->Exists(&exists);
michael@0 771 NS_ENSURE_SUCCESS(rv, rv);
michael@0 772
michael@0 773 if (exists && NS_FAILED(ReadMetadataFile(mMetadataFile, mMetadata))) {
michael@0 774 exists = false;
michael@0 775 }
michael@0 776
michael@0 777 if (!exists) {
michael@0 778 // If we are reading, we can't possibly have a cache hit.
michael@0 779 if (mOpenMode == eOpenForRead) {
michael@0 780 return NS_ERROR_FILE_NOT_FOUND;
michael@0 781 }
michael@0 782
michael@0 783 // Initialize Metadata with a valid empty state for the LRU cache.
michael@0 784 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
michael@0 785 Metadata::Entry& entry = mMetadata.mEntries[i];
michael@0 786 entry.mModuleIndex = i;
michael@0 787 entry.clear();
michael@0 788 }
michael@0 789 }
michael@0 790
michael@0 791 return NS_OK;
michael@0 792 }
michael@0 793
michael@0 794 nsresult
michael@0 795 MainProcessRunnable::OpenCacheFileForWrite()
michael@0 796 {
michael@0 797 AssertIsOnIOThread();
michael@0 798 MOZ_ASSERT(mState == eReadyToReadMetadata);
michael@0 799 MOZ_ASSERT(mOpenMode == eOpenForWrite);
michael@0 800
michael@0 801 mFileSize = mWriteParams.mSize;
michael@0 802
michael@0 803 // Kick out the oldest entry in the LRU queue in the metadata.
michael@0 804 mModuleIndex = mMetadata.mEntries[Metadata::kLastEntry].mModuleIndex;
michael@0 805
michael@0 806 nsCOMPtr<nsIFile> file;
michael@0 807 nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
michael@0 808 NS_ENSURE_SUCCESS(rv, rv);
michael@0 809
michael@0 810 QuotaManager* qm = QuotaManager::Get();
michael@0 811 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
michael@0 812
michael@0 813 // If we are allocating in temporary storage, ask the QuotaManager if we're
michael@0 814 // within the quota. If we are allocating in persistent storage, we've already
michael@0 815 // checked that we have the unlimited-storage permission, so there is nothing
michael@0 816 // to check.
michael@0 817 if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
michael@0 818 // Create the QuotaObject before all file IO and keep it alive until caching
michael@0 819 // completes to get maximum assertion coverage in QuotaManager against
michael@0 820 // concurrent removal, etc.
michael@0 821 mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
michael@0 822 NS_ENSURE_STATE(mQuotaObject);
michael@0 823
michael@0 824 if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
michael@0 825 // If the request fails, it might be because mOrigin is using too much
michael@0 826 // space (MaybeAllocateMoreSpace will not evict our own origin since it is
michael@0 827 // active). Try to make some space by evicting LRU entries until there is
michael@0 828 // enough space.
michael@0 829 EvictEntries(mDirectory, mGroup, mOrigin, mWriteParams.mSize, mMetadata);
michael@0 830 if (!mQuotaObject->MaybeAllocateMoreSpace(0, mWriteParams.mSize)) {
michael@0 831 return NS_ERROR_FAILURE;
michael@0 832 }
michael@0 833 }
michael@0 834 }
michael@0 835
michael@0 836 int32_t openFlags = PR_RDWR | PR_TRUNCATE | PR_CREATE_FILE;
michael@0 837 rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
michael@0 838 NS_ENSURE_SUCCESS(rv, rv);
michael@0 839
michael@0 840 // Move the mModuleIndex's LRU entry to the recent end of the queue.
michael@0 841 PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, Metadata::kLastEntry);
michael@0 842 Metadata::Entry& entry = mMetadata.mEntries[0];
michael@0 843 entry.mFastHash = mWriteParams.mFastHash;
michael@0 844 entry.mNumChars = mWriteParams.mNumChars;
michael@0 845 entry.mFullHash = mWriteParams.mFullHash;
michael@0 846 entry.mModuleIndex = mModuleIndex;
michael@0 847
michael@0 848 rv = WriteMetadataFile(mMetadataFile, mMetadata);
michael@0 849 NS_ENSURE_SUCCESS(rv, rv);
michael@0 850
michael@0 851 return NS_OK;
michael@0 852 }
michael@0 853
michael@0 854 nsresult
michael@0 855 MainProcessRunnable::OpenCacheFileForRead()
michael@0 856 {
michael@0 857 AssertIsOnIOThread();
michael@0 858 MOZ_ASSERT(mState == eReadyToOpenCacheFileForRead);
michael@0 859 MOZ_ASSERT(mOpenMode == eOpenForRead);
michael@0 860
michael@0 861 nsCOMPtr<nsIFile> file;
michael@0 862 nsresult rv = GetCacheFile(mDirectory, mModuleIndex, getter_AddRefs(file));
michael@0 863 NS_ENSURE_SUCCESS(rv, rv);
michael@0 864
michael@0 865 QuotaManager* qm = QuotaManager::Get();
michael@0 866 MOZ_ASSERT(qm, "We are on the QuotaManager's IO thread");
michael@0 867
michael@0 868 if (mPersistence == quota::PERSISTENCE_TYPE_TEMPORARY) {
michael@0 869 // Even though it's not strictly necessary, create the QuotaObject before
michael@0 870 // all file IO and keep it alive until caching completes to get maximum
michael@0 871 // assertion coverage in QuotaManager against concurrent removal, etc.
michael@0 872 mQuotaObject = qm->GetQuotaObject(mPersistence, mGroup, mOrigin, file);
michael@0 873 NS_ENSURE_STATE(mQuotaObject);
michael@0 874 }
michael@0 875
michael@0 876 rv = file->GetFileSize(&mFileSize);
michael@0 877 NS_ENSURE_SUCCESS(rv, rv);
michael@0 878
michael@0 879 int32_t openFlags = PR_RDONLY | nsIFile::OS_READAHEAD;
michael@0 880 rv = file->OpenNSPRFileDesc(openFlags, 0644, &mFileDesc);
michael@0 881 NS_ENSURE_SUCCESS(rv, rv);
michael@0 882
michael@0 883 // Move the mModuleIndex's LRU entry to the recent end of the queue.
michael@0 884 unsigned lruIndex = 0;
michael@0 885 while (mMetadata.mEntries[lruIndex].mModuleIndex != mModuleIndex) {
michael@0 886 if (++lruIndex == Metadata::kNumEntries) {
michael@0 887 return NS_ERROR_UNEXPECTED;
michael@0 888 }
michael@0 889 }
michael@0 890 Metadata::Entry entry = mMetadata.mEntries[lruIndex];
michael@0 891 PodMove(mMetadata.mEntries + 1, mMetadata.mEntries, lruIndex);
michael@0 892 mMetadata.mEntries[0] = entry;
michael@0 893
michael@0 894 rv = WriteMetadataFile(mMetadataFile, mMetadata);
michael@0 895 NS_ENSURE_SUCCESS(rv, rv);
michael@0 896
michael@0 897 return NS_OK;
michael@0 898 }
michael@0 899
michael@0 900 void
michael@0 901 MainProcessRunnable::FinishOnMainThread()
michael@0 902 {
michael@0 903 MOZ_ASSERT(NS_IsMainThread());
michael@0 904
michael@0 905 // Per FileDescriptorHolder::Finish()'s comment, call before
michael@0 906 // AllowNextSynchronizedOp.
michael@0 907 FileDescriptorHolder::Finish();
michael@0 908
michael@0 909 if (mNeedAllowNextSynchronizedOp) {
michael@0 910 mNeedAllowNextSynchronizedOp = false;
michael@0 911 QuotaManager* qm = QuotaManager::Get();
michael@0 912 if (qm) {
michael@0 913 qm->AllowNextSynchronizedOp(OriginOrPatternString::FromOrigin(mOrigin),
michael@0 914 Nullable<PersistenceType>(mPersistence),
michael@0 915 mStorageId);
michael@0 916 }
michael@0 917 }
michael@0 918 }
michael@0 919
michael@0 920 NS_IMETHODIMP
michael@0 921 MainProcessRunnable::Run()
michael@0 922 {
michael@0 923 nsresult rv;
michael@0 924
michael@0 925 // All success/failure paths must eventually call Finish() to avoid leaving
michael@0 926 // the parser hanging.
michael@0 927 switch (mState) {
michael@0 928 case eInitial: {
michael@0 929 MOZ_ASSERT(NS_IsMainThread());
michael@0 930
michael@0 931 rv = InitOnMainThread();
michael@0 932 if (NS_FAILED(rv)) {
michael@0 933 Fail();
michael@0 934 return NS_OK;
michael@0 935 }
michael@0 936
michael@0 937 mState = eWaitingToOpenMetadata;
michael@0 938 rv = QuotaManager::Get()->WaitForOpenAllowed(
michael@0 939 OriginOrPatternString::FromOrigin(mOrigin),
michael@0 940 Nullable<PersistenceType>(mPersistence),
michael@0 941 mStorageId, this);
michael@0 942 if (NS_FAILED(rv)) {
michael@0 943 Fail();
michael@0 944 return NS_OK;
michael@0 945 }
michael@0 946
michael@0 947 mNeedAllowNextSynchronizedOp = true;
michael@0 948 return NS_OK;
michael@0 949 }
michael@0 950
michael@0 951 case eWaitingToOpenMetadata: {
michael@0 952 MOZ_ASSERT(NS_IsMainThread());
michael@0 953
michael@0 954 mState = eReadyToReadMetadata;
michael@0 955 DispatchToIOThread();
michael@0 956 return NS_OK;
michael@0 957 }
michael@0 958
michael@0 959 case eReadyToReadMetadata: {
michael@0 960 AssertIsOnIOThread();
michael@0 961
michael@0 962 rv = ReadMetadata();
michael@0 963 if (NS_FAILED(rv)) {
michael@0 964 mState = eFailedToReadMetadata;
michael@0 965 NS_DispatchToMainThread(this);
michael@0 966 return NS_OK;
michael@0 967 }
michael@0 968
michael@0 969 if (mOpenMode == eOpenForRead) {
michael@0 970 mState = eSendingMetadataForRead;
michael@0 971 NS_DispatchToMainThread(this);
michael@0 972 return NS_OK;
michael@0 973 }
michael@0 974
michael@0 975 rv = OpenCacheFileForWrite();
michael@0 976 if (NS_FAILED(rv)) {
michael@0 977 Fail();
michael@0 978 return NS_OK;
michael@0 979 }
michael@0 980
michael@0 981 mState = eSendingCacheFile;
michael@0 982 NS_DispatchToMainThread(this);
michael@0 983 return NS_OK;
michael@0 984 }
michael@0 985
michael@0 986 case eFailedToReadMetadata: {
michael@0 987 MOZ_ASSERT(NS_IsMainThread());
michael@0 988
michael@0 989 CacheMiss();
michael@0 990 return NS_OK;
michael@0 991 }
michael@0 992
michael@0 993 case eSendingMetadataForRead: {
michael@0 994 MOZ_ASSERT(NS_IsMainThread());
michael@0 995 MOZ_ASSERT(mOpenMode == eOpenForRead);
michael@0 996
michael@0 997 mState = eWaitingToOpenCacheFileForRead;
michael@0 998 OnOpenMetadataForRead(mMetadata);
michael@0 999 return NS_OK;
michael@0 1000 }
michael@0 1001
michael@0 1002 case eReadyToOpenCacheFileForRead: {
michael@0 1003 AssertIsOnIOThread();
michael@0 1004 MOZ_ASSERT(mOpenMode == eOpenForRead);
michael@0 1005
michael@0 1006 rv = OpenCacheFileForRead();
michael@0 1007 if (NS_FAILED(rv)) {
michael@0 1008 Fail();
michael@0 1009 return NS_OK;
michael@0 1010 }
michael@0 1011
michael@0 1012 mState = eSendingCacheFile;
michael@0 1013 NS_DispatchToMainThread(this);
michael@0 1014 return NS_OK;
michael@0 1015 }
michael@0 1016
michael@0 1017 case eSendingCacheFile: {
michael@0 1018 MOZ_ASSERT(NS_IsMainThread());
michael@0 1019
michael@0 1020 mState = eOpened;
michael@0 1021 OnOpenCacheFile();
michael@0 1022 return NS_OK;
michael@0 1023 }
michael@0 1024
michael@0 1025 case eFailing: {
michael@0 1026 MOZ_ASSERT(NS_IsMainThread());
michael@0 1027
michael@0 1028 mState = eFinished;
michael@0 1029 OnFailure();
michael@0 1030 return NS_OK;
michael@0 1031 }
michael@0 1032
michael@0 1033 case eClosing: {
michael@0 1034 MOZ_ASSERT(NS_IsMainThread());
michael@0 1035
michael@0 1036 mState = eFinished;
michael@0 1037 OnClose();
michael@0 1038 return NS_OK;
michael@0 1039 }
michael@0 1040
michael@0 1041 case eWaitingToOpenCacheFileForRead:
michael@0 1042 case eOpened:
michael@0 1043 case eFinished: {
michael@0 1044 MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
michael@0 1045 }
michael@0 1046 }
michael@0 1047
michael@0 1048 MOZ_ASSUME_UNREACHABLE("Corrupt state");
michael@0 1049 return NS_OK;
michael@0 1050 }
michael@0 1051
michael@0 1052 bool
michael@0 1053 FindHashMatch(const Metadata& aMetadata, const ReadParams& aReadParams,
michael@0 1054 unsigned* aModuleIndex)
michael@0 1055 {
michael@0 1056 // Perform a fast hash of the first sNumFastHashChars chars. Each cache entry
michael@0 1057 // also stores an mFastHash of its first sNumFastHashChars so this gives us a
michael@0 1058 // fast way to probabilistically determine whether we have a cache hit. We
michael@0 1059 // still do a full hash of all the chars before returning the cache file to
michael@0 1060 // the engine to avoid penalizing the case where there are multiple cached
michael@0 1061 // asm.js modules where the first sNumFastHashChars are the same. The
michael@0 1062 // mFullHash of each cache entry can have a different mNumChars so the fast
michael@0 1063 // hash allows us to avoid performing up to Metadata::kNumEntries separate
michael@0 1064 // full hashes.
michael@0 1065 uint32_t numChars = aReadParams.mLimit - aReadParams.mBegin;
michael@0 1066 MOZ_ASSERT(numChars > sNumFastHashChars);
michael@0 1067 uint32_t fastHash = HashString(aReadParams.mBegin, sNumFastHashChars);
michael@0 1068
michael@0 1069 for (unsigned i = 0; i < Metadata::kNumEntries ; i++) {
michael@0 1070 // Compare the "fast hash" first to see whether it is worthwhile to
michael@0 1071 // hash all the chars.
michael@0 1072 Metadata::Entry entry = aMetadata.mEntries[i];
michael@0 1073 if (entry.mFastHash != fastHash) {
michael@0 1074 continue;
michael@0 1075 }
michael@0 1076
michael@0 1077 // Assuming we have enough characters, hash all the chars it would take
michael@0 1078 // to match this cache entry and compare to the cache entry. If we get a
michael@0 1079 // hit we'll still do a full source match later (in the JS engine), but
michael@0 1080 // the full hash match means this is probably the cache entry we want.
michael@0 1081 if (numChars < entry.mNumChars) {
michael@0 1082 continue;
michael@0 1083 }
michael@0 1084 uint32_t fullHash = HashString(aReadParams.mBegin, entry.mNumChars);
michael@0 1085 if (entry.mFullHash != fullHash) {
michael@0 1086 continue;
michael@0 1087 }
michael@0 1088
michael@0 1089 *aModuleIndex = entry.mModuleIndex;
michael@0 1090 return true;
michael@0 1091 }
michael@0 1092
michael@0 1093 return false;
michael@0 1094 }
michael@0 1095
michael@0 1096 // A runnable that executes for a cache access originating in the main process.
michael@0 1097 class SingleProcessRunnable MOZ_FINAL : public File,
michael@0 1098 private MainProcessRunnable
michael@0 1099 {
michael@0 1100 public:
michael@0 1101 // In the single-process case, the calling JS compilation thread holds the
michael@0 1102 // nsIPrincipal alive indirectly (via the global object -> compartment ->
michael@0 1103 // principal) so we don't have to ref-count it here. This is fortunate since
michael@0 1104 // we are off the main thread and nsIPrincipals can only be ref-counted on
michael@0 1105 // the main thread.
michael@0 1106 SingleProcessRunnable(nsIPrincipal* aPrincipal,
michael@0 1107 OpenMode aOpenMode,
michael@0 1108 WriteParams aWriteParams,
michael@0 1109 ReadParams aReadParams)
michael@0 1110 : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
michael@0 1111 mReadParams(aReadParams)
michael@0 1112 {
michael@0 1113 MOZ_ASSERT(IsMainProcess());
michael@0 1114 MOZ_ASSERT(!NS_IsMainThread());
michael@0 1115 MOZ_COUNT_CTOR(SingleProcessRunnable);
michael@0 1116 }
michael@0 1117
michael@0 1118 ~SingleProcessRunnable()
michael@0 1119 {
michael@0 1120 MOZ_COUNT_DTOR(SingleProcessRunnable);
michael@0 1121 }
michael@0 1122
michael@0 1123 private:
michael@0 1124 void
michael@0 1125 OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
michael@0 1126 {
michael@0 1127 uint32_t moduleIndex;
michael@0 1128 if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
michael@0 1129 MainProcessRunnable::OpenForRead(moduleIndex);
michael@0 1130 } else {
michael@0 1131 MainProcessRunnable::CacheMiss();
michael@0 1132 }
michael@0 1133 }
michael@0 1134
michael@0 1135 void
michael@0 1136 OnOpenCacheFile() MOZ_OVERRIDE
michael@0 1137 {
michael@0 1138 File::OnOpen();
michael@0 1139 }
michael@0 1140
michael@0 1141 void
michael@0 1142 Close() MOZ_OVERRIDE MOZ_FINAL
michael@0 1143 {
michael@0 1144 MainProcessRunnable::Close();
michael@0 1145 }
michael@0 1146
michael@0 1147 void
michael@0 1148 OnFailure() MOZ_OVERRIDE
michael@0 1149 {
michael@0 1150 MainProcessRunnable::OnFailure();
michael@0 1151 File::OnFailure();
michael@0 1152 }
michael@0 1153
michael@0 1154 void
michael@0 1155 OnClose() MOZ_OVERRIDE MOZ_FINAL
michael@0 1156 {
michael@0 1157 MainProcessRunnable::OnClose();
michael@0 1158 File::OnClose();
michael@0 1159 }
michael@0 1160
michael@0 1161 // Avoid MSVC 'dominance' warning by having clear Run() override.
michael@0 1162 NS_IMETHODIMP
michael@0 1163 Run() MOZ_OVERRIDE
michael@0 1164 {
michael@0 1165 return MainProcessRunnable::Run();
michael@0 1166 }
michael@0 1167
michael@0 1168 ReadParams mReadParams;
michael@0 1169 };
michael@0 1170
michael@0 1171 // A runnable that executes in a parent process for a cache access originating
michael@0 1172 // in the content process. This runnable gets registered as an IPDL subprotocol
michael@0 1173 // actor so that it can communicate with the corresponding ChildProcessRunnable.
michael@0 1174 class ParentProcessRunnable MOZ_FINAL : public PAsmJSCacheEntryParent,
michael@0 1175 public MainProcessRunnable
michael@0 1176 {
michael@0 1177 public:
michael@0 1178 // The given principal comes from an IPC::Principal which will be dec-refed
michael@0 1179 // at the end of the message, so we must ref-count it here. Fortunately, we
michael@0 1180 // are on the main thread (where PContent messages are delivered).
michael@0 1181 ParentProcessRunnable(nsIPrincipal* aPrincipal,
michael@0 1182 OpenMode aOpenMode,
michael@0 1183 WriteParams aWriteParams)
michael@0 1184 : MainProcessRunnable(aPrincipal, aOpenMode, aWriteParams),
michael@0 1185 mPrincipalHolder(aPrincipal),
michael@0 1186 mActorDestroyed(false),
michael@0 1187 mOpened(false),
michael@0 1188 mFinished(false)
michael@0 1189 {
michael@0 1190 MOZ_ASSERT(IsMainProcess());
michael@0 1191 MOZ_ASSERT(NS_IsMainThread());
michael@0 1192 MOZ_COUNT_CTOR(ParentProcessRunnable);
michael@0 1193 }
michael@0 1194
michael@0 1195 private:
michael@0 1196 ~ParentProcessRunnable()
michael@0 1197 {
michael@0 1198 MOZ_ASSERT(!mPrincipalHolder, "Should have already been released");
michael@0 1199 MOZ_ASSERT(mActorDestroyed);
michael@0 1200 MOZ_ASSERT(mFinished);
michael@0 1201 MOZ_COUNT_DTOR(ParentProcessRunnable);
michael@0 1202 }
michael@0 1203
michael@0 1204 bool
michael@0 1205 Recv__delete__() MOZ_OVERRIDE
michael@0 1206 {
michael@0 1207 MOZ_ASSERT(!mFinished);
michael@0 1208 mFinished = true;
michael@0 1209
michael@0 1210 if (mOpened) {
michael@0 1211 MainProcessRunnable::Close();
michael@0 1212 } else {
michael@0 1213 MainProcessRunnable::Fail();
michael@0 1214 }
michael@0 1215
michael@0 1216 return true;
michael@0 1217 }
michael@0 1218
michael@0 1219 void
michael@0 1220 ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
michael@0 1221 {
michael@0 1222 MOZ_ASSERT(!mActorDestroyed);
michael@0 1223 mActorDestroyed = true;
michael@0 1224
michael@0 1225 // Assume ActorDestroy can happen at any time, so probe the current state to
michael@0 1226 // determine what needs to happen.
michael@0 1227
michael@0 1228 if (mFinished) {
michael@0 1229 return;
michael@0 1230 }
michael@0 1231
michael@0 1232 mFinished = true;
michael@0 1233
michael@0 1234 if (mOpened) {
michael@0 1235 MainProcessRunnable::Close();
michael@0 1236 } else {
michael@0 1237 MainProcessRunnable::Fail();
michael@0 1238 }
michael@0 1239 }
michael@0 1240
michael@0 1241 void
michael@0 1242 OnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
michael@0 1243 {
michael@0 1244 MOZ_ASSERT(NS_IsMainThread());
michael@0 1245
michael@0 1246 if (!SendOnOpenMetadataForRead(aMetadata)) {
michael@0 1247 unused << Send__delete__(this);
michael@0 1248 }
michael@0 1249 }
michael@0 1250
michael@0 1251 bool
michael@0 1252 RecvSelectCacheFileToRead(const uint32_t& aModuleIndex) MOZ_OVERRIDE
michael@0 1253 {
michael@0 1254 MainProcessRunnable::OpenForRead(aModuleIndex);
michael@0 1255 return true;
michael@0 1256 }
michael@0 1257
michael@0 1258 bool
michael@0 1259 RecvCacheMiss() MOZ_OVERRIDE
michael@0 1260 {
michael@0 1261 MainProcessRunnable::CacheMiss();
michael@0 1262 return true;
michael@0 1263 }
michael@0 1264
michael@0 1265 void
michael@0 1266 OnOpenCacheFile() MOZ_OVERRIDE
michael@0 1267 {
michael@0 1268 MOZ_ASSERT(NS_IsMainThread());
michael@0 1269
michael@0 1270 MOZ_ASSERT(!mOpened);
michael@0 1271 mOpened = true;
michael@0 1272
michael@0 1273 FileDescriptor::PlatformHandleType handle =
michael@0 1274 FileDescriptor::PlatformHandleType(PR_FileDesc2NativeHandle(mFileDesc));
michael@0 1275 if (!SendOnOpenCacheFile(mFileSize, handle)) {
michael@0 1276 unused << Send__delete__(this);
michael@0 1277 }
michael@0 1278 }
michael@0 1279
michael@0 1280 void
michael@0 1281 OnClose() MOZ_OVERRIDE MOZ_FINAL
michael@0 1282 {
michael@0 1283 MOZ_ASSERT(NS_IsMainThread());
michael@0 1284 MOZ_ASSERT(mOpened);
michael@0 1285
michael@0 1286 mFinished = true;
michael@0 1287
michael@0 1288 MainProcessRunnable::OnClose();
michael@0 1289
michael@0 1290 MOZ_ASSERT(mActorDestroyed);
michael@0 1291
michael@0 1292 mPrincipalHolder = nullptr;
michael@0 1293 }
michael@0 1294
michael@0 1295 void
michael@0 1296 OnFailure() MOZ_OVERRIDE
michael@0 1297 {
michael@0 1298 MOZ_ASSERT(NS_IsMainThread());
michael@0 1299 MOZ_ASSERT(!mOpened);
michael@0 1300
michael@0 1301 mFinished = true;
michael@0 1302
michael@0 1303 MainProcessRunnable::OnFailure();
michael@0 1304
michael@0 1305 if (!mActorDestroyed) {
michael@0 1306 unused << Send__delete__(this);
michael@0 1307 }
michael@0 1308
michael@0 1309 mPrincipalHolder = nullptr;
michael@0 1310 }
michael@0 1311
michael@0 1312 nsCOMPtr<nsIPrincipal> mPrincipalHolder;
michael@0 1313 bool mActorDestroyed;
michael@0 1314 bool mOpened;
michael@0 1315 bool mFinished;
michael@0 1316 };
michael@0 1317
michael@0 1318 } // unnamed namespace
michael@0 1319
michael@0 1320 PAsmJSCacheEntryParent*
michael@0 1321 AllocEntryParent(OpenMode aOpenMode,
michael@0 1322 WriteParams aWriteParams,
michael@0 1323 nsIPrincipal* aPrincipal)
michael@0 1324 {
michael@0 1325 ParentProcessRunnable* runnable =
michael@0 1326 new ParentProcessRunnable(aPrincipal, aOpenMode, aWriteParams);
michael@0 1327
michael@0 1328 // AddRef to keep the runnable alive until DeallocEntryParent.
michael@0 1329 runnable->AddRef();
michael@0 1330
michael@0 1331 nsresult rv = NS_DispatchToMainThread(runnable);
michael@0 1332 NS_ENSURE_SUCCESS(rv, nullptr);
michael@0 1333
michael@0 1334 return runnable;
michael@0 1335 }
michael@0 1336
michael@0 1337 void
michael@0 1338 DeallocEntryParent(PAsmJSCacheEntryParent* aActor)
michael@0 1339 {
michael@0 1340 // Match the AddRef in AllocEntryParent.
michael@0 1341 static_cast<ParentProcessRunnable*>(aActor)->Release();
michael@0 1342 }
michael@0 1343
michael@0 1344 namespace {
michael@0 1345
michael@0 1346 class ChildProcessRunnable MOZ_FINAL : public File,
michael@0 1347 public PAsmJSCacheEntryChild
michael@0 1348 {
michael@0 1349 public:
michael@0 1350 NS_DECL_NSIRUNNABLE
michael@0 1351
michael@0 1352 // In the single-process case, the calling JS compilation thread holds the
michael@0 1353 // nsIPrincipal alive indirectly (via the global object -> compartment ->
michael@0 1354 // principal) so we don't have to ref-count it here. This is fortunate since
michael@0 1355 // we are off the main thread and nsIPrincipals can only be ref-counted on
michael@0 1356 // the main thread.
michael@0 1357 ChildProcessRunnable(nsIPrincipal* aPrincipal,
michael@0 1358 OpenMode aOpenMode,
michael@0 1359 WriteParams aWriteParams,
michael@0 1360 ReadParams aReadParams)
michael@0 1361 : mPrincipal(aPrincipal),
michael@0 1362 mOpenMode(aOpenMode),
michael@0 1363 mWriteParams(aWriteParams),
michael@0 1364 mReadParams(aReadParams),
michael@0 1365 mActorDestroyed(false),
michael@0 1366 mState(eInitial)
michael@0 1367 {
michael@0 1368 MOZ_ASSERT(!IsMainProcess());
michael@0 1369 MOZ_ASSERT(!NS_IsMainThread());
michael@0 1370 MOZ_COUNT_CTOR(ChildProcessRunnable);
michael@0 1371 }
michael@0 1372
michael@0 1373 ~ChildProcessRunnable()
michael@0 1374 {
michael@0 1375 MOZ_ASSERT(mState == eFinished);
michael@0 1376 MOZ_ASSERT(mActorDestroyed);
michael@0 1377 MOZ_COUNT_DTOR(ChildProcessRunnable);
michael@0 1378 }
michael@0 1379
michael@0 1380 private:
michael@0 1381 bool
michael@0 1382 RecvOnOpenMetadataForRead(const Metadata& aMetadata) MOZ_OVERRIDE
michael@0 1383 {
michael@0 1384 MOZ_ASSERT(NS_IsMainThread());
michael@0 1385 MOZ_ASSERT(mState == eOpening);
michael@0 1386
michael@0 1387 uint32_t moduleIndex;
michael@0 1388 if (FindHashMatch(aMetadata, mReadParams, &moduleIndex)) {
michael@0 1389 return SendSelectCacheFileToRead(moduleIndex);
michael@0 1390 }
michael@0 1391
michael@0 1392 return SendCacheMiss();
michael@0 1393 }
michael@0 1394
michael@0 1395 bool
michael@0 1396 RecvOnOpenCacheFile(const int64_t& aFileSize,
michael@0 1397 const FileDescriptor& aFileDesc) MOZ_OVERRIDE
michael@0 1398 {
michael@0 1399 MOZ_ASSERT(NS_IsMainThread());
michael@0 1400 MOZ_ASSERT(mState == eOpening);
michael@0 1401
michael@0 1402 mFileSize = aFileSize;
michael@0 1403
michael@0 1404 mFileDesc = PR_ImportFile(PROsfd(aFileDesc.PlatformHandle()));
michael@0 1405 if (!mFileDesc) {
michael@0 1406 return false;
michael@0 1407 }
michael@0 1408
michael@0 1409 mState = eOpened;
michael@0 1410 File::OnOpen();
michael@0 1411 return true;
michael@0 1412 }
michael@0 1413
michael@0 1414 bool
michael@0 1415 Recv__delete__() MOZ_OVERRIDE
michael@0 1416 {
michael@0 1417 MOZ_ASSERT(NS_IsMainThread());
michael@0 1418 MOZ_ASSERT(mState == eOpening);
michael@0 1419
michael@0 1420 Fail();
michael@0 1421 return true;
michael@0 1422 }
michael@0 1423
michael@0 1424 void
michael@0 1425 ActorDestroy(ActorDestroyReason why) MOZ_OVERRIDE
michael@0 1426 {
michael@0 1427 MOZ_ASSERT(NS_IsMainThread());
michael@0 1428 mActorDestroyed = true;
michael@0 1429 }
michael@0 1430
michael@0 1431 void
michael@0 1432 Close() MOZ_OVERRIDE MOZ_FINAL
michael@0 1433 {
michael@0 1434 MOZ_ASSERT(mState == eOpened);
michael@0 1435
michael@0 1436 mState = eClosing;
michael@0 1437 NS_DispatchToMainThread(this);
michael@0 1438 }
michael@0 1439
michael@0 1440 private:
michael@0 1441 void
michael@0 1442 Fail()
michael@0 1443 {
michael@0 1444 MOZ_ASSERT(NS_IsMainThread());
michael@0 1445 MOZ_ASSERT(mState == eInitial || mState == eOpening);
michael@0 1446
michael@0 1447 mState = eFinished;
michael@0 1448 File::OnFailure();
michael@0 1449 }
michael@0 1450
michael@0 1451 nsIPrincipal* const mPrincipal;
michael@0 1452 const OpenMode mOpenMode;
michael@0 1453 WriteParams mWriteParams;
michael@0 1454 ReadParams mReadParams;
michael@0 1455 bool mActorDestroyed;
michael@0 1456
michael@0 1457 enum State {
michael@0 1458 eInitial, // Just created, waiting to dispatched to the main thread
michael@0 1459 eOpening, // Waiting for the parent process to respond
michael@0 1460 eOpened, // Parent process opened the entry and sent it back
michael@0 1461 eClosing, // Waiting to be dispatched to the main thread to Send__delete__
michael@0 1462 eFinished // Terminal state
michael@0 1463 };
michael@0 1464 State mState;
michael@0 1465 };
michael@0 1466
michael@0 1467 NS_IMETHODIMP
michael@0 1468 ChildProcessRunnable::Run()
michael@0 1469 {
michael@0 1470 switch (mState) {
michael@0 1471 case eInitial: {
michael@0 1472 MOZ_ASSERT(NS_IsMainThread());
michael@0 1473
michael@0 1474 // AddRef to keep this runnable alive until IPDL deallocates the
michael@0 1475 // subprotocol (DeallocEntryChild).
michael@0 1476 AddRef();
michael@0 1477
michael@0 1478 if (!ContentChild::GetSingleton()->SendPAsmJSCacheEntryConstructor(
michael@0 1479 this, mOpenMode, mWriteParams, IPC::Principal(mPrincipal)))
michael@0 1480 {
michael@0 1481 // On failure, undo the AddRef (since DeallocEntryChild will not be
michael@0 1482 // called) and unblock the parsing thread with a failure. The main
michael@0 1483 // thread event loop still holds an outstanding ref which will keep
michael@0 1484 // 'this' alive until returning to the event loop.
michael@0 1485 Release();
michael@0 1486
michael@0 1487 Fail();
michael@0 1488 return NS_OK;
michael@0 1489 }
michael@0 1490
michael@0 1491 mState = eOpening;
michael@0 1492 return NS_OK;
michael@0 1493 }
michael@0 1494
michael@0 1495 case eClosing: {
michael@0 1496 MOZ_ASSERT(NS_IsMainThread());
michael@0 1497
michael@0 1498 // Per FileDescriptorHolder::Finish()'s comment, call before
michael@0 1499 // AllowNextSynchronizedOp (which happens in the parent upon receipt of
michael@0 1500 // the Send__delete__ message).
michael@0 1501 File::OnClose();
michael@0 1502
michael@0 1503 if (!mActorDestroyed) {
michael@0 1504 unused << Send__delete__(this);
michael@0 1505 }
michael@0 1506
michael@0 1507 mState = eFinished;
michael@0 1508 return NS_OK;
michael@0 1509 }
michael@0 1510
michael@0 1511 case eOpening:
michael@0 1512 case eOpened:
michael@0 1513 case eFinished: {
michael@0 1514 MOZ_ASSUME_UNREACHABLE("Shouldn't Run() in this state");
michael@0 1515 }
michael@0 1516 }
michael@0 1517
michael@0 1518 MOZ_ASSUME_UNREACHABLE("Corrupt state");
michael@0 1519 return NS_OK;
michael@0 1520 }
michael@0 1521
michael@0 1522 } // unnamed namespace
michael@0 1523
michael@0 1524 void
michael@0 1525 DeallocEntryChild(PAsmJSCacheEntryChild* aActor)
michael@0 1526 {
michael@0 1527 // Match the AddRef before SendPAsmJSCacheEntryConstructor.
michael@0 1528 static_cast<ChildProcessRunnable*>(aActor)->Release();
michael@0 1529 }
michael@0 1530
michael@0 1531 namespace {
michael@0 1532
michael@0 1533 bool
michael@0 1534 OpenFile(nsIPrincipal* aPrincipal,
michael@0 1535 OpenMode aOpenMode,
michael@0 1536 WriteParams aWriteParams,
michael@0 1537 ReadParams aReadParams,
michael@0 1538 File::AutoClose* aFile)
michael@0 1539 {
michael@0 1540 MOZ_ASSERT_IF(aOpenMode == eOpenForRead, aWriteParams.mSize == 0);
michael@0 1541 MOZ_ASSERT_IF(aOpenMode == eOpenForWrite, aReadParams.mBegin == nullptr);
michael@0 1542
michael@0 1543 // There are three reasons we don't attempt caching from the main thread:
michael@0 1544 // 1. In the parent process: QuotaManager::WaitForOpenAllowed prevents
michael@0 1545 // synchronous waiting on the main thread requiring a runnable to be
michael@0 1546 // dispatched to the main thread.
michael@0 1547 // 2. In the child process: the IPDL PContent messages we need to
michael@0 1548 // synchronously wait on are dispatched to the main thread.
michael@0 1549 // 3. While a cache lookup *should* be much faster than compilation, IO
michael@0 1550 // operations can be unpredictably slow and we'd like to avoid the
michael@0 1551 // occasional janks on the main thread.
michael@0 1552 // We could use a nested event loop to address 1 and 2, but we're potentially
michael@0 1553 // in the middle of running JS (eval()) and nested event loops can be
michael@0 1554 // semantically observable.
michael@0 1555 if (NS_IsMainThread()) {
michael@0 1556 return false;
michael@0 1557 }
michael@0 1558
michael@0 1559 // If we are in a child process, we need to synchronously call into the
michael@0 1560 // parent process to open the file and interact with the QuotaManager. The
michael@0 1561 // child can then map the file into its address space to perform I/O.
michael@0 1562 nsRefPtr<File> file;
michael@0 1563 if (IsMainProcess()) {
michael@0 1564 file = new SingleProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
michael@0 1565 aReadParams);
michael@0 1566 } else {
michael@0 1567 file = new ChildProcessRunnable(aPrincipal, aOpenMode, aWriteParams,
michael@0 1568 aReadParams);
michael@0 1569 }
michael@0 1570
michael@0 1571 if (!file->BlockUntilOpen(aFile)) {
michael@0 1572 return false;
michael@0 1573 }
michael@0 1574
michael@0 1575 return file->MapMemory(aOpenMode);
michael@0 1576 }
michael@0 1577
michael@0 1578 } // anonymous namespace
michael@0 1579
michael@0 1580 typedef uint32_t AsmJSCookieType;
michael@0 1581 static const uint32_t sAsmJSCookie = 0x600d600d;
michael@0 1582
michael@0 1583 bool
michael@0 1584 OpenEntryForRead(nsIPrincipal* aPrincipal,
michael@0 1585 const jschar* aBegin,
michael@0 1586 const jschar* aLimit,
michael@0 1587 size_t* aSize,
michael@0 1588 const uint8_t** aMemory,
michael@0 1589 intptr_t* aFile)
michael@0 1590 {
michael@0 1591 if (size_t(aLimit - aBegin) < sMinCachedModuleLength) {
michael@0 1592 return false;
michael@0 1593 }
michael@0 1594
michael@0 1595 ReadParams readParams;
michael@0 1596 readParams.mBegin = aBegin;
michael@0 1597 readParams.mLimit = aLimit;
michael@0 1598
michael@0 1599 File::AutoClose file;
michael@0 1600 WriteParams notAWrite;
michael@0 1601 if (!OpenFile(aPrincipal, eOpenForRead, notAWrite, readParams, &file)) {
michael@0 1602 return false;
michael@0 1603 }
michael@0 1604
michael@0 1605 // Although we trust that the stored cache files have not been arbitrarily
michael@0 1606 // corrupted, it is possible that a previous execution aborted in the middle
michael@0 1607 // of writing a cache file (crash, OOM-killer, etc). To protect against
michael@0 1608 // partially-written cache files, we use the following scheme:
michael@0 1609 // - Allocate an extra word at the beginning of every cache file which
michael@0 1610 // starts out 0 (OpenFile opens with PR_TRUNCATE).
michael@0 1611 // - After the asm.js serialization is complete, PR_SyncMemMap to write
michael@0 1612 // everything to disk and then store a non-zero value (sAsmJSCookie)
michael@0 1613 // in the first word.
michael@0 1614 // - When attempting to read a cache file, check whether the first word is
michael@0 1615 // sAsmJSCookie.
michael@0 1616 if (file->FileSize() < sizeof(AsmJSCookieType) ||
michael@0 1617 *(AsmJSCookieType*)file->MappedMemory() != sAsmJSCookie) {
michael@0 1618 return false;
michael@0 1619 }
michael@0 1620
michael@0 1621 *aSize = file->FileSize() - sizeof(AsmJSCookieType);
michael@0 1622 *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
michael@0 1623
michael@0 1624 // The caller guarnatees a call to CloseEntryForRead (on success or
michael@0 1625 // failure) at which point the file will be closed.
michael@0 1626 file.Forget(reinterpret_cast<File**>(aFile));
michael@0 1627 return true;
michael@0 1628 }
michael@0 1629
michael@0 1630 void
michael@0 1631 CloseEntryForRead(JS::Handle<JSObject*> global,
michael@0 1632 size_t aSize,
michael@0 1633 const uint8_t* aMemory,
michael@0 1634 intptr_t aFile)
michael@0 1635 {
michael@0 1636 File::AutoClose file(reinterpret_cast<File*>(aFile));
michael@0 1637
michael@0 1638 MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
michael@0 1639 MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
michael@0 1640 }
michael@0 1641
michael@0 1642 bool
michael@0 1643 OpenEntryForWrite(nsIPrincipal* aPrincipal,
michael@0 1644 bool aInstalled,
michael@0 1645 const jschar* aBegin,
michael@0 1646 const jschar* aEnd,
michael@0 1647 size_t aSize,
michael@0 1648 uint8_t** aMemory,
michael@0 1649 intptr_t* aFile)
michael@0 1650 {
michael@0 1651 if (size_t(aEnd - aBegin) < sMinCachedModuleLength) {
michael@0 1652 return false;
michael@0 1653 }
michael@0 1654
michael@0 1655 // Add extra space for the AsmJSCookieType (see OpenEntryForRead).
michael@0 1656 aSize += sizeof(AsmJSCookieType);
michael@0 1657
michael@0 1658 static_assert(sNumFastHashChars < sMinCachedModuleLength, "HashString safe");
michael@0 1659
michael@0 1660 WriteParams writeParams;
michael@0 1661 writeParams.mInstalled = aInstalled;
michael@0 1662 writeParams.mSize = aSize;
michael@0 1663 writeParams.mFastHash = HashString(aBegin, sNumFastHashChars);
michael@0 1664 writeParams.mNumChars = aEnd - aBegin;
michael@0 1665 writeParams.mFullHash = HashString(aBegin, writeParams.mNumChars);
michael@0 1666
michael@0 1667 File::AutoClose file;
michael@0 1668 ReadParams notARead;
michael@0 1669 if (!OpenFile(aPrincipal, eOpenForWrite, writeParams, notARead, &file)) {
michael@0 1670 return false;
michael@0 1671 }
michael@0 1672
michael@0 1673 // Strip off the AsmJSCookieType from the buffer returned to the caller,
michael@0 1674 // which expects a buffer of aSize, not a buffer of sizeWithCookie starting
michael@0 1675 // with a cookie.
michael@0 1676 *aMemory = (uint8_t*) file->MappedMemory() + sizeof(AsmJSCookieType);
michael@0 1677
michael@0 1678 // The caller guarnatees a call to CloseEntryForWrite (on success or
michael@0 1679 // failure) at which point the file will be closed
michael@0 1680 file.Forget(reinterpret_cast<File**>(aFile));
michael@0 1681 return true;
michael@0 1682 }
michael@0 1683
michael@0 1684 void
michael@0 1685 CloseEntryForWrite(JS::Handle<JSObject*> global,
michael@0 1686 size_t aSize,
michael@0 1687 uint8_t* aMemory,
michael@0 1688 intptr_t aFile)
michael@0 1689 {
michael@0 1690 File::AutoClose file(reinterpret_cast<File*>(aFile));
michael@0 1691
michael@0 1692 MOZ_ASSERT(aSize + sizeof(AsmJSCookieType) == file->FileSize());
michael@0 1693 MOZ_ASSERT(aMemory - sizeof(AsmJSCookieType) == file->MappedMemory());
michael@0 1694
michael@0 1695 // Flush to disk before writing the cookie (see OpenEntryForRead).
michael@0 1696 if (PR_SyncMemMap(file->FileDesc(),
michael@0 1697 file->MappedMemory(),
michael@0 1698 file->FileSize()) == PR_SUCCESS) {
michael@0 1699 *(AsmJSCookieType*)file->MappedMemory() = sAsmJSCookie;
michael@0 1700 }
michael@0 1701 }
michael@0 1702
michael@0 1703 bool
michael@0 1704 GetBuildId(JS::BuildIdCharVector* aBuildID)
michael@0 1705 {
michael@0 1706 nsCOMPtr<nsIXULAppInfo> info = do_GetService("@mozilla.org/xre/app-info;1");
michael@0 1707 if (!info) {
michael@0 1708 return false;
michael@0 1709 }
michael@0 1710
michael@0 1711 nsCString buildID;
michael@0 1712 nsresult rv = info->GetPlatformBuildID(buildID);
michael@0 1713 NS_ENSURE_SUCCESS(rv, false);
michael@0 1714
michael@0 1715 if (!aBuildID->resize(buildID.Length())) {
michael@0 1716 return false;
michael@0 1717 }
michael@0 1718
michael@0 1719 for (size_t i = 0; i < buildID.Length(); i++) {
michael@0 1720 (*aBuildID)[i] = buildID[i];
michael@0 1721 }
michael@0 1722
michael@0 1723 return true;
michael@0 1724 }
michael@0 1725
michael@0 1726 class Client : public quota::Client
michael@0 1727 {
michael@0 1728 public:
michael@0 1729 NS_IMETHOD_(MozExternalRefCountType)
michael@0 1730 AddRef() MOZ_OVERRIDE;
michael@0 1731
michael@0 1732 NS_IMETHOD_(MozExternalRefCountType)
michael@0 1733 Release() MOZ_OVERRIDE;
michael@0 1734
michael@0 1735 virtual Type
michael@0 1736 GetType() MOZ_OVERRIDE
michael@0 1737 {
michael@0 1738 return ASMJS;
michael@0 1739 }
michael@0 1740
michael@0 1741 virtual nsresult
michael@0 1742 InitOrigin(PersistenceType aPersistenceType,
michael@0 1743 const nsACString& aGroup,
michael@0 1744 const nsACString& aOrigin,
michael@0 1745 UsageInfo* aUsageInfo) MOZ_OVERRIDE
michael@0 1746 {
michael@0 1747 if (!aUsageInfo) {
michael@0 1748 return NS_OK;
michael@0 1749 }
michael@0 1750 return GetUsageForOrigin(aPersistenceType, aGroup, aOrigin, aUsageInfo);
michael@0 1751 }
michael@0 1752
michael@0 1753 virtual nsresult
michael@0 1754 GetUsageForOrigin(PersistenceType aPersistenceType,
michael@0 1755 const nsACString& aGroup,
michael@0 1756 const nsACString& aOrigin,
michael@0 1757 UsageInfo* aUsageInfo) MOZ_OVERRIDE
michael@0 1758 {
michael@0 1759 QuotaManager* qm = QuotaManager::Get();
michael@0 1760 MOZ_ASSERT(qm, "We were being called by the QuotaManager");
michael@0 1761
michael@0 1762 nsCOMPtr<nsIFile> directory;
michael@0 1763 nsresult rv = qm->GetDirectoryForOrigin(aPersistenceType, aOrigin,
michael@0 1764 getter_AddRefs(directory));
michael@0 1765 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1766 MOZ_ASSERT(directory, "We're here because the origin directory exists");
michael@0 1767
michael@0 1768 rv = directory->Append(NS_LITERAL_STRING(ASMJSCACHE_DIRECTORY_NAME));
michael@0 1769 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1770
michael@0 1771 DebugOnly<bool> exists;
michael@0 1772 MOZ_ASSERT(NS_SUCCEEDED(directory->Exists(&exists)) && exists);
michael@0 1773
michael@0 1774 nsCOMPtr<nsISimpleEnumerator> entries;
michael@0 1775 rv = directory->GetDirectoryEntries(getter_AddRefs(entries));
michael@0 1776 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1777
michael@0 1778 bool hasMore;
michael@0 1779 while (NS_SUCCEEDED((rv = entries->HasMoreElements(&hasMore))) &&
michael@0 1780 hasMore && !aUsageInfo->Canceled()) {
michael@0 1781 nsCOMPtr<nsISupports> entry;
michael@0 1782 rv = entries->GetNext(getter_AddRefs(entry));
michael@0 1783 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1784
michael@0 1785 nsCOMPtr<nsIFile> file = do_QueryInterface(entry);
michael@0 1786 NS_ENSURE_TRUE(file, NS_NOINTERFACE);
michael@0 1787
michael@0 1788 int64_t fileSize;
michael@0 1789 rv = file->GetFileSize(&fileSize);
michael@0 1790 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1791
michael@0 1792 MOZ_ASSERT(fileSize >= 0, "Negative size?!");
michael@0 1793
michael@0 1794 // Since the client is not explicitly storing files, append to database
michael@0 1795 // usage which represents implicit storage allocation.
michael@0 1796 aUsageInfo->AppendToDatabaseUsage(uint64_t(fileSize));
michael@0 1797 }
michael@0 1798 NS_ENSURE_SUCCESS(rv, rv);
michael@0 1799
michael@0 1800 return NS_OK;
michael@0 1801 }
michael@0 1802
michael@0 1803 virtual void
michael@0 1804 OnOriginClearCompleted(PersistenceType aPersistenceType,
michael@0 1805 const OriginOrPatternString& aOriginOrPattern)
michael@0 1806 MOZ_OVERRIDE
michael@0 1807 { }
michael@0 1808
michael@0 1809 virtual void
michael@0 1810 ReleaseIOThreadObjects() MOZ_OVERRIDE
michael@0 1811 { }
michael@0 1812
michael@0 1813 virtual bool
michael@0 1814 IsFileServiceUtilized() MOZ_OVERRIDE
michael@0 1815 {
michael@0 1816 return false;
michael@0 1817 }
michael@0 1818
michael@0 1819 virtual bool
michael@0 1820 IsTransactionServiceActivated() MOZ_OVERRIDE
michael@0 1821 {
michael@0 1822 return false;
michael@0 1823 }
michael@0 1824
michael@0 1825 virtual void
michael@0 1826 WaitForStoragesToComplete(nsTArray<nsIOfflineStorage*>& aStorages,
michael@0 1827 nsIRunnable* aCallback) MOZ_OVERRIDE
michael@0 1828 {
michael@0 1829 MOZ_ASSUME_UNREACHABLE("There are no storages");
michael@0 1830 }
michael@0 1831
michael@0 1832 virtual void
michael@0 1833 AbortTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
michael@0 1834 {
michael@0 1835 MOZ_ASSUME_UNREACHABLE("There are no storages");
michael@0 1836 }
michael@0 1837
michael@0 1838 virtual bool
michael@0 1839 HasTransactionsForStorage(nsIOfflineStorage* aStorage) MOZ_OVERRIDE
michael@0 1840 {
michael@0 1841 return false;
michael@0 1842 }
michael@0 1843
michael@0 1844 virtual void
michael@0 1845 ShutdownTransactionService() MOZ_OVERRIDE
michael@0 1846 { }
michael@0 1847
michael@0 1848 private:
michael@0 1849 nsAutoRefCnt mRefCnt;
michael@0 1850 NS_DECL_OWNINGTHREAD
michael@0 1851 };
michael@0 1852
michael@0 1853 NS_IMPL_ADDREF(asmjscache::Client)
michael@0 1854 NS_IMPL_RELEASE(asmjscache::Client)
michael@0 1855
michael@0 1856 quota::Client*
michael@0 1857 CreateClient()
michael@0 1858 {
michael@0 1859 return new Client();
michael@0 1860 }
michael@0 1861
michael@0 1862 } // namespace asmjscache
michael@0 1863 } // namespace dom
michael@0 1864 } // namespace mozilla
michael@0 1865
michael@0 1866 namespace IPC {
michael@0 1867
michael@0 1868 using mozilla::dom::asmjscache::Metadata;
michael@0 1869 using mozilla::dom::asmjscache::WriteParams;
michael@0 1870
michael@0 1871 void
michael@0 1872 ParamTraits<Metadata>::Write(Message* aMsg, const paramType& aParam)
michael@0 1873 {
michael@0 1874 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
michael@0 1875 const Metadata::Entry& entry = aParam.mEntries[i];
michael@0 1876 WriteParam(aMsg, entry.mFastHash);
michael@0 1877 WriteParam(aMsg, entry.mNumChars);
michael@0 1878 WriteParam(aMsg, entry.mFullHash);
michael@0 1879 WriteParam(aMsg, entry.mModuleIndex);
michael@0 1880 }
michael@0 1881 }
michael@0 1882
michael@0 1883 bool
michael@0 1884 ParamTraits<Metadata>::Read(const Message* aMsg, void** aIter,
michael@0 1885 paramType* aResult)
michael@0 1886 {
michael@0 1887 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
michael@0 1888 Metadata::Entry& entry = aResult->mEntries[i];
michael@0 1889 if (!ReadParam(aMsg, aIter, &entry.mFastHash) ||
michael@0 1890 !ReadParam(aMsg, aIter, &entry.mNumChars) ||
michael@0 1891 !ReadParam(aMsg, aIter, &entry.mFullHash) ||
michael@0 1892 !ReadParam(aMsg, aIter, &entry.mModuleIndex))
michael@0 1893 {
michael@0 1894 return false;
michael@0 1895 }
michael@0 1896 }
michael@0 1897 return true;
michael@0 1898 }
michael@0 1899
michael@0 1900 void
michael@0 1901 ParamTraits<Metadata>::Log(const paramType& aParam, std::wstring* aLog)
michael@0 1902 {
michael@0 1903 for (unsigned i = 0; i < Metadata::kNumEntries; i++) {
michael@0 1904 const Metadata::Entry& entry = aParam.mEntries[i];
michael@0 1905 LogParam(entry.mFastHash, aLog);
michael@0 1906 LogParam(entry.mNumChars, aLog);
michael@0 1907 LogParam(entry.mFullHash, aLog);
michael@0 1908 LogParam(entry.mModuleIndex, aLog);
michael@0 1909 }
michael@0 1910 }
michael@0 1911
michael@0 1912 void
michael@0 1913 ParamTraits<WriteParams>::Write(Message* aMsg, const paramType& aParam)
michael@0 1914 {
michael@0 1915 WriteParam(aMsg, aParam.mSize);
michael@0 1916 WriteParam(aMsg, aParam.mFastHash);
michael@0 1917 WriteParam(aMsg, aParam.mNumChars);
michael@0 1918 WriteParam(aMsg, aParam.mFullHash);
michael@0 1919 WriteParam(aMsg, aParam.mInstalled);
michael@0 1920 }
michael@0 1921
michael@0 1922 bool
michael@0 1923 ParamTraits<WriteParams>::Read(const Message* aMsg, void** aIter,
michael@0 1924 paramType* aResult)
michael@0 1925 {
michael@0 1926 return ReadParam(aMsg, aIter, &aResult->mSize) &&
michael@0 1927 ReadParam(aMsg, aIter, &aResult->mFastHash) &&
michael@0 1928 ReadParam(aMsg, aIter, &aResult->mNumChars) &&
michael@0 1929 ReadParam(aMsg, aIter, &aResult->mFullHash) &&
michael@0 1930 ReadParam(aMsg, aIter, &aResult->mInstalled);
michael@0 1931 }
michael@0 1932
michael@0 1933 void
michael@0 1934 ParamTraits<WriteParams>::Log(const paramType& aParam, std::wstring* aLog)
michael@0 1935 {
michael@0 1936 LogParam(aParam.mSize, aLog);
michael@0 1937 LogParam(aParam.mFastHash, aLog);
michael@0 1938 LogParam(aParam.mNumChars, aLog);
michael@0 1939 LogParam(aParam.mFullHash, aLog);
michael@0 1940 LogParam(aParam.mInstalled, aLog);
michael@0 1941 }
michael@0 1942
michael@0 1943 } // namespace IPC

mercurial