storage/src/mozStorageService.cpp

changeset 0
6474c204b198
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/storage/src/mozStorageService.cpp	Wed Dec 31 06:09:35 2014 +0100
     1.3 @@ -0,0 +1,943 @@
     1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
     1.5 + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
     1.6 + * This Source Code Form is subject to the terms of the Mozilla Public
     1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this
     1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
     1.9 +
    1.10 +#include "mozilla/Attributes.h"
    1.11 +#include "mozilla/DebugOnly.h"
    1.12 +
    1.13 +#include "mozStorageService.h"
    1.14 +#include "mozStorageConnection.h"
    1.15 +#include "prinit.h"
    1.16 +#include "nsAutoPtr.h"
    1.17 +#include "nsCollationCID.h"
    1.18 +#include "nsEmbedCID.h"
    1.19 +#include "nsThreadUtils.h"
    1.20 +#include "mozStoragePrivateHelpers.h"
    1.21 +#include "nsILocale.h"
    1.22 +#include "nsILocaleService.h"
    1.23 +#include "nsIXPConnect.h"
    1.24 +#include "nsIObserverService.h"
    1.25 +#include "nsIPropertyBag2.h"
    1.26 +#include "mozilla/Services.h"
    1.27 +#include "mozilla/Preferences.h"
    1.28 +#include "mozilla/LateWriteChecks.h"
    1.29 +#include "mozIStorageCompletionCallback.h"
    1.30 +#include "mozIStoragePendingStatement.h"
    1.31 +
    1.32 +#include "sqlite3.h"
    1.33 +
    1.34 +#ifdef SQLITE_OS_WIN
    1.35 +// "windows.h" was included and it can #define lots of things we care about...
    1.36 +#undef CompareString
    1.37 +#endif
    1.38 +
    1.39 +#include "nsIPromptService.h"
    1.40 +
    1.41 +#ifdef MOZ_STORAGE_MEMORY
    1.42 +#  include "mozmemory.h"
    1.43 +#  ifdef MOZ_DMD
    1.44 +#    include "DMD.h"
    1.45 +#  endif
    1.46 +#endif
    1.47 +
    1.48 +////////////////////////////////////////////////////////////////////////////////
    1.49 +//// Defines
    1.50 +
    1.51 +#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
    1.52 +#define PREF_TS_SYNCHRONOUS_DEFAULT 1
    1.53 +
    1.54 +#define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
    1.55 +
    1.56 +// This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
    1.57 +// db/sqlite3/src/Makefile.in.
    1.58 +#define PREF_TS_PAGESIZE_DEFAULT 32768
    1.59 +
    1.60 +namespace mozilla {
    1.61 +namespace storage {
    1.62 +
    1.63 +////////////////////////////////////////////////////////////////////////////////
    1.64 +//// Memory Reporting
    1.65 +
    1.66 +#ifdef MOZ_DMD
    1.67 +static mozilla::Atomic<size_t> gSqliteMemoryUsed;
    1.68 +#endif
    1.69 +
    1.70 +static int64_t
    1.71 +StorageSQLiteDistinguishedAmount()
    1.72 +{
    1.73 +  return ::sqlite3_memory_used();
    1.74 +}
    1.75 +
    1.76 +/**
    1.77 + * Passes a single SQLite memory statistic to a memory reporter callback.
    1.78 + *
    1.79 + * @param aHandleReport
    1.80 + *        The callback.
    1.81 + * @param aData
    1.82 + *        The data for the callback.
    1.83 + * @param aConn
    1.84 + *        The SQLite connection.
    1.85 + * @param aPathHead
    1.86 + *        Head of the path for the memory report.
    1.87 + * @param aKind
    1.88 + *        The memory report statistic kind, one of "stmt", "cache" or
    1.89 + *        "schema".
    1.90 + * @param aDesc
    1.91 + *        The memory report description.
    1.92 + * @param aOption
    1.93 + *        The SQLite constant for getting the measurement.
    1.94 + * @param aTotal
    1.95 + *        The accumulator for the measurement.
    1.96 + */
    1.97 +nsresult
    1.98 +ReportConn(nsIHandleReportCallback *aHandleReport,
    1.99 +           nsISupports *aData,
   1.100 +           Connection *aConn,
   1.101 +           const nsACString &aPathHead,
   1.102 +           const nsACString &aKind,
   1.103 +           const nsACString &aDesc,
   1.104 +           int32_t aOption,
   1.105 +           size_t *aTotal)
   1.106 +{
   1.107 +  nsCString path(aPathHead);
   1.108 +  path.Append(aKind);
   1.109 +  path.AppendLiteral("-used");
   1.110 +
   1.111 +  int32_t val = aConn->getSqliteRuntimeStatus(aOption);
   1.112 +  nsresult rv = aHandleReport->Callback(EmptyCString(), path,
   1.113 +                                        nsIMemoryReporter::KIND_HEAP,
   1.114 +                                        nsIMemoryReporter::UNITS_BYTES,
   1.115 +                                        int64_t(val), aDesc, aData);
   1.116 +  NS_ENSURE_SUCCESS(rv, rv);
   1.117 +  *aTotal += val;
   1.118 +
   1.119 +  return NS_OK;
   1.120 +}
   1.121 +
   1.122 +// Warning: To get a Connection's measurements requires holding its lock.
   1.123 +// There may be a delay getting the lock if another thread is accessing the
   1.124 +// Connection.  This isn't very nice if CollectReports is called from the main
   1.125 +// thread!  But at the time of writing this function is only called when
   1.126 +// about:memory is loaded (not, for example, when telemetry pings occur) and
   1.127 +// any delays in that case aren't so bad.
   1.128 +NS_IMETHODIMP
   1.129 +Service::CollectReports(nsIHandleReportCallback *aHandleReport,
   1.130 +                        nsISupports *aData)
   1.131 +{
   1.132 +  nsresult rv;
   1.133 +  size_t totalConnSize = 0;
   1.134 +  {
   1.135 +    nsTArray<nsRefPtr<Connection> > connections;
   1.136 +    getConnections(connections);
   1.137 +
   1.138 +    for (uint32_t i = 0; i < connections.Length(); i++) {
   1.139 +      nsRefPtr<Connection> &conn = connections[i];
   1.140 +
   1.141 +      // Someone may have closed the Connection, in which case we skip it.
   1.142 +      bool isReady;
   1.143 +      (void)conn->GetConnectionReady(&isReady);
   1.144 +      if (!isReady) {
   1.145 +          continue;
   1.146 +      }
   1.147 +
   1.148 +      nsCString pathHead("explicit/storage/sqlite/");
   1.149 +      pathHead.Append(conn->getFilename());
   1.150 +      pathHead.AppendLiteral("/");
   1.151 +
   1.152 +      SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
   1.153 +
   1.154 +      NS_NAMED_LITERAL_CSTRING(stmtDesc,
   1.155 +        "Memory (approximate) used by all prepared statements used by "
   1.156 +        "connections to this database.");
   1.157 +      rv = ReportConn(aHandleReport, aData, conn, pathHead,
   1.158 +                      NS_LITERAL_CSTRING("stmt"), stmtDesc,
   1.159 +                      SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
   1.160 +      NS_ENSURE_SUCCESS(rv, rv);
   1.161 +
   1.162 +      NS_NAMED_LITERAL_CSTRING(cacheDesc,
   1.163 +        "Memory (approximate) used by all pager caches used by connections "
   1.164 +        "to this database.");
   1.165 +      rv = ReportConn(aHandleReport, aData, conn, pathHead,
   1.166 +                      NS_LITERAL_CSTRING("cache"), cacheDesc,
   1.167 +                      SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
   1.168 +      NS_ENSURE_SUCCESS(rv, rv);
   1.169 +
   1.170 +      NS_NAMED_LITERAL_CSTRING(schemaDesc,
   1.171 +        "Memory (approximate) used to store the schema for all databases "
   1.172 +        "associated with connections to this database.");
   1.173 +      rv = ReportConn(aHandleReport, aData, conn, pathHead,
   1.174 +                      NS_LITERAL_CSTRING("schema"), schemaDesc,
   1.175 +                      SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
   1.176 +      NS_ENSURE_SUCCESS(rv, rv);
   1.177 +    }
   1.178 +
   1.179 +#ifdef MOZ_DMD
   1.180 +    if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
   1.181 +      NS_WARNING("memory consumption reported by SQLite doesn't match "
   1.182 +                 "our measurements");
   1.183 +    }
   1.184 +#endif
   1.185 +  }
   1.186 +
   1.187 +  int64_t other = ::sqlite3_memory_used() - totalConnSize;
   1.188 +
   1.189 +  rv = aHandleReport->Callback(
   1.190 +          EmptyCString(),
   1.191 +          NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
   1.192 +          KIND_HEAP, UNITS_BYTES, other,
   1.193 +          NS_LITERAL_CSTRING("All unclassified sqlite memory."),
   1.194 +          aData);
   1.195 +  NS_ENSURE_SUCCESS(rv, rv);
   1.196 +
   1.197 +  return NS_OK;
   1.198 +}
   1.199 +
   1.200 +////////////////////////////////////////////////////////////////////////////////
   1.201 +//// Service
   1.202 +
   1.203 +NS_IMPL_ISUPPORTS(
   1.204 +  Service,
   1.205 +  mozIStorageService,
   1.206 +  nsIObserver,
   1.207 +  nsIMemoryReporter
   1.208 +)
   1.209 +
   1.210 +Service *Service::gService = nullptr;
   1.211 +
   1.212 +Service *
   1.213 +Service::getSingleton()
   1.214 +{
   1.215 +  if (gService) {
   1.216 +    NS_ADDREF(gService);
   1.217 +    return gService;
   1.218 +  }
   1.219 +
   1.220 +  // Ensure that we are using the same version of SQLite that we compiled with
   1.221 +  // or newer.  Our configure check ensures we are using a new enough version
   1.222 +  // at compile time.
   1.223 +  if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
   1.224 +    nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
   1.225 +    if (ps) {
   1.226 +      nsAutoString title, message;
   1.227 +      title.AppendLiteral("SQLite Version Error");
   1.228 +      message.AppendLiteral("The application has been updated, but your version "
   1.229 +                          "of SQLite is too old and the application cannot "
   1.230 +                          "run.");
   1.231 +      (void)ps->Alert(nullptr, title.get(), message.get());
   1.232 +    }
   1.233 +    ::PR_Abort();
   1.234 +  }
   1.235 +
   1.236 +  // The first reference to the storage service must be obtained on the
   1.237 +  // main thread.
   1.238 +  NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
   1.239 +  gService = new Service();
   1.240 +  if (gService) {
   1.241 +    NS_ADDREF(gService);
   1.242 +    if (NS_FAILED(gService->initialize()))
   1.243 +      NS_RELEASE(gService);
   1.244 +  }
   1.245 +
   1.246 +  return gService;
   1.247 +}
   1.248 +
   1.249 +nsIXPConnect *Service::sXPConnect = nullptr;
   1.250 +
   1.251 +// static
   1.252 +already_AddRefed<nsIXPConnect>
   1.253 +Service::getXPConnect()
   1.254 +{
   1.255 +  NS_PRECONDITION(NS_IsMainThread(),
   1.256 +                  "Must only get XPConnect on the main thread!");
   1.257 +  NS_PRECONDITION(gService,
   1.258 +                  "Can not get XPConnect without an instance of our service!");
   1.259 +
   1.260 +  // If we've been shutdown, sXPConnect will be null.  To prevent leaks, we do
   1.261 +  // not cache the service after this point.
   1.262 +  nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
   1.263 +  if (!xpc)
   1.264 +    xpc = do_GetService(nsIXPConnect::GetCID());
   1.265 +  NS_ASSERTION(xpc, "Could not get XPConnect!");
   1.266 +  return xpc.forget();
   1.267 +}
   1.268 +
   1.269 +int32_t Service::sSynchronousPref;
   1.270 +
   1.271 +// static
   1.272 +int32_t
   1.273 +Service::getSynchronousPref()
   1.274 +{
   1.275 +  return sSynchronousPref;
   1.276 +}
   1.277 +
   1.278 +int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
   1.279 +
   1.280 +Service::Service()
   1.281 +: mMutex("Service::mMutex")
   1.282 +, mSqliteVFS(nullptr)
   1.283 +, mRegistrationMutex("Service::mRegistrationMutex")
   1.284 +, mConnections()
   1.285 +{
   1.286 +}
   1.287 +
   1.288 +Service::~Service()
   1.289 +{
   1.290 +  mozilla::UnregisterWeakMemoryReporter(this);
   1.291 +  mozilla::UnregisterStorageSQLiteDistinguishedAmount();
   1.292 +
   1.293 +  int rc = sqlite3_vfs_unregister(mSqliteVFS);
   1.294 +  if (rc != SQLITE_OK)
   1.295 +    NS_WARNING("Failed to unregister sqlite vfs wrapper.");
   1.296 +
   1.297 +  // Shutdown the sqlite3 API.  Warn if shutdown did not turn out okay, but
   1.298 +  // there is nothing actionable we can do in that case.
   1.299 +  rc = ::sqlite3_shutdown();
   1.300 +  if (rc != SQLITE_OK)
   1.301 +    NS_WARNING("sqlite3 did not shutdown cleanly.");
   1.302 +
   1.303 +  DebugOnly<bool> shutdownObserved = !sXPConnect;
   1.304 +  NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
   1.305 +
   1.306 +  gService = nullptr;
   1.307 +  delete mSqliteVFS;
   1.308 +  mSqliteVFS = nullptr;
   1.309 +}
   1.310 +
   1.311 +void
   1.312 +Service::registerConnection(Connection *aConnection)
   1.313 +{
   1.314 +  mRegistrationMutex.AssertNotCurrentThreadOwns();
   1.315 +  MutexAutoLock mutex(mRegistrationMutex);
   1.316 +  (void)mConnections.AppendElement(aConnection);
   1.317 +}
   1.318 +
   1.319 +void
   1.320 +Service::unregisterConnection(Connection *aConnection)
   1.321 +{
   1.322 +  // If this is the last Connection it might be the only thing keeping Service
   1.323 +  // alive.  So ensure that Service is destroyed only after the Connection is
   1.324 +  // cleanly unregistered and destroyed.
   1.325 +  nsRefPtr<Service> kungFuDeathGrip(this);
   1.326 +  {
   1.327 +    mRegistrationMutex.AssertNotCurrentThreadOwns();
   1.328 +    MutexAutoLock mutex(mRegistrationMutex);
   1.329 +    DebugOnly<bool> removed = mConnections.RemoveElement(aConnection);
   1.330 +    // Assert if we try to unregister a non-existent connection.
   1.331 +    MOZ_ASSERT(removed);
   1.332 +  }
   1.333 +}
   1.334 +
   1.335 +void
   1.336 +Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections)
   1.337 +{
   1.338 +  mRegistrationMutex.AssertNotCurrentThreadOwns();
   1.339 +  MutexAutoLock mutex(mRegistrationMutex);
   1.340 +  aConnections.Clear();
   1.341 +  aConnections.AppendElements(mConnections);
   1.342 +}
   1.343 +
   1.344 +void
   1.345 +Service::minimizeMemory()
   1.346 +{
   1.347 +  nsTArray<nsRefPtr<Connection> > connections;
   1.348 +  getConnections(connections);
   1.349 +
   1.350 +  for (uint32_t i = 0; i < connections.Length(); i++) {
   1.351 +    nsRefPtr<Connection> conn = connections[i];
   1.352 +    if (conn->connectionReady()) {
   1.353 +      NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
   1.354 +      nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
   1.355 +        NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
   1.356 +      DebugOnly<nsresult> rv;
   1.357 +
   1.358 +      if (!syncConn) {
   1.359 +        nsCOMPtr<mozIStoragePendingStatement> ps;
   1.360 +        rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr,
   1.361 +          getter_AddRefs(ps));
   1.362 +      } else {
   1.363 +        rv = connections[i]->ExecuteSimpleSQL(shrinkPragma);
   1.364 +      }
   1.365 +
   1.366 +      MOZ_ASSERT(NS_SUCCEEDED(rv),
   1.367 +        "Should have been able to purge sqlite caches");
   1.368 +    }
   1.369 +  }
   1.370 +}
   1.371 +
   1.372 +void
   1.373 +Service::shutdown()
   1.374 +{
   1.375 +  NS_IF_RELEASE(sXPConnect);
   1.376 +}
   1.377 +
   1.378 +sqlite3_vfs *ConstructTelemetryVFS();
   1.379 +
   1.380 +#ifdef MOZ_STORAGE_MEMORY
   1.381 +
   1.382 +namespace {
   1.383 +
   1.384 +// By default, SQLite tracks the size of all its heap blocks by adding an extra
   1.385 +// 8 bytes at the start of the block to hold the size.  Unfortunately, this
   1.386 +// causes a lot of 2^N-sized allocations to be rounded up by jemalloc
   1.387 +// allocator, wasting memory.  For example, a request for 1024 bytes has 8
   1.388 +// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
   1.389 +// to 2048 bytes, wasting 1012 bytes.  (See bug 676189 for more details.)
   1.390 +//
   1.391 +// So we register jemalloc as the malloc implementation, which avoids this
   1.392 +// 8-byte overhead, and thus a lot of waste.  This requires us to provide a
   1.393 +// function, sqliteMemRoundup(), which computes the actual size that will be
   1.394 +// allocated for a given request.  SQLite uses this function before all
   1.395 +// allocations, and may be able to use any excess bytes caused by the rounding.
   1.396 +//
   1.397 +// Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size
   1.398 +// are necessary because the sqlite_mem_methods type signatures differ slightly
   1.399 +// from the standard ones -- they use int instead of size_t.  But we don't need
   1.400 +// a wrapper for moz_free.
   1.401 +
   1.402 +#ifdef MOZ_DMD
   1.403 +
   1.404 +// sqlite does its own memory accounting, and we use its numbers in our memory
   1.405 +// reporters.  But we don't want sqlite's heap blocks to show up in DMD's
   1.406 +// output as unreported, so we mark them as reported when they're allocated and
   1.407 +// mark them as unreported when they are freed.
   1.408 +//
   1.409 +// In other words, we are marking all sqlite heap blocks as reported even
   1.410 +// though we're not reporting them ourselves.  Instead we're trusting that
   1.411 +// sqlite is fully and correctly accounting for all of its heap blocks via its
   1.412 +// own memory accounting.  Well, we don't have to trust it entirely, because
   1.413 +// it's easy to keep track (while doing this DMD-specific marking) of exactly
   1.414 +// how much memory SQLite is using.  And we can compare that against what
   1.415 +// SQLite reports it is using.
   1.416 +
   1.417 +MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
   1.418 +MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
   1.419 +
   1.420 +#endif
   1.421 +
   1.422 +static void *sqliteMemMalloc(int n)
   1.423 +{
   1.424 +  void* p = ::moz_malloc(n);
   1.425 +#ifdef MOZ_DMD
   1.426 +  gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
   1.427 +#endif
   1.428 +  return p;
   1.429 +}
   1.430 +
   1.431 +static void sqliteMemFree(void *p)
   1.432 +{
   1.433 +#ifdef MOZ_DMD
   1.434 +  gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
   1.435 +#endif
   1.436 +  ::moz_free(p);
   1.437 +}
   1.438 +
   1.439 +static void *sqliteMemRealloc(void *p, int n)
   1.440 +{
   1.441 +#ifdef MOZ_DMD
   1.442 +  gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
   1.443 +  void *pnew = ::moz_realloc(p, n);
   1.444 +  if (pnew) {
   1.445 +    gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
   1.446 +  } else {
   1.447 +    // realloc failed;  undo the SqliteMallocSizeOfOnFree from above
   1.448 +    gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
   1.449 +  }
   1.450 +  return pnew;
   1.451 +#else
   1.452 +  return ::moz_realloc(p, n);
   1.453 +#endif
   1.454 +}
   1.455 +
   1.456 +static int sqliteMemSize(void *p)
   1.457 +{
   1.458 +  return ::moz_malloc_usable_size(p);
   1.459 +}
   1.460 +
   1.461 +static int sqliteMemRoundup(int n)
   1.462 +{
   1.463 +  n = malloc_good_size(n);
   1.464 +
   1.465 +  // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
   1.466 +  // allocations be 8-aligned.  So we round up sub-8 requests to 8.  This
   1.467 +  // wastes a small amount of memory but is obviously safe.
   1.468 +  return n <= 8 ? 8 : n;
   1.469 +}
   1.470 +
   1.471 +static int sqliteMemInit(void *p)
   1.472 +{
   1.473 +  return 0;
   1.474 +}
   1.475 +
   1.476 +static void sqliteMemShutdown(void *p)
   1.477 +{
   1.478 +}
   1.479 +
   1.480 +const sqlite3_mem_methods memMethods = {
   1.481 +  &sqliteMemMalloc,
   1.482 +  &sqliteMemFree,
   1.483 +  &sqliteMemRealloc,
   1.484 +  &sqliteMemSize,
   1.485 +  &sqliteMemRoundup,
   1.486 +  &sqliteMemInit,
   1.487 +  &sqliteMemShutdown,
   1.488 +  nullptr
   1.489 +};
   1.490 +
   1.491 +} // anonymous namespace
   1.492 +
   1.493 +#endif  // MOZ_STORAGE_MEMORY
   1.494 +
   1.495 +static const char* sObserverTopics[] = {
   1.496 +  "memory-pressure",
   1.497 +  "xpcom-shutdown",
   1.498 +  "xpcom-shutdown-threads"
   1.499 +};
   1.500 +
   1.501 +nsresult
   1.502 +Service::initialize()
   1.503 +{
   1.504 +  MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
   1.505 +
   1.506 +  int rc;
   1.507 +
   1.508 +#ifdef MOZ_STORAGE_MEMORY
   1.509 +  rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
   1.510 +  if (rc != SQLITE_OK)
   1.511 +    return convertResultCode(rc);
   1.512 +#endif
   1.513 +
   1.514 +  // Explicitly initialize sqlite3.  Although this is implicitly called by
   1.515 +  // various sqlite3 functions (and the sqlite3_open calls in our case),
   1.516 +  // the documentation suggests calling this directly.  So we do.
   1.517 +  rc = ::sqlite3_initialize();
   1.518 +  if (rc != SQLITE_OK)
   1.519 +    return convertResultCode(rc);
   1.520 +
   1.521 +  mSqliteVFS = ConstructTelemetryVFS();
   1.522 +  if (mSqliteVFS) {
   1.523 +    rc = sqlite3_vfs_register(mSqliteVFS, 1);
   1.524 +    if (rc != SQLITE_OK)
   1.525 +      return convertResultCode(rc);
   1.526 +  } else {
   1.527 +    NS_WARNING("Failed to register telemetry VFS");
   1.528 +  }
   1.529 +
   1.530 +  // Register for xpcom-shutdown so we can cleanup after ourselves.  The
   1.531 +  // observer service can only be used on the main thread.
   1.532 +  nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
   1.533 +  NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
   1.534 +
   1.535 +  for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
   1.536 +    nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
   1.537 +    if (NS_WARN_IF(NS_FAILED(rv))) {
   1.538 +      return rv;
   1.539 +    }
   1.540 +  }
   1.541 +
   1.542 +  // We cache XPConnect for our language helpers.  XPConnect can only be
   1.543 +  // used on the main thread.
   1.544 +  (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
   1.545 +
   1.546 +  // We need to obtain the toolkit.storage.synchronous preferences on the main
   1.547 +  // thread because the preference service can only be accessed there.  This
   1.548 +  // is cached in the service for all future Open[Unshared]Database calls.
   1.549 +  sSynchronousPref =
   1.550 +    Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
   1.551 +
   1.552 +  // We need to obtain the toolkit.storage.pageSize preferences on the main
   1.553 +  // thread because the preference service can only be accessed there.  This
   1.554 +  // is cached in the service for all future Open[Unshared]Database calls.
   1.555 +  sDefaultPageSize =
   1.556 +      Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
   1.557 +
   1.558 +  mozilla::RegisterWeakMemoryReporter(this);
   1.559 +  mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
   1.560 +
   1.561 +  return NS_OK;
   1.562 +}
   1.563 +
   1.564 +int
   1.565 +Service::localeCompareStrings(const nsAString &aStr1,
   1.566 +                              const nsAString &aStr2,
   1.567 +                              int32_t aComparisonStrength)
   1.568 +{
   1.569 +  // The implementation of nsICollation.CompareString() is platform-dependent.
   1.570 +  // On Linux it's not thread-safe.  It may not be on Windows and OS X either,
   1.571 +  // but it's more difficult to tell.  We therefore synchronize this method.
   1.572 +  MutexAutoLock mutex(mMutex);
   1.573 +
   1.574 +  nsICollation *coll = getLocaleCollation();
   1.575 +  if (!coll) {
   1.576 +    NS_ERROR("Storage service has no collation");
   1.577 +    return 0;
   1.578 +  }
   1.579 +
   1.580 +  int32_t res;
   1.581 +  nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
   1.582 +  if (NS_FAILED(rv)) {
   1.583 +    NS_ERROR("Collation compare string failed");
   1.584 +    return 0;
   1.585 +  }
   1.586 +
   1.587 +  return res;
   1.588 +}
   1.589 +
   1.590 +nsICollation *
   1.591 +Service::getLocaleCollation()
   1.592 +{
   1.593 +  mMutex.AssertCurrentThreadOwns();
   1.594 +
   1.595 +  if (mLocaleCollation)
   1.596 +    return mLocaleCollation;
   1.597 +
   1.598 +  nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
   1.599 +  if (!svc) {
   1.600 +    NS_WARNING("Could not get locale service");
   1.601 +    return nullptr;
   1.602 +  }
   1.603 +
   1.604 +  nsCOMPtr<nsILocale> appLocale;
   1.605 +  nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
   1.606 +  if (NS_FAILED(rv)) {
   1.607 +    NS_WARNING("Could not get application locale");
   1.608 +    return nullptr;
   1.609 +  }
   1.610 +
   1.611 +  nsCOMPtr<nsICollationFactory> collFact =
   1.612 +    do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
   1.613 +  if (!collFact) {
   1.614 +    NS_WARNING("Could not create collation factory");
   1.615 +    return nullptr;
   1.616 +  }
   1.617 +
   1.618 +  rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
   1.619 +  if (NS_FAILED(rv)) {
   1.620 +    NS_WARNING("Could not create collation");
   1.621 +    return nullptr;
   1.622 +  }
   1.623 +
   1.624 +  return mLocaleCollation;
   1.625 +}
   1.626 +
   1.627 +////////////////////////////////////////////////////////////////////////////////
   1.628 +//// mozIStorageService
   1.629 +
   1.630 +
   1.631 +NS_IMETHODIMP
   1.632 +Service::OpenSpecialDatabase(const char *aStorageKey,
   1.633 +                             mozIStorageConnection **_connection)
   1.634 +{
   1.635 +  nsresult rv;
   1.636 +
   1.637 +  nsCOMPtr<nsIFile> storageFile;
   1.638 +  if (::strcmp(aStorageKey, "memory") == 0) {
   1.639 +    // just fall through with nullptr storageFile, this will cause the storage
   1.640 +    // connection to use a memory DB.
   1.641 +  }
   1.642 +  else {
   1.643 +    return NS_ERROR_INVALID_ARG;
   1.644 +  }
   1.645 +
   1.646 +  nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
   1.647 +
   1.648 +  rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
   1.649 +  NS_ENSURE_SUCCESS(rv, rv);
   1.650 +
   1.651 +  msc.forget(_connection);
   1.652 +  return NS_OK;
   1.653 +
   1.654 +}
   1.655 +
   1.656 +namespace {
   1.657 +
   1.658 +class AsyncInitDatabase MOZ_FINAL : public nsRunnable
   1.659 +{
   1.660 +public:
   1.661 +  AsyncInitDatabase(Connection* aConnection,
   1.662 +                    nsIFile* aStorageFile,
   1.663 +                    int32_t aGrowthIncrement,
   1.664 +                    mozIStorageCompletionCallback* aCallback)
   1.665 +    : mConnection(aConnection)
   1.666 +    , mStorageFile(aStorageFile)
   1.667 +    , mGrowthIncrement(aGrowthIncrement)
   1.668 +    , mCallback(aCallback)
   1.669 +  {
   1.670 +    MOZ_ASSERT(NS_IsMainThread());
   1.671 +  }
   1.672 +
   1.673 +  NS_IMETHOD Run()
   1.674 +  {
   1.675 +    MOZ_ASSERT(!NS_IsMainThread());
   1.676 +    nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
   1.677 +                               : mConnection->initialize();
   1.678 +    if (NS_FAILED(rv)) {
   1.679 +      return DispatchResult(rv, nullptr);
   1.680 +    }
   1.681 +
   1.682 +    if (mGrowthIncrement >= 0) {
   1.683 +      // Ignore errors. In the future, we might wish to log them.
   1.684 +      (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
   1.685 +    }
   1.686 +
   1.687 +    return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
   1.688 +                          mConnection));
   1.689 +  }
   1.690 +
   1.691 +private:
   1.692 +  nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
   1.693 +    nsRefPtr<CallbackComplete> event =
   1.694 +      new CallbackComplete(aStatus,
   1.695 +                           aValue,
   1.696 +                           mCallback.forget());
   1.697 +    return NS_DispatchToMainThread(event);
   1.698 +  }
   1.699 +
   1.700 +  ~AsyncInitDatabase()
   1.701 +  {
   1.702 +    nsCOMPtr<nsIThread> thread;
   1.703 +    DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
   1.704 +    MOZ_ASSERT(NS_SUCCEEDED(rv));
   1.705 +    (void)NS_ProxyRelease(thread, mStorageFile);
   1.706 +
   1.707 +    // Handle ambiguous nsISupports inheritance.
   1.708 +    Connection *rawConnection = nullptr;
   1.709 +    mConnection.swap(rawConnection);
   1.710 +    (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
   1.711 +                                                    rawConnection));
   1.712 +
   1.713 +    // Generally, the callback will be released by CallbackComplete.
   1.714 +    // However, if for some reason Run() is not executed, we still
   1.715 +    // need to ensure that it is released here.
   1.716 +    mozIStorageCompletionCallback *rawCallback = nullptr;
   1.717 +    mCallback.swap(rawCallback);
   1.718 +    (void)NS_ProxyRelease(thread, rawCallback);
   1.719 +  }
   1.720 +
   1.721 +  nsRefPtr<Connection> mConnection;
   1.722 +  nsCOMPtr<nsIFile> mStorageFile;
   1.723 +  int32_t mGrowthIncrement;
   1.724 +  nsRefPtr<mozIStorageCompletionCallback> mCallback;
   1.725 +};
   1.726 +
   1.727 +} // anonymous namespace
   1.728 +
   1.729 +NS_IMETHODIMP
   1.730 +Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
   1.731 +                           nsIPropertyBag2 *aOptions,
   1.732 +                           mozIStorageCompletionCallback *aCallback)
   1.733 +{
   1.734 +  if (!NS_IsMainThread()) {
   1.735 +    return NS_ERROR_NOT_SAME_THREAD;
   1.736 +  }
   1.737 +  NS_ENSURE_ARG(aDatabaseStore);
   1.738 +  NS_ENSURE_ARG(aCallback);
   1.739 +
   1.740 +  nsCOMPtr<nsIFile> storageFile;
   1.741 +  int flags = SQLITE_OPEN_READWRITE;
   1.742 +
   1.743 +  nsCOMPtr<nsISupports> dbStore;
   1.744 +  nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
   1.745 +  if (NS_SUCCEEDED(rv)) {
   1.746 +    // Generally, aDatabaseStore holds the database nsIFile.
   1.747 +    storageFile = do_QueryInterface(dbStore, &rv);
   1.748 +    if (NS_FAILED(rv)) {
   1.749 +      return NS_ERROR_INVALID_ARG;
   1.750 +    }
   1.751 +
   1.752 +    rv = storageFile->Clone(getter_AddRefs(storageFile));
   1.753 +    MOZ_ASSERT(NS_SUCCEEDED(rv));
   1.754 +
   1.755 +    // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
   1.756 +    flags |= SQLITE_OPEN_CREATE;
   1.757 +
   1.758 +    // Extract and apply the shared-cache option.
   1.759 +    bool shared = false;
   1.760 +    if (aOptions) {
   1.761 +      rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
   1.762 +      if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
   1.763 +        return NS_ERROR_INVALID_ARG;
   1.764 +      }
   1.765 +    }
   1.766 +    flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
   1.767 +  } else {
   1.768 +    // Sometimes, however, it's a special database name.
   1.769 +    nsAutoCString keyString;
   1.770 +    rv = aDatabaseStore->GetAsACString(keyString);
   1.771 +    if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
   1.772 +      return NS_ERROR_INVALID_ARG;
   1.773 +    }
   1.774 +
   1.775 +    // Just fall through with nullptr storageFile, this will cause the storage
   1.776 +    // connection to use a memory DB.
   1.777 +  }
   1.778 +
   1.779 +  int32_t growthIncrement = -1;
   1.780 +  if (aOptions && storageFile) {
   1.781 +    rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
   1.782 +                                      &growthIncrement);
   1.783 +    if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
   1.784 +      return NS_ERROR_INVALID_ARG;
   1.785 +    }
   1.786 +  }
   1.787 +
   1.788 +  // Create connection on this thread, but initialize it on its helper thread.
   1.789 +  nsRefPtr<Connection> msc = new Connection(this, flags, true);
   1.790 +  nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
   1.791 +  MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
   1.792 +
   1.793 +  nsRefPtr<AsyncInitDatabase> asyncInit =
   1.794 +    new AsyncInitDatabase(msc,
   1.795 +                          storageFile,
   1.796 +                          growthIncrement,
   1.797 +                          aCallback);
   1.798 +  return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
   1.799 +}
   1.800 +
   1.801 +NS_IMETHODIMP
   1.802 +Service::OpenDatabase(nsIFile *aDatabaseFile,
   1.803 +                      mozIStorageConnection **_connection)
   1.804 +{
   1.805 +  NS_ENSURE_ARG(aDatabaseFile);
   1.806 +
   1.807 +  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   1.808 +  // reasons.
   1.809 +  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
   1.810 +              SQLITE_OPEN_CREATE;
   1.811 +  nsRefPtr<Connection> msc = new Connection(this, flags, false);
   1.812 +
   1.813 +  nsresult rv = msc->initialize(aDatabaseFile);
   1.814 +  NS_ENSURE_SUCCESS(rv, rv);
   1.815 +
   1.816 +  msc.forget(_connection);
   1.817 +  return NS_OK;
   1.818 +}
   1.819 +
   1.820 +NS_IMETHODIMP
   1.821 +Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
   1.822 +                              mozIStorageConnection **_connection)
   1.823 +{
   1.824 +  NS_ENSURE_ARG(aDatabaseFile);
   1.825 +
   1.826 +  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   1.827 +  // reasons.
   1.828 +  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
   1.829 +              SQLITE_OPEN_CREATE;
   1.830 +  nsRefPtr<Connection> msc = new Connection(this, flags, false);
   1.831 +
   1.832 +  nsresult rv = msc->initialize(aDatabaseFile);
   1.833 +  NS_ENSURE_SUCCESS(rv, rv);
   1.834 +
   1.835 +  msc.forget(_connection);
   1.836 +  return NS_OK;
   1.837 +}
   1.838 +
   1.839 +NS_IMETHODIMP
   1.840 +Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
   1.841 +                                 mozIStorageConnection **_connection)
   1.842 +{
   1.843 +  NS_ENSURE_ARG(aFileURL);
   1.844 +
   1.845 +  // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
   1.846 +  // reasons.
   1.847 +  int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
   1.848 +              SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
   1.849 +  nsRefPtr<Connection> msc = new Connection(this, flags, false);
   1.850 +
   1.851 +  nsresult rv = msc->initialize(aFileURL);
   1.852 +  NS_ENSURE_SUCCESS(rv, rv);
   1.853 +
   1.854 +  msc.forget(_connection);
   1.855 +  return NS_OK;
   1.856 +}
   1.857 +
   1.858 +NS_IMETHODIMP
   1.859 +Service::BackupDatabaseFile(nsIFile *aDBFile,
   1.860 +                            const nsAString &aBackupFileName,
   1.861 +                            nsIFile *aBackupParentDirectory,
   1.862 +                            nsIFile **backup)
   1.863 +{
   1.864 +  nsresult rv;
   1.865 +  nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
   1.866 +  if (!parentDir) {
   1.867 +    // This argument is optional, and defaults to the same parent directory
   1.868 +    // as the current file.
   1.869 +    rv = aDBFile->GetParent(getter_AddRefs(parentDir));
   1.870 +    NS_ENSURE_SUCCESS(rv, rv);
   1.871 +  }
   1.872 +
   1.873 +  nsCOMPtr<nsIFile> backupDB;
   1.874 +  rv = parentDir->Clone(getter_AddRefs(backupDB));
   1.875 +  NS_ENSURE_SUCCESS(rv, rv);
   1.876 +
   1.877 +  rv = backupDB->Append(aBackupFileName);
   1.878 +  NS_ENSURE_SUCCESS(rv, rv);
   1.879 +
   1.880 +  rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
   1.881 +  NS_ENSURE_SUCCESS(rv, rv);
   1.882 +
   1.883 +  nsAutoString fileName;
   1.884 +  rv = backupDB->GetLeafName(fileName);
   1.885 +  NS_ENSURE_SUCCESS(rv, rv);
   1.886 +
   1.887 +  rv = backupDB->Remove(false);
   1.888 +  NS_ENSURE_SUCCESS(rv, rv);
   1.889 +
   1.890 +  backupDB.forget(backup);
   1.891 +
   1.892 +  return aDBFile->CopyTo(parentDir, fileName);
   1.893 +}
   1.894 +
   1.895 +////////////////////////////////////////////////////////////////////////////////
   1.896 +//// nsIObserver
   1.897 +
   1.898 +NS_IMETHODIMP
   1.899 +Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
   1.900 +{
   1.901 +  if (strcmp(aTopic, "memory-pressure") == 0) {
   1.902 +    minimizeMemory();
   1.903 +  } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
   1.904 +    shutdown();
   1.905 +  } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
   1.906 +    nsCOMPtr<nsIObserverService> os =
   1.907 +      mozilla::services::GetObserverService();
   1.908 +
   1.909 +    for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
   1.910 +      (void)os->RemoveObserver(this, sObserverTopics[i]);
   1.911 +    }
   1.912 +
   1.913 +    bool anyOpen = false;
   1.914 +    do {
   1.915 +      nsTArray<nsRefPtr<Connection> > connections;
   1.916 +      getConnections(connections);
   1.917 +      anyOpen = false;
   1.918 +      for (uint32_t i = 0; i < connections.Length(); i++) {
   1.919 +        nsRefPtr<Connection> &conn = connections[i];
   1.920 +        if (conn->isClosing()) {
   1.921 +          anyOpen = true;
   1.922 +          break;
   1.923 +        }
   1.924 +      }
   1.925 +      if (anyOpen) {
   1.926 +        nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
   1.927 +        NS_ProcessNextEvent(thread);
   1.928 +      }
   1.929 +    } while (anyOpen);
   1.930 +
   1.931 +    if (gShutdownChecks == SCM_CRASH) {
   1.932 +      nsTArray<nsRefPtr<Connection> > connections;
   1.933 +      getConnections(connections);
   1.934 +      for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
   1.935 +        if (!connections[i]->isClosed()) {
   1.936 +          MOZ_CRASH();
   1.937 +        }
   1.938 +      }
   1.939 +    }
   1.940 +  }
   1.941 +
   1.942 +  return NS_OK;
   1.943 +}
   1.944 +
   1.945 +} // namespace storage
   1.946 +} // namespace mozilla

mercurial