dom/src/storage/DOMStorageDBThread.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/dom/src/storage/DOMStorageDBThread.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,1326 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
     1.5 +/* This Source Code Form is subject to the terms of the Mozilla Public
     1.6 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.7 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.8 +
     1.9 +#include "DOMStorageDBThread.h"
    1.10 +#include "DOMStorageCache.h"
    1.11 +
    1.12 +#include "nsIEffectiveTLDService.h"
    1.13 +#include "nsDirectoryServiceUtils.h"
    1.14 +#include "nsAppDirectoryServiceDefs.h"
    1.15 +#include "nsThreadUtils.h"
    1.16 +#include "nsProxyRelease.h"
    1.17 +#include "mozStorageCID.h"
    1.18 +#include "mozStorageHelper.h"
    1.19 +#include "mozIStorageService.h"
    1.20 +#include "mozIStorageBindingParamsArray.h"
    1.21 +#include "mozIStorageBindingParams.h"
    1.22 +#include "mozIStorageValueArray.h"
    1.23 +#include "mozIStorageFunction.h"
    1.24 +#include "nsIObserverService.h"
    1.25 +#include "nsIVariant.h"
    1.26 +#include "mozilla/IOInterposer.h"
    1.27 +#include "mozilla/Services.h"
    1.28 +
    1.29 +// How long we collect write oprerations
    1.30 +// before they are flushed to the database
    1.31 +// In milliseconds.
    1.32 +#define FLUSHING_INTERVAL_MS 5000
    1.33 +
    1.34 +// Write Ahead Log's maximum size is 512KB
    1.35 +#define MAX_WAL_SIZE_BYTES 512 * 1024
    1.36 +
    1.37 +namespace mozilla {
    1.38 +namespace dom {
    1.39 +
    1.40 +DOMStorageDBBridge::DOMStorageDBBridge()
    1.41 +{
    1.42 +}
    1.43 +
    1.44 +
    1.45 +DOMStorageDBThread::DOMStorageDBThread()
    1.46 +: mThread(nullptr)
    1.47 +, mMonitor("DOMStorageThreadMonitor")
    1.48 +, mStopIOThread(false)
    1.49 +, mWALModeEnabled(false)
    1.50 +, mDBReady(false)
    1.51 +, mStatus(NS_OK)
    1.52 +, mWorkerStatements(mWorkerConnection)
    1.53 +, mReaderStatements(mReaderConnection)
    1.54 +, mDirtyEpoch(0)
    1.55 +, mFlushImmediately(false)
    1.56 +, mPriorityCounter(0)
    1.57 +{
    1.58 +}
    1.59 +
    1.60 +nsresult
    1.61 +DOMStorageDBThread::Init()
    1.62 +{
    1.63 +  nsresult rv;
    1.64 +
    1.65 +  // Need to determine location on the main thread, since
    1.66 +  // NS_GetSpecialDirectory access the atom table that can
    1.67 +  // be accessed only on the main thread.
    1.68 +  rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
    1.69 +                              getter_AddRefs(mDatabaseFile));
    1.70 +  NS_ENSURE_SUCCESS(rv, rv);
    1.71 +
    1.72 +  rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
    1.73 +  NS_ENSURE_SUCCESS(rv, rv);
    1.74 +
    1.75 +  // Ensure mozIStorageService init on the main thread first.
    1.76 +  nsCOMPtr<mozIStorageService> service =
    1.77 +    do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
    1.78 +  NS_ENSURE_SUCCESS(rv, rv);
    1.79 +
    1.80 +  // Need to keep the lock to avoid setting mThread later then
    1.81 +  // the thread body executes.
    1.82 +  MonitorAutoLock monitor(mMonitor);
    1.83 +
    1.84 +  mThread = PR_CreateThread(PR_USER_THREAD, &DOMStorageDBThread::ThreadFunc, this,
    1.85 +                            PR_PRIORITY_LOW, PR_GLOBAL_THREAD, PR_JOINABLE_THREAD,
    1.86 +                            262144);
    1.87 +  if (!mThread) {
    1.88 +    return NS_ERROR_OUT_OF_MEMORY;
    1.89 +  }
    1.90 +
    1.91 +  return NS_OK;
    1.92 +}
    1.93 +
    1.94 +nsresult
    1.95 +DOMStorageDBThread::Shutdown()
    1.96 +{
    1.97 +  if (!mThread) {
    1.98 +    return NS_ERROR_NOT_INITIALIZED;
    1.99 +  }
   1.100 +
   1.101 +  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
   1.102 +
   1.103 +  {
   1.104 +    MonitorAutoLock monitor(mMonitor);
   1.105 +
   1.106 +    // After we stop, no other operations can be accepted
   1.107 +    mFlushImmediately = true;
   1.108 +    mStopIOThread = true;
   1.109 +    monitor.Notify();
   1.110 +  }
   1.111 +
   1.112 +  PR_JoinThread(mThread);
   1.113 +  mThread = nullptr;
   1.114 +
   1.115 +  return mStatus;
   1.116 +}
   1.117 +
   1.118 +void
   1.119 +DOMStorageDBThread::SyncPreload(DOMStorageCacheBridge* aCache, bool aForceSync)
   1.120 +{
   1.121 +  if (!aForceSync && aCache->LoadedCount()) {
   1.122 +    // Preload already started for this cache, just wait for it to finish.
   1.123 +    // LoadWait will exit after LoadDone on the cache has been called.
   1.124 +    SetHigherPriority();
   1.125 +    aCache->LoadWait();
   1.126 +    SetDefaultPriority();
   1.127 +    return;
   1.128 +  }
   1.129 +
   1.130 +  // Bypass sync load when an update is pending in the queue to write, we would
   1.131 +  // get incosistent data in the cache.  Also don't allow sync main-thread preload
   1.132 +  // when DB open and init is still pending on the background thread.
   1.133 +  if (mDBReady && mWALModeEnabled) {
   1.134 +    bool pendingTasks;
   1.135 +    {
   1.136 +      MonitorAutoLock monitor(mMonitor);
   1.137 +      pendingTasks = mPendingTasks.IsScopeUpdatePending(aCache->Scope()) ||
   1.138 +                     mPendingTasks.IsScopeClearPending(aCache->Scope());
   1.139 +    }
   1.140 +
   1.141 +    if (!pendingTasks) {
   1.142 +      // WAL is enabled, thus do the load synchronously on the main thread.
   1.143 +      DBOperation preload(DBOperation::opPreload, aCache);
   1.144 +      preload.PerformAndFinalize(this);
   1.145 +      return;
   1.146 +    }
   1.147 +  }
   1.148 +
   1.149 +  // Need to go asynchronously since WAL is not allowed or scheduled updates
   1.150 +  // need to be flushed first.
   1.151 +  // Schedule preload for this cache as the first operation.
   1.152 +  nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
   1.153 +
   1.154 +  // LoadWait exits after LoadDone of the cache has been called.
   1.155 +  if (NS_SUCCEEDED(rv)) {
   1.156 +    aCache->LoadWait();
   1.157 +  }
   1.158 +}
   1.159 +
   1.160 +void
   1.161 +DOMStorageDBThread::AsyncFlush()
   1.162 +{
   1.163 +  MonitorAutoLock monitor(mMonitor);
   1.164 +  mFlushImmediately = true;
   1.165 +  monitor.Notify();
   1.166 +}
   1.167 +
   1.168 +bool
   1.169 +DOMStorageDBThread::ShouldPreloadScope(const nsACString& aScope)
   1.170 +{
   1.171 +  MonitorAutoLock monitor(mMonitor);
   1.172 +  return mScopesHavingData.Contains(aScope);
   1.173 +}
   1.174 +
   1.175 +namespace { // anon
   1.176 +
   1.177 +PLDHashOperator
   1.178 +GetScopesHavingDataEnum(nsCStringHashKey* aKey, void* aArg)
   1.179 +{
   1.180 +  InfallibleTArray<nsCString>* scopes =
   1.181 +      static_cast<InfallibleTArray<nsCString>*>(aArg);
   1.182 +  scopes->AppendElement(aKey->GetKey());
   1.183 +  return PL_DHASH_NEXT;
   1.184 +}
   1.185 +
   1.186 +} // anon
   1.187 +
   1.188 +void
   1.189 +DOMStorageDBThread::GetScopesHavingData(InfallibleTArray<nsCString>* aScopes)
   1.190 +{
   1.191 +  MonitorAutoLock monitor(mMonitor);
   1.192 +  mScopesHavingData.EnumerateEntries(GetScopesHavingDataEnum, aScopes);
   1.193 +}
   1.194 +
   1.195 +nsresult
   1.196 +DOMStorageDBThread::InsertDBOp(DOMStorageDBThread::DBOperation* aOperation)
   1.197 +{
   1.198 +  MonitorAutoLock monitor(mMonitor);
   1.199 +
   1.200 +  // Sentinel to don't forget to delete the operation when we exit early.
   1.201 +  nsAutoPtr<DOMStorageDBThread::DBOperation> opScope(aOperation);
   1.202 +
   1.203 +  if (mStopIOThread) {
   1.204 +    // Thread use after shutdown demanded.
   1.205 +    MOZ_ASSERT(false);
   1.206 +    return NS_ERROR_NOT_INITIALIZED;
   1.207 +  }
   1.208 +
   1.209 +  if (NS_FAILED(mStatus)) {
   1.210 +    MonitorAutoUnlock unlock(mMonitor);
   1.211 +    aOperation->Finalize(mStatus);
   1.212 +    return mStatus;
   1.213 +  }
   1.214 +
   1.215 +  switch (aOperation->Type()) {
   1.216 +  case DBOperation::opPreload:
   1.217 +  case DBOperation::opPreloadUrgent:
   1.218 +    if (mPendingTasks.IsScopeUpdatePending(aOperation->Scope())) {
   1.219 +      // If there is a pending update operation for the scope first do the flush
   1.220 +      // before we preload the cache.  This may happen in an extremely rare case
   1.221 +      // when a child process throws away its cache before flush on the parent
   1.222 +      // has finished.  If we would preloaded the cache as a priority operation 
   1.223 +      // before the pending flush, we would have got an inconsistent cache content.
   1.224 +      mFlushImmediately = true;
   1.225 +    } else if (mPendingTasks.IsScopeClearPending(aOperation->Scope())) {
   1.226 +      // The scope is scheduled to be cleared, so just quickly load as empty.
   1.227 +      // We need to do this to prevent load of the DB data before the scope has
   1.228 +      // actually been cleared from the database.  Preloads are processed
   1.229 +      // immediately before update and clear operations on the database that
   1.230 +      // are flushed periodically in batches.
   1.231 +      MonitorAutoUnlock unlock(mMonitor);
   1.232 +      aOperation->Finalize(NS_OK);
   1.233 +      return NS_OK;
   1.234 +    }
   1.235 +    // NO BREAK
   1.236 +
   1.237 +  case DBOperation::opGetUsage:
   1.238 +    if (aOperation->Type() == DBOperation::opPreloadUrgent) {
   1.239 +      SetHigherPriority(); // Dropped back after urgent preload execution
   1.240 +      mPreloads.InsertElementAt(0, aOperation);
   1.241 +    } else {
   1.242 +      mPreloads.AppendElement(aOperation);
   1.243 +    }
   1.244 +
   1.245 +    // DB operation adopted, don't delete it.
   1.246 +    opScope.forget();
   1.247 +
   1.248 +    // Immediately start executing this.
   1.249 +    monitor.Notify();
   1.250 +    break;
   1.251 +
   1.252 +  default:
   1.253 +    // Update operations are first collected, coalesced and then flushed
   1.254 +    // after a short time.
   1.255 +    mPendingTasks.Add(aOperation);
   1.256 +
   1.257 +    // DB operation adopted, don't delete it.
   1.258 +    opScope.forget();
   1.259 +
   1.260 +    ScheduleFlush();
   1.261 +    break;
   1.262 +  }
   1.263 +
   1.264 +  return NS_OK;
   1.265 +}
   1.266 +
   1.267 +void
   1.268 +DOMStorageDBThread::SetHigherPriority()
   1.269 +{
   1.270 +  ++mPriorityCounter;
   1.271 +  PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
   1.272 +}
   1.273 +
   1.274 +void
   1.275 +DOMStorageDBThread::SetDefaultPriority()
   1.276 +{
   1.277 +  if (--mPriorityCounter <= 0) {
   1.278 +    PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
   1.279 +  }
   1.280 +}
   1.281 +
   1.282 +void
   1.283 +DOMStorageDBThread::ThreadFunc(void* aArg)
   1.284 +{
   1.285 +  PR_SetCurrentThreadName("localStorage DB");
   1.286 +  mozilla::IOInterposer::RegisterCurrentThread();
   1.287 +
   1.288 +  DOMStorageDBThread* thread = static_cast<DOMStorageDBThread*>(aArg);
   1.289 +  thread->ThreadFunc();
   1.290 +  mozilla::IOInterposer::UnregisterCurrentThread();
   1.291 +}
   1.292 +
   1.293 +void
   1.294 +DOMStorageDBThread::ThreadFunc()
   1.295 +{
   1.296 +  nsresult rv = InitDatabase();
   1.297 +
   1.298 +  MonitorAutoLock lockMonitor(mMonitor);
   1.299 +
   1.300 +  if (NS_FAILED(rv)) {
   1.301 +    mStatus = rv;
   1.302 +    mStopIOThread = true;
   1.303 +    return;
   1.304 +  }
   1.305 +
   1.306 +  while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() || mPendingTasks.HasTasks())) {
   1.307 +    if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
   1.308 +      // Flush time is up or flush has been forced, do it now.
   1.309 +      UnscheduleFlush();
   1.310 +      if (mPendingTasks.Prepare()) {
   1.311 +        {
   1.312 +          MonitorAutoUnlock unlockMonitor(mMonitor);
   1.313 +          rv = mPendingTasks.Execute(this);
   1.314 +        }
   1.315 +
   1.316 +        if (!mPendingTasks.Finalize(rv)) {
   1.317 +          mStatus = rv;
   1.318 +          NS_WARNING("localStorage DB access broken");
   1.319 +        }
   1.320 +      }
   1.321 +      NotifyFlushCompletion();
   1.322 +    } else if (MOZ_LIKELY(mPreloads.Length())) {
   1.323 +      nsAutoPtr<DBOperation> op(mPreloads[0]);
   1.324 +      mPreloads.RemoveElementAt(0);
   1.325 +      {
   1.326 +        MonitorAutoUnlock unlockMonitor(mMonitor);
   1.327 +        op->PerformAndFinalize(this);
   1.328 +      }
   1.329 +
   1.330 +      if (op->Type() == DBOperation::opPreloadUrgent) {
   1.331 +        SetDefaultPriority(); // urgent preload unscheduled
   1.332 +      }
   1.333 +    } else if (MOZ_UNLIKELY(!mStopIOThread)) {
   1.334 +      lockMonitor.Wait(TimeUntilFlush());
   1.335 +    }
   1.336 +  } // thread loop
   1.337 +
   1.338 +  mStatus = ShutdownDatabase();
   1.339 +}
   1.340 +
   1.341 +extern void
   1.342 +ReverseString(const nsCSubstring& aSource, nsCSubstring& aResult);
   1.343 +
   1.344 +namespace { // anon
   1.345 +
   1.346 +class nsReverseStringSQLFunction MOZ_FINAL : public mozIStorageFunction
   1.347 +{
   1.348 +  NS_DECL_ISUPPORTS
   1.349 +  NS_DECL_MOZISTORAGEFUNCTION
   1.350 +};
   1.351 +
   1.352 +NS_IMPL_ISUPPORTS(nsReverseStringSQLFunction, mozIStorageFunction)
   1.353 +
   1.354 +NS_IMETHODIMP
   1.355 +nsReverseStringSQLFunction::OnFunctionCall(
   1.356 +    mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
   1.357 +{
   1.358 +  nsresult rv;
   1.359 +
   1.360 +  nsAutoCString stringToReverse;
   1.361 +  rv = aFunctionArguments->GetUTF8String(0, stringToReverse);
   1.362 +  NS_ENSURE_SUCCESS(rv, rv);
   1.363 +
   1.364 +  nsAutoCString result;
   1.365 +  ReverseString(stringToReverse, result);
   1.366 +
   1.367 +  nsCOMPtr<nsIWritableVariant> outVar(do_CreateInstance(
   1.368 +      NS_VARIANT_CONTRACTID, &rv));
   1.369 +  NS_ENSURE_SUCCESS(rv, rv);
   1.370 +
   1.371 +  rv = outVar->SetAsAUTF8String(result);
   1.372 +  NS_ENSURE_SUCCESS(rv, rv);
   1.373 +
   1.374 +  *aResult = outVar.get();
   1.375 +  outVar.forget();
   1.376 +  return NS_OK;
   1.377 +}
   1.378 +
   1.379 +} // anon
   1.380 +
   1.381 +nsresult
   1.382 +DOMStorageDBThread::OpenDatabaseConnection()
   1.383 +{
   1.384 +  nsresult rv;
   1.385 +
   1.386 +  MOZ_ASSERT(!NS_IsMainThread());
   1.387 +
   1.388 +  nsCOMPtr<mozIStorageService> service
   1.389 +      = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
   1.390 +  NS_ENSURE_SUCCESS(rv, rv);
   1.391 +
   1.392 +  nsCOMPtr<mozIStorageConnection> connection;
   1.393 +  rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
   1.394 +  if (rv == NS_ERROR_FILE_CORRUPTED) {
   1.395 +    // delete the db and try opening again
   1.396 +    rv = mDatabaseFile->Remove(false);
   1.397 +    NS_ENSURE_SUCCESS(rv, rv);
   1.398 +    rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
   1.399 +  }
   1.400 +  NS_ENSURE_SUCCESS(rv, rv);
   1.401 +
   1.402 +  return NS_OK;
   1.403 +}
   1.404 +
   1.405 +nsresult
   1.406 +DOMStorageDBThread::InitDatabase()
   1.407 +{
   1.408 +  Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_INIT_DATABASE_MS> timer;
   1.409 +
   1.410 +  nsresult rv;
   1.411 +
   1.412 +  // Here we are on the worker thread. This opens the worker connection.
   1.413 +  MOZ_ASSERT(!NS_IsMainThread());
   1.414 +
   1.415 +  rv = OpenDatabaseConnection();
   1.416 +  NS_ENSURE_SUCCESS(rv, rv);
   1.417 +
   1.418 +  rv = TryJournalMode();
   1.419 +  NS_ENSURE_SUCCESS(rv, rv);
   1.420 +
   1.421 +  // Create a read-only clone
   1.422 +  (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
   1.423 +  NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
   1.424 +
   1.425 +  mozStorageTransaction transaction(mWorkerConnection, false);
   1.426 +
   1.427 +  // Ensure Gecko 1.9.1 storage table
   1.428 +  rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   1.429 +         "CREATE TABLE IF NOT EXISTS webappsstore2 ("
   1.430 +         "scope TEXT, "
   1.431 +         "key TEXT, "
   1.432 +         "value TEXT, "
   1.433 +         "secure INTEGER, "
   1.434 +         "owner TEXT)"));
   1.435 +  NS_ENSURE_SUCCESS(rv, rv);
   1.436 +
   1.437 +  rv = mWorkerConnection->ExecuteSimpleSQL(NS_LITERAL_CSTRING(
   1.438 +        "CREATE UNIQUE INDEX IF NOT EXISTS scope_key_index"
   1.439 +        " ON webappsstore2(scope, key)"));
   1.440 +  NS_ENSURE_SUCCESS(rv, rv);
   1.441 +
   1.442 +  nsCOMPtr<mozIStorageFunction> function1(new nsReverseStringSQLFunction());
   1.443 +  NS_ENSURE_TRUE(function1, NS_ERROR_OUT_OF_MEMORY);
   1.444 +
   1.445 +  rv = mWorkerConnection->CreateFunction(NS_LITERAL_CSTRING("REVERSESTRING"), 1, function1);
   1.446 +  NS_ENSURE_SUCCESS(rv, rv);
   1.447 +
   1.448 +  bool exists;
   1.449 +
   1.450 +  // Check if there is storage of Gecko 1.9.0 and if so, upgrade that storage
   1.451 +  // to actual webappsstore2 table and drop the obsolete table. First process
   1.452 +  // this newer table upgrade to priority potential duplicates from older
   1.453 +  // storage table.
   1.454 +  rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("webappsstore"),
   1.455 +                                &exists);
   1.456 +  NS_ENSURE_SUCCESS(rv, rv);
   1.457 +
   1.458 +  if (exists) {
   1.459 +    rv = mWorkerConnection->ExecuteSimpleSQL(
   1.460 +      NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
   1.461 +                         "webappsstore2(scope, key, value, secure, owner) "
   1.462 +                         "SELECT REVERSESTRING(domain) || '.:', key, value, secure, owner "
   1.463 +                         "FROM webappsstore"));
   1.464 +    NS_ENSURE_SUCCESS(rv, rv);
   1.465 +
   1.466 +    rv = mWorkerConnection->ExecuteSimpleSQL(
   1.467 +      NS_LITERAL_CSTRING("DROP TABLE webappsstore"));
   1.468 +    NS_ENSURE_SUCCESS(rv, rv);
   1.469 +  }
   1.470 +
   1.471 +  // Check if there is storage of Gecko 1.8 and if so, upgrade that storage
   1.472 +  // to actual webappsstore2 table and drop the obsolete table. Potential
   1.473 +  // duplicates will be ignored.
   1.474 +  rv = mWorkerConnection->TableExists(NS_LITERAL_CSTRING("moz_webappsstore"),
   1.475 +                                &exists);
   1.476 +  NS_ENSURE_SUCCESS(rv, rv);
   1.477 +
   1.478 +  if (exists) {
   1.479 +    rv = mWorkerConnection->ExecuteSimpleSQL(
   1.480 +      NS_LITERAL_CSTRING("INSERT OR IGNORE INTO "
   1.481 +                         "webappsstore2(scope, key, value, secure, owner) "
   1.482 +                         "SELECT REVERSESTRING(domain) || '.:', key, value, secure, domain "
   1.483 +                         "FROM moz_webappsstore"));
   1.484 +    NS_ENSURE_SUCCESS(rv, rv);
   1.485 +
   1.486 +    rv = mWorkerConnection->ExecuteSimpleSQL(
   1.487 +      NS_LITERAL_CSTRING("DROP TABLE moz_webappsstore"));
   1.488 +    NS_ENSURE_SUCCESS(rv, rv);
   1.489 +  }
   1.490 +
   1.491 +  rv = transaction.Commit();
   1.492 +  NS_ENSURE_SUCCESS(rv, rv);
   1.493 +
   1.494 +  // Database open and all initiation operation are done.  Switching this flag
   1.495 +  // to true allow main thread to read directly from the database.
   1.496 +  // If we would allow this sooner, we would have opened a window where main thread
   1.497 +  // read might operate on a totaly broken and incosistent database.
   1.498 +  mDBReady = true;
   1.499 +
   1.500 +  // List scopes having any stored data
   1.501 +  nsCOMPtr<mozIStorageStatement> stmt;
   1.502 +  rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING("SELECT DISTINCT scope FROM webappsstore2"),
   1.503 +                                    getter_AddRefs(stmt));
   1.504 +  NS_ENSURE_SUCCESS(rv, rv);
   1.505 +  mozStorageStatementScoper scope(stmt);
   1.506 +
   1.507 +  while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
   1.508 +    nsAutoCString foundScope;
   1.509 +    rv = stmt->GetUTF8String(0, foundScope);
   1.510 +    NS_ENSURE_SUCCESS(rv, rv);
   1.511 +
   1.512 +    MonitorAutoLock monitor(mMonitor);
   1.513 +    mScopesHavingData.PutEntry(foundScope);
   1.514 +  }
   1.515 +
   1.516 +  return NS_OK;
   1.517 +}
   1.518 +
   1.519 +nsresult
   1.520 +DOMStorageDBThread::SetJournalMode(bool aIsWal)
   1.521 +{
   1.522 +  nsresult rv;
   1.523 +
   1.524 +  nsAutoCString stmtString(
   1.525 +    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
   1.526 +  if (aIsWal) {
   1.527 +    stmtString.AppendLiteral("wal");
   1.528 +  } else {
   1.529 +    stmtString.AppendLiteral("truncate");
   1.530 +  }
   1.531 +
   1.532 +  nsCOMPtr<mozIStorageStatement> stmt;
   1.533 +  rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
   1.534 +  NS_ENSURE_SUCCESS(rv, rv);
   1.535 +  mozStorageStatementScoper scope(stmt);
   1.536 +
   1.537 +  bool hasResult = false;
   1.538 +  rv = stmt->ExecuteStep(&hasResult);
   1.539 +  NS_ENSURE_SUCCESS(rv, rv);
   1.540 +  if (!hasResult) {
   1.541 +    return NS_ERROR_FAILURE;
   1.542 +  }
   1.543 +
   1.544 +  nsAutoCString journalMode;
   1.545 +  rv = stmt->GetUTF8String(0, journalMode);
   1.546 +  NS_ENSURE_SUCCESS(rv, rv);
   1.547 +  if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
   1.548 +      (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
   1.549 +    return NS_ERROR_FAILURE;
   1.550 +  }
   1.551 +
   1.552 +  return NS_OK;
   1.553 +}
   1.554 +
   1.555 +nsresult
   1.556 +DOMStorageDBThread::TryJournalMode()
   1.557 +{
   1.558 +  nsresult rv;
   1.559 +
   1.560 +  rv = SetJournalMode(true);
   1.561 +  if (NS_FAILED(rv)) {
   1.562 +    mWALModeEnabled = false;
   1.563 +
   1.564 +    rv = SetJournalMode(false);
   1.565 +    NS_ENSURE_SUCCESS(rv, rv);
   1.566 +  } else {
   1.567 +    mWALModeEnabled = true;
   1.568 +
   1.569 +    rv = ConfigureWALBehavior();
   1.570 +    NS_ENSURE_SUCCESS(rv, rv);
   1.571 +  }
   1.572 +
   1.573 +  return NS_OK;
   1.574 +}
   1.575 +
   1.576 +nsresult
   1.577 +DOMStorageDBThread::ConfigureWALBehavior()
   1.578 +{
   1.579 +  // Get the DB's page size
   1.580 +  nsCOMPtr<mozIStorageStatement> stmt;
   1.581 +  nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
   1.582 +    MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
   1.583 +  ), getter_AddRefs(stmt));
   1.584 +  NS_ENSURE_SUCCESS(rv, rv);
   1.585 +
   1.586 +  bool hasResult = false;
   1.587 +  rv = stmt->ExecuteStep(&hasResult);
   1.588 +  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
   1.589 +
   1.590 +  int32_t pageSize = 0;
   1.591 +  rv = stmt->GetInt32(0, &pageSize);
   1.592 +  NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
   1.593 +
   1.594 +  // Set the threshold for auto-checkpointing the WAL.
   1.595 +  // We don't want giant logs slowing down reads & shutdown.
   1.596 +  int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
   1.597 +  nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
   1.598 +  thresholdPragma.AppendInt(thresholdInPages);
   1.599 +  rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
   1.600 +  NS_ENSURE_SUCCESS(rv, rv);
   1.601 +
   1.602 +  // Set the maximum WAL log size to reduce footprint on mobile (large empty
   1.603 +  // WAL files will be truncated)
   1.604 +  nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
   1.605 +  // bug 600307: mak recommends setting this to 3 times the auto-checkpoint threshold
   1.606 +  journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
   1.607 +  rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
   1.608 +  NS_ENSURE_SUCCESS(rv, rv);
   1.609 +
   1.610 +  return NS_OK;
   1.611 +}
   1.612 +
   1.613 +nsresult
   1.614 +DOMStorageDBThread::ShutdownDatabase()
   1.615 +{
   1.616 +  // Has to be called on the worker thread.
   1.617 +  MOZ_ASSERT(!NS_IsMainThread());
   1.618 +
   1.619 +  nsresult rv = mStatus;
   1.620 +
   1.621 +  mDBReady = false;
   1.622 +
   1.623 +  // Finalize the cached statements.
   1.624 +  mReaderStatements.FinalizeStatements();
   1.625 +  mWorkerStatements.FinalizeStatements();
   1.626 +
   1.627 +  if (mReaderConnection) {
   1.628 +    // No need to sync access to mReaderConnection since the main thread
   1.629 +    // is right now joining this thread, unable to execute any events.
   1.630 +    mReaderConnection->Close();
   1.631 +    mReaderConnection = nullptr;
   1.632 +  }
   1.633 +
   1.634 +  if (mWorkerConnection) {
   1.635 +    rv = mWorkerConnection->Close();
   1.636 +    mWorkerConnection = nullptr;
   1.637 +  }
   1.638 +
   1.639 +  return rv;
   1.640 +}
   1.641 +
   1.642 +void
   1.643 +DOMStorageDBThread::ScheduleFlush()
   1.644 +{
   1.645 +  if (mDirtyEpoch) {
   1.646 +    return; // Already scheduled
   1.647 +  }
   1.648 +
   1.649 +  mDirtyEpoch = PR_IntervalNow() | 1; // Must be non-zero to indicate we are scheduled
   1.650 +
   1.651 +  // Wake the monitor from indefinite sleep...
   1.652 +  mMonitor.Notify();
   1.653 +}
   1.654 +
   1.655 +void
   1.656 +DOMStorageDBThread::UnscheduleFlush()
   1.657 +{
   1.658 +  // We are just about to do the flush, drop flags
   1.659 +  mFlushImmediately = false;
   1.660 +  mDirtyEpoch = 0;
   1.661 +}
   1.662 +
   1.663 +PRIntervalTime
   1.664 +DOMStorageDBThread::TimeUntilFlush()
   1.665 +{
   1.666 +  if (mFlushImmediately) {
   1.667 +    return 0; // Do it now regardless the timeout.
   1.668 +  }
   1.669 +
   1.670 +  static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
   1.671 +      "PR_INTERVAL_NO_TIMEOUT must be non-zero");
   1.672 +
   1.673 +  if (!mDirtyEpoch) {
   1.674 +    return PR_INTERVAL_NO_TIMEOUT; // No pending task...
   1.675 +  }
   1.676 +
   1.677 +  static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
   1.678 +
   1.679 +  PRIntervalTime now = PR_IntervalNow() | 1;
   1.680 +  PRIntervalTime age = now - mDirtyEpoch;
   1.681 +  if (age > kMaxAge) {
   1.682 +    return 0; // It is time.
   1.683 +  }
   1.684 +
   1.685 +  return kMaxAge - age; // Time left, this is used to sleep the monitor
   1.686 +}
   1.687 +
   1.688 +void
   1.689 +DOMStorageDBThread::NotifyFlushCompletion()
   1.690 +{
   1.691 +#ifdef DOM_STORAGE_TESTS
   1.692 +  if (!NS_IsMainThread()) {
   1.693 +    nsRefPtr<nsRunnableMethod<DOMStorageDBThread, void, false> > event =
   1.694 +      NS_NewNonOwningRunnableMethod(this, &DOMStorageDBThread::NotifyFlushCompletion);
   1.695 +    NS_DispatchToMainThread(event);
   1.696 +    return;
   1.697 +  }
   1.698 +
   1.699 +  nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
   1.700 +  if (obs) {
   1.701 +    obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
   1.702 +  }
   1.703 +#endif
   1.704 +}
   1.705 +
   1.706 +// DOMStorageDBThread::DBOperation
   1.707 +
   1.708 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
   1.709 +                                             DOMStorageCacheBridge* aCache,
   1.710 +                                             const nsAString& aKey,
   1.711 +                                             const nsAString& aValue)
   1.712 +: mType(aType)
   1.713 +, mCache(aCache)
   1.714 +, mKey(aKey)
   1.715 +, mValue(aValue)
   1.716 +{
   1.717 +  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
   1.718 +}
   1.719 +
   1.720 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
   1.721 +                                             DOMStorageUsageBridge* aUsage)
   1.722 +: mType(aType)
   1.723 +, mUsage(aUsage)
   1.724 +{
   1.725 +  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
   1.726 +}
   1.727 +
   1.728 +DOMStorageDBThread::DBOperation::DBOperation(const OperationType aType,
   1.729 +                                             const nsACString& aScope)
   1.730 +: mType(aType)
   1.731 +, mCache(nullptr)
   1.732 +, mScope(aScope)
   1.733 +{
   1.734 +  MOZ_COUNT_CTOR(DOMStorageDBThread::DBOperation);
   1.735 +}
   1.736 +
   1.737 +DOMStorageDBThread::DBOperation::~DBOperation()
   1.738 +{
   1.739 +  MOZ_COUNT_DTOR(DOMStorageDBThread::DBOperation);
   1.740 +}
   1.741 +
   1.742 +const nsCString
   1.743 +DOMStorageDBThread::DBOperation::Scope()
   1.744 +{
   1.745 +  if (mCache) {
   1.746 +    return mCache->Scope();
   1.747 +  }
   1.748 +
   1.749 +  return mScope;
   1.750 +}
   1.751 +
   1.752 +const nsCString
   1.753 +DOMStorageDBThread::DBOperation::Target()
   1.754 +{
   1.755 +  switch (mType) {
   1.756 +    case opAddItem:
   1.757 +    case opUpdateItem:
   1.758 +    case opRemoveItem:
   1.759 +      return Scope() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
   1.760 +
   1.761 +    default:
   1.762 +      return Scope();
   1.763 +  }
   1.764 +}
   1.765 +
   1.766 +void
   1.767 +DOMStorageDBThread::DBOperation::PerformAndFinalize(DOMStorageDBThread* aThread)
   1.768 +{
   1.769 +  Finalize(Perform(aThread));
   1.770 +}
   1.771 +
   1.772 +nsresult
   1.773 +DOMStorageDBThread::DBOperation::Perform(DOMStorageDBThread* aThread)
   1.774 +{
   1.775 +  nsresult rv;
   1.776 +
   1.777 +  switch (mType) {
   1.778 +  case opPreload:
   1.779 +  case opPreloadUrgent:
   1.780 +  {
   1.781 +    // Already loaded?
   1.782 +    if (mCache->Loaded()) {
   1.783 +      break;
   1.784 +    }
   1.785 +
   1.786 +    StatementCache* statements;
   1.787 +    if (MOZ_UNLIKELY(NS_IsMainThread())) {
   1.788 +      statements = &aThread->mReaderStatements;
   1.789 +    } else {
   1.790 +      statements = &aThread->mWorkerStatements;
   1.791 +    }
   1.792 +
   1.793 +    // OFFSET is an optimization when we have to do a sync load
   1.794 +    // and cache has already loaded some parts asynchronously.
   1.795 +    // It skips keys we have already loaded.
   1.796 +    nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
   1.797 +        "SELECT key, value FROM webappsstore2 "
   1.798 +        "WHERE scope = :scope ORDER BY key "
   1.799 +        "LIMIT -1 OFFSET :offset");
   1.800 +    NS_ENSURE_STATE(stmt);
   1.801 +    mozStorageStatementScoper scope(stmt);
   1.802 +
   1.803 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.804 +                                    mCache->Scope());
   1.805 +    NS_ENSURE_SUCCESS(rv, rv);
   1.806 +
   1.807 +    rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
   1.808 +                               static_cast<int32_t>(mCache->LoadedCount()));
   1.809 +    NS_ENSURE_SUCCESS(rv, rv);
   1.810 +
   1.811 +    bool exists;
   1.812 +    while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
   1.813 +      nsAutoString key;
   1.814 +      rv = stmt->GetString(0, key);
   1.815 +      NS_ENSURE_SUCCESS(rv, rv);
   1.816 +
   1.817 +      nsAutoString value;
   1.818 +      rv = stmt->GetString(1, value);
   1.819 +      NS_ENSURE_SUCCESS(rv, rv);
   1.820 +
   1.821 +      if (!mCache->LoadItem(key, value)) {
   1.822 +        break;
   1.823 +      }
   1.824 +    }
   1.825 +
   1.826 +    mCache->LoadDone(NS_OK);
   1.827 +    break;
   1.828 +  }
   1.829 +
   1.830 +  case opGetUsage:
   1.831 +  {
   1.832 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.833 +      "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2"
   1.834 +      " WHERE scope LIKE :scope"
   1.835 +    );
   1.836 +    NS_ENSURE_STATE(stmt);
   1.837 +
   1.838 +    mozStorageStatementScoper scope(stmt);
   1.839 +
   1.840 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.841 +                                    mUsage->Scope() + NS_LITERAL_CSTRING("%"));
   1.842 +    NS_ENSURE_SUCCESS(rv, rv);
   1.843 +
   1.844 +    bool exists;
   1.845 +    rv = stmt->ExecuteStep(&exists);
   1.846 +    NS_ENSURE_SUCCESS(rv, rv);
   1.847 +
   1.848 +    int64_t usage = 0;
   1.849 +    if (exists) {
   1.850 +      rv = stmt->GetInt64(0, &usage);
   1.851 +      NS_ENSURE_SUCCESS(rv, rv);
   1.852 +    }
   1.853 +
   1.854 +    mUsage->LoadUsage(usage);
   1.855 +    break;
   1.856 +  }
   1.857 +
   1.858 +  case opAddItem:
   1.859 +  case opUpdateItem:
   1.860 +  {
   1.861 +    MOZ_ASSERT(!NS_IsMainThread());
   1.862 +
   1.863 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.864 +      "INSERT OR REPLACE INTO webappsstore2 (scope, key, value) "
   1.865 +      "VALUES (:scope, :key, :value) "
   1.866 +    );
   1.867 +    NS_ENSURE_STATE(stmt);
   1.868 +
   1.869 +    mozStorageStatementScoper scope(stmt);
   1.870 +
   1.871 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.872 +                                    mCache->Scope());
   1.873 +    NS_ENSURE_SUCCESS(rv, rv);
   1.874 +    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
   1.875 +                                mKey);
   1.876 +    NS_ENSURE_SUCCESS(rv, rv);
   1.877 +    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
   1.878 +                                mValue);
   1.879 +    NS_ENSURE_SUCCESS(rv, rv);
   1.880 +
   1.881 +    rv = stmt->Execute();
   1.882 +    NS_ENSURE_SUCCESS(rv, rv);
   1.883 +
   1.884 +    aThread->mScopesHavingData.PutEntry(Scope());
   1.885 +    break;
   1.886 +  }
   1.887 +
   1.888 +  case opRemoveItem:
   1.889 +  {
   1.890 +    MOZ_ASSERT(!NS_IsMainThread());
   1.891 +
   1.892 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.893 +      "DELETE FROM webappsstore2 "
   1.894 +      "WHERE scope = :scope "
   1.895 +        "AND key = :key "
   1.896 +    );
   1.897 +    NS_ENSURE_STATE(stmt);
   1.898 +    mozStorageStatementScoper scope(stmt);
   1.899 +
   1.900 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.901 +                                    mCache->Scope());
   1.902 +    NS_ENSURE_SUCCESS(rv, rv);
   1.903 +    rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
   1.904 +                                mKey);
   1.905 +    NS_ENSURE_SUCCESS(rv, rv);
   1.906 +
   1.907 +    rv = stmt->Execute();
   1.908 +    NS_ENSURE_SUCCESS(rv, rv);
   1.909 +
   1.910 +    break;
   1.911 +  }
   1.912 +
   1.913 +  case opClear:
   1.914 +  {
   1.915 +    MOZ_ASSERT(!NS_IsMainThread());
   1.916 +
   1.917 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.918 +      "DELETE FROM webappsstore2 "
   1.919 +      "WHERE scope = :scope"
   1.920 +    );
   1.921 +    NS_ENSURE_STATE(stmt);
   1.922 +    mozStorageStatementScoper scope(stmt);
   1.923 +
   1.924 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.925 +                                    mCache->Scope());
   1.926 +    NS_ENSURE_SUCCESS(rv, rv);
   1.927 +
   1.928 +    rv = stmt->Execute();
   1.929 +    NS_ENSURE_SUCCESS(rv, rv);
   1.930 +
   1.931 +    aThread->mScopesHavingData.RemoveEntry(Scope());
   1.932 +    break;
   1.933 +  }
   1.934 +
   1.935 +  case opClearAll:
   1.936 +  {
   1.937 +    MOZ_ASSERT(!NS_IsMainThread());
   1.938 +
   1.939 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.940 +      "DELETE FROM webappsstore2"
   1.941 +    );
   1.942 +    NS_ENSURE_STATE(stmt);
   1.943 +    mozStorageStatementScoper scope(stmt);
   1.944 +
   1.945 +    rv = stmt->Execute();
   1.946 +    NS_ENSURE_SUCCESS(rv, rv);
   1.947 +
   1.948 +    aThread->mScopesHavingData.Clear();
   1.949 +    break;
   1.950 +  }
   1.951 +
   1.952 +  case opClearMatchingScope:
   1.953 +  {
   1.954 +    MOZ_ASSERT(!NS_IsMainThread());
   1.955 +
   1.956 +    nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
   1.957 +      "DELETE FROM webappsstore2"
   1.958 +      " WHERE scope GLOB :scope"
   1.959 +    );
   1.960 +    NS_ENSURE_STATE(stmt);
   1.961 +    mozStorageStatementScoper scope(stmt);
   1.962 +
   1.963 +    rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
   1.964 +                                    mScope + NS_LITERAL_CSTRING("*"));
   1.965 +    NS_ENSURE_SUCCESS(rv, rv);
   1.966 +
   1.967 +    rv = stmt->Execute();
   1.968 +    NS_ENSURE_SUCCESS(rv, rv);
   1.969 +
   1.970 +    break;
   1.971 +  }
   1.972 +
   1.973 +  default:
   1.974 +    NS_ERROR("Unknown task type");
   1.975 +    break;
   1.976 +  }
   1.977 +
   1.978 +  return NS_OK;
   1.979 +}
   1.980 +
   1.981 +void
   1.982 +DOMStorageDBThread::DBOperation::Finalize(nsresult aRv)
   1.983 +{
   1.984 +  switch (mType) {
   1.985 +  case opPreloadUrgent:
   1.986 +  case opPreload:
   1.987 +    if (NS_FAILED(aRv)) {
   1.988 +      // When we are here, something failed when loading from the database.
   1.989 +      // Notify that the storage is loaded to prevent deadlock of the main thread,
   1.990 +      // even though it is actually empty or incomplete.
   1.991 +      NS_WARNING("Failed to preload localStorage");
   1.992 +    }
   1.993 +
   1.994 +    mCache->LoadDone(aRv);
   1.995 +    break;
   1.996 +
   1.997 +  case opGetUsage:
   1.998 +    if (NS_FAILED(aRv)) {
   1.999 +      mUsage->LoadUsage(0);
  1.1000 +    }
  1.1001 +
  1.1002 +    break;
  1.1003 +
  1.1004 +  default:
  1.1005 +    if (NS_FAILED(aRv)) {
  1.1006 +      NS_WARNING("localStorage update/clear operation failed,"
  1.1007 +                 " data may not persist or clean up");
  1.1008 +    }
  1.1009 +
  1.1010 +    break;
  1.1011 +  }
  1.1012 +}
  1.1013 +
  1.1014 +// DOMStorageDBThread::PendingOperations
  1.1015 +
  1.1016 +DOMStorageDBThread::PendingOperations::PendingOperations()
  1.1017 +: mFlushFailureCount(0)
  1.1018 +{
  1.1019 +}
  1.1020 +
  1.1021 +bool
  1.1022 +DOMStorageDBThread::PendingOperations::HasTasks()
  1.1023 +{
  1.1024 +  return !!mUpdates.Count() || !!mClears.Count();
  1.1025 +}
  1.1026 +
  1.1027 +namespace { // anon
  1.1028 +
  1.1029 +PLDHashOperator
  1.1030 +ForgetUpdatesForScope(const nsACString& aMapping,
  1.1031 +                      nsAutoPtr<DOMStorageDBThread::DBOperation>& aPendingTask,
  1.1032 +                      void* aArg)
  1.1033 +{
  1.1034 +  DOMStorageDBThread::DBOperation* newOp = static_cast<DOMStorageDBThread::DBOperation*>(aArg);
  1.1035 +
  1.1036 +  if (newOp->Type() == DOMStorageDBThread::DBOperation::opClear &&
  1.1037 +      aPendingTask->Scope() != newOp->Scope()) {
  1.1038 +    return PL_DHASH_NEXT;
  1.1039 +  }
  1.1040 +
  1.1041 +  if (newOp->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
  1.1042 +      !StringBeginsWith(aPendingTask->Scope(), newOp->Scope())) {
  1.1043 +    return PL_DHASH_NEXT;
  1.1044 +  }
  1.1045 +
  1.1046 +  return PL_DHASH_REMOVE;
  1.1047 +}
  1.1048 +
  1.1049 +} // anon
  1.1050 +
  1.1051 +bool
  1.1052 +DOMStorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
  1.1053 +                                                                   DBOperation::OperationType aPendingType,
  1.1054 +                                                                   DBOperation::OperationType aNewType)
  1.1055 +{
  1.1056 +  if (aNewOp->Type() != aNewType) {
  1.1057 +    return false;
  1.1058 +  }
  1.1059 +
  1.1060 +  DOMStorageDBThread::DBOperation* pendingTask;
  1.1061 +  if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
  1.1062 +    return false;
  1.1063 +  }
  1.1064 +
  1.1065 +  if (pendingTask->Type() != aPendingType) {
  1.1066 +    return false;
  1.1067 +  }
  1.1068 +
  1.1069 +  return true;
  1.1070 +}
  1.1071 +
  1.1072 +void
  1.1073 +DOMStorageDBThread::PendingOperations::Add(DOMStorageDBThread::DBOperation* aOperation)
  1.1074 +{
  1.1075 +  // Optimize: when a key to remove has never been written to disk
  1.1076 +  // just bypass this operation.  A kew is new when an operation scheduled
  1.1077 +  // to write it to the database is of type opAddItem.
  1.1078 +  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opRemoveItem)) {
  1.1079 +    mUpdates.Remove(aOperation->Target());
  1.1080 +    delete aOperation;
  1.1081 +    return;
  1.1082 +  }
  1.1083 +
  1.1084 +  // Optimize: when changing a key that is new and has never been
  1.1085 +  // written to disk, keep type of the operation to store it at opAddItem.
  1.1086 +  // This allows optimization to just forget adding a new key when
  1.1087 +  // it is removed from the storage before flush.
  1.1088 +  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem, DBOperation::opUpdateItem)) {
  1.1089 +    aOperation->mType = DBOperation::opAddItem;
  1.1090 +  }
  1.1091 +
  1.1092 +  // Optimize: to prevent lose of remove operation on a key when doing
  1.1093 +  // remove/set/remove on a previously existing key we have to change
  1.1094 +  // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
  1.1095 +  // pending for the key.
  1.1096 +  if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem, DBOperation::opAddItem)) {
  1.1097 +    aOperation->mType = DBOperation::opUpdateItem;
  1.1098 +  }
  1.1099 +
  1.1100 +  switch (aOperation->Type())
  1.1101 +  {
  1.1102 +  // Operations on single keys
  1.1103 +
  1.1104 +  case DBOperation::opAddItem:
  1.1105 +  case DBOperation::opUpdateItem:
  1.1106 +  case DBOperation::opRemoveItem:
  1.1107 +    // Override any existing operation for the target (=scope+key).
  1.1108 +    mUpdates.Put(aOperation->Target(), aOperation);
  1.1109 +    break;
  1.1110 +
  1.1111 +  // Clear operations
  1.1112 +
  1.1113 +  case DBOperation::opClear:
  1.1114 +  case DBOperation::opClearMatchingScope:
  1.1115 +    // Drop all update (insert/remove) operations for equivavelent or matching scope.
  1.1116 +    // We do this as an optimization as well as a must based on the logic,
  1.1117 +    // if we would not delete the update tasks, changes would have been stored
  1.1118 +    // to the database after clear operations have been executed.
  1.1119 +    mUpdates.Enumerate(ForgetUpdatesForScope, aOperation);
  1.1120 +    mClears.Put(aOperation->Target(), aOperation);
  1.1121 +    break;
  1.1122 +
  1.1123 +  case DBOperation::opClearAll:
  1.1124 +    // Drop simply everything, this is a super-operation.
  1.1125 +    mUpdates.Clear();
  1.1126 +    mClears.Clear();
  1.1127 +    mClears.Put(aOperation->Target(), aOperation);
  1.1128 +    break;
  1.1129 +
  1.1130 +  default:
  1.1131 +    MOZ_ASSERT(false);
  1.1132 +    break;
  1.1133 +  }
  1.1134 +}
  1.1135 +
  1.1136 +namespace { // anon
  1.1137 +
  1.1138 +PLDHashOperator
  1.1139 +CollectTasks(const nsACString& aMapping, nsAutoPtr<DOMStorageDBThread::DBOperation>& aOperation, void* aArg)
  1.1140 +{
  1.1141 +  nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >* tasks =
  1.1142 +    static_cast<nsTArray<nsAutoPtr<DOMStorageDBThread::DBOperation> >*>(aArg);
  1.1143 +
  1.1144 +  tasks->AppendElement(aOperation.forget());
  1.1145 +  return PL_DHASH_NEXT;
  1.1146 +}
  1.1147 +
  1.1148 +} // anon
  1.1149 +
  1.1150 +bool
  1.1151 +DOMStorageDBThread::PendingOperations::Prepare()
  1.1152 +{
  1.1153 +  // Called under the lock
  1.1154 +
  1.1155 +  // First collect clear operations and then updates, we can
  1.1156 +  // do this since whenever a clear operation for a scope is
  1.1157 +  // scheduled, we drop all updates matching that scope. So,
  1.1158 +  // all scope-related update operations we have here now were
  1.1159 +  // scheduled after the clear operations.
  1.1160 +  mClears.Enumerate(CollectTasks, &mExecList);
  1.1161 +  mClears.Clear();
  1.1162 +
  1.1163 +  mUpdates.Enumerate(CollectTasks, &mExecList);
  1.1164 +  mUpdates.Clear();
  1.1165 +
  1.1166 +  return !!mExecList.Length();
  1.1167 +}
  1.1168 +
  1.1169 +nsresult
  1.1170 +DOMStorageDBThread::PendingOperations::Execute(DOMStorageDBThread* aThread)
  1.1171 +{
  1.1172 +  // Called outside the lock
  1.1173 +
  1.1174 +  mozStorageTransaction transaction(aThread->mWorkerConnection, false);
  1.1175 +
  1.1176 +  nsresult rv;
  1.1177 +
  1.1178 +  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
  1.1179 +    DOMStorageDBThread::DBOperation* task = mExecList[i];
  1.1180 +    rv = task->Perform(aThread);
  1.1181 +    if (NS_FAILED(rv)) {
  1.1182 +      return rv;
  1.1183 +    }
  1.1184 +  }
  1.1185 +
  1.1186 +  rv = transaction.Commit();
  1.1187 +  if (NS_FAILED(rv)) {
  1.1188 +    return rv;
  1.1189 +  }
  1.1190 +
  1.1191 +  return NS_OK;
  1.1192 +}
  1.1193 +
  1.1194 +bool
  1.1195 +DOMStorageDBThread::PendingOperations::Finalize(nsresult aRv)
  1.1196 +{
  1.1197 +  // Called under the lock
  1.1198 +
  1.1199 +  // The list is kept on a failure to retry it
  1.1200 +  if (NS_FAILED(aRv)) {
  1.1201 +    // XXX Followup: we may try to reopen the database and flush these
  1.1202 +    // pending tasks, however testing showed that even though I/O is actually
  1.1203 +    // broken some amount of operations is left in sqlite+system buffers and
  1.1204 +    // seems like successfully flushed to disk.
  1.1205 +    // Tested by removing a flash card and disconnecting from network while
  1.1206 +    // using a network drive on Windows system.
  1.1207 +    NS_WARNING("Flush operation on localStorage database failed");
  1.1208 +
  1.1209 +    ++mFlushFailureCount;
  1.1210 +
  1.1211 +    return mFlushFailureCount >= 5;
  1.1212 +  }
  1.1213 +
  1.1214 +  mFlushFailureCount = 0;
  1.1215 +  mExecList.Clear();
  1.1216 +  return true;
  1.1217 +}
  1.1218 +
  1.1219 +namespace { // anon
  1.1220 +
  1.1221 +class FindPendingOperationForScopeData
  1.1222 +{
  1.1223 +public:
  1.1224 +  FindPendingOperationForScopeData(const nsACString& aScope) : mScope(aScope), mFound(false) {}
  1.1225 +  nsCString mScope;
  1.1226 +  bool mFound;
  1.1227 +};
  1.1228 +
  1.1229 +PLDHashOperator
  1.1230 +FindPendingClearForScope(const nsACString& aMapping,
  1.1231 +                         DOMStorageDBThread::DBOperation* aPendingOperation,
  1.1232 +                         void* aArg)
  1.1233 +{
  1.1234 +  FindPendingOperationForScopeData* data =
  1.1235 +    static_cast<FindPendingOperationForScopeData*>(aArg);
  1.1236 +
  1.1237 +  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearAll) {
  1.1238 +    data->mFound = true;
  1.1239 +    return PL_DHASH_STOP;
  1.1240 +  }
  1.1241 +
  1.1242 +  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClear &&
  1.1243 +      data->mScope == aPendingOperation->Scope()) {
  1.1244 +    data->mFound = true;
  1.1245 +    return PL_DHASH_STOP;
  1.1246 +  }
  1.1247 +
  1.1248 +  if (aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opClearMatchingScope &&
  1.1249 +      StringBeginsWith(data->mScope, aPendingOperation->Scope())) {
  1.1250 +    data->mFound = true;
  1.1251 +    return PL_DHASH_STOP;
  1.1252 +  }
  1.1253 +
  1.1254 +  return PL_DHASH_NEXT;
  1.1255 +}
  1.1256 +
  1.1257 +} // anon
  1.1258 +
  1.1259 +bool
  1.1260 +DOMStorageDBThread::PendingOperations::IsScopeClearPending(const nsACString& aScope)
  1.1261 +{
  1.1262 +  // Called under the lock
  1.1263 +
  1.1264 +  FindPendingOperationForScopeData data(aScope);
  1.1265 +  mClears.EnumerateRead(FindPendingClearForScope, &data);
  1.1266 +  if (data.mFound) {
  1.1267 +    return true;
  1.1268 +  }
  1.1269 +
  1.1270 +  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
  1.1271 +    DOMStorageDBThread::DBOperation* task = mExecList[i];
  1.1272 +    FindPendingClearForScope(EmptyCString(), task, &data);
  1.1273 +
  1.1274 +    if (data.mFound) {
  1.1275 +      return true;
  1.1276 +    }
  1.1277 +  }
  1.1278 +
  1.1279 +  return false;
  1.1280 +}
  1.1281 +
  1.1282 +namespace { // anon
  1.1283 +
  1.1284 +PLDHashOperator
  1.1285 +FindPendingUpdateForScope(const nsACString& aMapping,
  1.1286 +                          DOMStorageDBThread::DBOperation* aPendingOperation,
  1.1287 +                          void* aArg)
  1.1288 +{
  1.1289 +  FindPendingOperationForScopeData* data =
  1.1290 +    static_cast<FindPendingOperationForScopeData*>(aArg);
  1.1291 +
  1.1292 +  if ((aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opAddItem ||
  1.1293 +       aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opUpdateItem ||
  1.1294 +       aPendingOperation->Type() == DOMStorageDBThread::DBOperation::opRemoveItem) &&
  1.1295 +       data->mScope == aPendingOperation->Scope()) {
  1.1296 +    data->mFound = true;
  1.1297 +    return PL_DHASH_STOP;
  1.1298 +  }
  1.1299 +
  1.1300 +  return PL_DHASH_NEXT;
  1.1301 +}
  1.1302 +
  1.1303 +} // anon
  1.1304 +
  1.1305 +bool
  1.1306 +DOMStorageDBThread::PendingOperations::IsScopeUpdatePending(const nsACString& aScope)
  1.1307 +{
  1.1308 +  // Called under the lock
  1.1309 +
  1.1310 +  FindPendingOperationForScopeData data(aScope);
  1.1311 +  mUpdates.EnumerateRead(FindPendingUpdateForScope, &data);
  1.1312 +  if (data.mFound) {
  1.1313 +    return true;
  1.1314 +  }
  1.1315 +
  1.1316 +  for (uint32_t i = 0; i < mExecList.Length(); ++i) {
  1.1317 +    DOMStorageDBThread::DBOperation* task = mExecList[i];
  1.1318 +    FindPendingUpdateForScope(EmptyCString(), task, &data);
  1.1319 +
  1.1320 +    if (data.mFound) {
  1.1321 +      return true;
  1.1322 +    }
  1.1323 +  }
  1.1324 +
  1.1325 +  return false;
  1.1326 +}
  1.1327 +
  1.1328 +} // ::dom
  1.1329 +} // ::mozilla

mercurial