storage/src/mozStorageService.cpp

Wed, 31 Dec 2014 13:27:57 +0100

author
Michael Schloh von Bennewitz <michael@schloh.com>
date
Wed, 31 Dec 2014 13:27:57 +0100
branch
TOR_BUG_3246
changeset 6
8bccb770b82d
permissions
-rw-r--r--

Ignore runtime configuration files generated during quality assurance.

michael@0 1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
michael@0 2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
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
michael@0 5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
michael@0 6
michael@0 7 #include "mozilla/Attributes.h"
michael@0 8 #include "mozilla/DebugOnly.h"
michael@0 9
michael@0 10 #include "mozStorageService.h"
michael@0 11 #include "mozStorageConnection.h"
michael@0 12 #include "prinit.h"
michael@0 13 #include "nsAutoPtr.h"
michael@0 14 #include "nsCollationCID.h"
michael@0 15 #include "nsEmbedCID.h"
michael@0 16 #include "nsThreadUtils.h"
michael@0 17 #include "mozStoragePrivateHelpers.h"
michael@0 18 #include "nsILocale.h"
michael@0 19 #include "nsILocaleService.h"
michael@0 20 #include "nsIXPConnect.h"
michael@0 21 #include "nsIObserverService.h"
michael@0 22 #include "nsIPropertyBag2.h"
michael@0 23 #include "mozilla/Services.h"
michael@0 24 #include "mozilla/Preferences.h"
michael@0 25 #include "mozilla/LateWriteChecks.h"
michael@0 26 #include "mozIStorageCompletionCallback.h"
michael@0 27 #include "mozIStoragePendingStatement.h"
michael@0 28
michael@0 29 #include "sqlite3.h"
michael@0 30
michael@0 31 #ifdef SQLITE_OS_WIN
michael@0 32 // "windows.h" was included and it can #define lots of things we care about...
michael@0 33 #undef CompareString
michael@0 34 #endif
michael@0 35
michael@0 36 #include "nsIPromptService.h"
michael@0 37
michael@0 38 #ifdef MOZ_STORAGE_MEMORY
michael@0 39 # include "mozmemory.h"
michael@0 40 # ifdef MOZ_DMD
michael@0 41 # include "DMD.h"
michael@0 42 # endif
michael@0 43 #endif
michael@0 44
michael@0 45 ////////////////////////////////////////////////////////////////////////////////
michael@0 46 //// Defines
michael@0 47
michael@0 48 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
michael@0 49 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
michael@0 50
michael@0 51 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
michael@0 52
michael@0 53 // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
michael@0 54 // db/sqlite3/src/Makefile.in.
michael@0 55 #define PREF_TS_PAGESIZE_DEFAULT 32768
michael@0 56
michael@0 57 namespace mozilla {
michael@0 58 namespace storage {
michael@0 59
michael@0 60 ////////////////////////////////////////////////////////////////////////////////
michael@0 61 //// Memory Reporting
michael@0 62
michael@0 63 #ifdef MOZ_DMD
michael@0 64 static mozilla::Atomic<size_t> gSqliteMemoryUsed;
michael@0 65 #endif
michael@0 66
michael@0 67 static int64_t
michael@0 68 StorageSQLiteDistinguishedAmount()
michael@0 69 {
michael@0 70 return ::sqlite3_memory_used();
michael@0 71 }
michael@0 72
michael@0 73 /**
michael@0 74 * Passes a single SQLite memory statistic to a memory reporter callback.
michael@0 75 *
michael@0 76 * @param aHandleReport
michael@0 77 * The callback.
michael@0 78 * @param aData
michael@0 79 * The data for the callback.
michael@0 80 * @param aConn
michael@0 81 * The SQLite connection.
michael@0 82 * @param aPathHead
michael@0 83 * Head of the path for the memory report.
michael@0 84 * @param aKind
michael@0 85 * The memory report statistic kind, one of "stmt", "cache" or
michael@0 86 * "schema".
michael@0 87 * @param aDesc
michael@0 88 * The memory report description.
michael@0 89 * @param aOption
michael@0 90 * The SQLite constant for getting the measurement.
michael@0 91 * @param aTotal
michael@0 92 * The accumulator for the measurement.
michael@0 93 */
michael@0 94 nsresult
michael@0 95 ReportConn(nsIHandleReportCallback *aHandleReport,
michael@0 96 nsISupports *aData,
michael@0 97 Connection *aConn,
michael@0 98 const nsACString &aPathHead,
michael@0 99 const nsACString &aKind,
michael@0 100 const nsACString &aDesc,
michael@0 101 int32_t aOption,
michael@0 102 size_t *aTotal)
michael@0 103 {
michael@0 104 nsCString path(aPathHead);
michael@0 105 path.Append(aKind);
michael@0 106 path.AppendLiteral("-used");
michael@0 107
michael@0 108 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
michael@0 109 nsresult rv = aHandleReport->Callback(EmptyCString(), path,
michael@0 110 nsIMemoryReporter::KIND_HEAP,
michael@0 111 nsIMemoryReporter::UNITS_BYTES,
michael@0 112 int64_t(val), aDesc, aData);
michael@0 113 NS_ENSURE_SUCCESS(rv, rv);
michael@0 114 *aTotal += val;
michael@0 115
michael@0 116 return NS_OK;
michael@0 117 }
michael@0 118
michael@0 119 // Warning: To get a Connection's measurements requires holding its lock.
michael@0 120 // There may be a delay getting the lock if another thread is accessing the
michael@0 121 // Connection. This isn't very nice if CollectReports is called from the main
michael@0 122 // thread! But at the time of writing this function is only called when
michael@0 123 // about:memory is loaded (not, for example, when telemetry pings occur) and
michael@0 124 // any delays in that case aren't so bad.
michael@0 125 NS_IMETHODIMP
michael@0 126 Service::CollectReports(nsIHandleReportCallback *aHandleReport,
michael@0 127 nsISupports *aData)
michael@0 128 {
michael@0 129 nsresult rv;
michael@0 130 size_t totalConnSize = 0;
michael@0 131 {
michael@0 132 nsTArray<nsRefPtr<Connection> > connections;
michael@0 133 getConnections(connections);
michael@0 134
michael@0 135 for (uint32_t i = 0; i < connections.Length(); i++) {
michael@0 136 nsRefPtr<Connection> &conn = connections[i];
michael@0 137
michael@0 138 // Someone may have closed the Connection, in which case we skip it.
michael@0 139 bool isReady;
michael@0 140 (void)conn->GetConnectionReady(&isReady);
michael@0 141 if (!isReady) {
michael@0 142 continue;
michael@0 143 }
michael@0 144
michael@0 145 nsCString pathHead("explicit/storage/sqlite/");
michael@0 146 pathHead.Append(conn->getFilename());
michael@0 147 pathHead.AppendLiteral("/");
michael@0 148
michael@0 149 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
michael@0 150
michael@0 151 NS_NAMED_LITERAL_CSTRING(stmtDesc,
michael@0 152 "Memory (approximate) used by all prepared statements used by "
michael@0 153 "connections to this database.");
michael@0 154 rv = ReportConn(aHandleReport, aData, conn, pathHead,
michael@0 155 NS_LITERAL_CSTRING("stmt"), stmtDesc,
michael@0 156 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
michael@0 157 NS_ENSURE_SUCCESS(rv, rv);
michael@0 158
michael@0 159 NS_NAMED_LITERAL_CSTRING(cacheDesc,
michael@0 160 "Memory (approximate) used by all pager caches used by connections "
michael@0 161 "to this database.");
michael@0 162 rv = ReportConn(aHandleReport, aData, conn, pathHead,
michael@0 163 NS_LITERAL_CSTRING("cache"), cacheDesc,
michael@0 164 SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
michael@0 165 NS_ENSURE_SUCCESS(rv, rv);
michael@0 166
michael@0 167 NS_NAMED_LITERAL_CSTRING(schemaDesc,
michael@0 168 "Memory (approximate) used to store the schema for all databases "
michael@0 169 "associated with connections to this database.");
michael@0 170 rv = ReportConn(aHandleReport, aData, conn, pathHead,
michael@0 171 NS_LITERAL_CSTRING("schema"), schemaDesc,
michael@0 172 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
michael@0 173 NS_ENSURE_SUCCESS(rv, rv);
michael@0 174 }
michael@0 175
michael@0 176 #ifdef MOZ_DMD
michael@0 177 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
michael@0 178 NS_WARNING("memory consumption reported by SQLite doesn't match "
michael@0 179 "our measurements");
michael@0 180 }
michael@0 181 #endif
michael@0 182 }
michael@0 183
michael@0 184 int64_t other = ::sqlite3_memory_used() - totalConnSize;
michael@0 185
michael@0 186 rv = aHandleReport->Callback(
michael@0 187 EmptyCString(),
michael@0 188 NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
michael@0 189 KIND_HEAP, UNITS_BYTES, other,
michael@0 190 NS_LITERAL_CSTRING("All unclassified sqlite memory."),
michael@0 191 aData);
michael@0 192 NS_ENSURE_SUCCESS(rv, rv);
michael@0 193
michael@0 194 return NS_OK;
michael@0 195 }
michael@0 196
michael@0 197 ////////////////////////////////////////////////////////////////////////////////
michael@0 198 //// Service
michael@0 199
michael@0 200 NS_IMPL_ISUPPORTS(
michael@0 201 Service,
michael@0 202 mozIStorageService,
michael@0 203 nsIObserver,
michael@0 204 nsIMemoryReporter
michael@0 205 )
michael@0 206
michael@0 207 Service *Service::gService = nullptr;
michael@0 208
michael@0 209 Service *
michael@0 210 Service::getSingleton()
michael@0 211 {
michael@0 212 if (gService) {
michael@0 213 NS_ADDREF(gService);
michael@0 214 return gService;
michael@0 215 }
michael@0 216
michael@0 217 // Ensure that we are using the same version of SQLite that we compiled with
michael@0 218 // or newer. Our configure check ensures we are using a new enough version
michael@0 219 // at compile time.
michael@0 220 if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
michael@0 221 nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
michael@0 222 if (ps) {
michael@0 223 nsAutoString title, message;
michael@0 224 title.AppendLiteral("SQLite Version Error");
michael@0 225 message.AppendLiteral("The application has been updated, but your version "
michael@0 226 "of SQLite is too old and the application cannot "
michael@0 227 "run.");
michael@0 228 (void)ps->Alert(nullptr, title.get(), message.get());
michael@0 229 }
michael@0 230 ::PR_Abort();
michael@0 231 }
michael@0 232
michael@0 233 // The first reference to the storage service must be obtained on the
michael@0 234 // main thread.
michael@0 235 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
michael@0 236 gService = new Service();
michael@0 237 if (gService) {
michael@0 238 NS_ADDREF(gService);
michael@0 239 if (NS_FAILED(gService->initialize()))
michael@0 240 NS_RELEASE(gService);
michael@0 241 }
michael@0 242
michael@0 243 return gService;
michael@0 244 }
michael@0 245
michael@0 246 nsIXPConnect *Service::sXPConnect = nullptr;
michael@0 247
michael@0 248 // static
michael@0 249 already_AddRefed<nsIXPConnect>
michael@0 250 Service::getXPConnect()
michael@0 251 {
michael@0 252 NS_PRECONDITION(NS_IsMainThread(),
michael@0 253 "Must only get XPConnect on the main thread!");
michael@0 254 NS_PRECONDITION(gService,
michael@0 255 "Can not get XPConnect without an instance of our service!");
michael@0 256
michael@0 257 // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
michael@0 258 // not cache the service after this point.
michael@0 259 nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
michael@0 260 if (!xpc)
michael@0 261 xpc = do_GetService(nsIXPConnect::GetCID());
michael@0 262 NS_ASSERTION(xpc, "Could not get XPConnect!");
michael@0 263 return xpc.forget();
michael@0 264 }
michael@0 265
michael@0 266 int32_t Service::sSynchronousPref;
michael@0 267
michael@0 268 // static
michael@0 269 int32_t
michael@0 270 Service::getSynchronousPref()
michael@0 271 {
michael@0 272 return sSynchronousPref;
michael@0 273 }
michael@0 274
michael@0 275 int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
michael@0 276
michael@0 277 Service::Service()
michael@0 278 : mMutex("Service::mMutex")
michael@0 279 , mSqliteVFS(nullptr)
michael@0 280 , mRegistrationMutex("Service::mRegistrationMutex")
michael@0 281 , mConnections()
michael@0 282 {
michael@0 283 }
michael@0 284
michael@0 285 Service::~Service()
michael@0 286 {
michael@0 287 mozilla::UnregisterWeakMemoryReporter(this);
michael@0 288 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
michael@0 289
michael@0 290 int rc = sqlite3_vfs_unregister(mSqliteVFS);
michael@0 291 if (rc != SQLITE_OK)
michael@0 292 NS_WARNING("Failed to unregister sqlite vfs wrapper.");
michael@0 293
michael@0 294 // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
michael@0 295 // there is nothing actionable we can do in that case.
michael@0 296 rc = ::sqlite3_shutdown();
michael@0 297 if (rc != SQLITE_OK)
michael@0 298 NS_WARNING("sqlite3 did not shutdown cleanly.");
michael@0 299
michael@0 300 DebugOnly<bool> shutdownObserved = !sXPConnect;
michael@0 301 NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
michael@0 302
michael@0 303 gService = nullptr;
michael@0 304 delete mSqliteVFS;
michael@0 305 mSqliteVFS = nullptr;
michael@0 306 }
michael@0 307
michael@0 308 void
michael@0 309 Service::registerConnection(Connection *aConnection)
michael@0 310 {
michael@0 311 mRegistrationMutex.AssertNotCurrentThreadOwns();
michael@0 312 MutexAutoLock mutex(mRegistrationMutex);
michael@0 313 (void)mConnections.AppendElement(aConnection);
michael@0 314 }
michael@0 315
michael@0 316 void
michael@0 317 Service::unregisterConnection(Connection *aConnection)
michael@0 318 {
michael@0 319 // If this is the last Connection it might be the only thing keeping Service
michael@0 320 // alive. So ensure that Service is destroyed only after the Connection is
michael@0 321 // cleanly unregistered and destroyed.
michael@0 322 nsRefPtr<Service> kungFuDeathGrip(this);
michael@0 323 {
michael@0 324 mRegistrationMutex.AssertNotCurrentThreadOwns();
michael@0 325 MutexAutoLock mutex(mRegistrationMutex);
michael@0 326 DebugOnly<bool> removed = mConnections.RemoveElement(aConnection);
michael@0 327 // Assert if we try to unregister a non-existent connection.
michael@0 328 MOZ_ASSERT(removed);
michael@0 329 }
michael@0 330 }
michael@0 331
michael@0 332 void
michael@0 333 Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections)
michael@0 334 {
michael@0 335 mRegistrationMutex.AssertNotCurrentThreadOwns();
michael@0 336 MutexAutoLock mutex(mRegistrationMutex);
michael@0 337 aConnections.Clear();
michael@0 338 aConnections.AppendElements(mConnections);
michael@0 339 }
michael@0 340
michael@0 341 void
michael@0 342 Service::minimizeMemory()
michael@0 343 {
michael@0 344 nsTArray<nsRefPtr<Connection> > connections;
michael@0 345 getConnections(connections);
michael@0 346
michael@0 347 for (uint32_t i = 0; i < connections.Length(); i++) {
michael@0 348 nsRefPtr<Connection> conn = connections[i];
michael@0 349 if (conn->connectionReady()) {
michael@0 350 NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
michael@0 351 nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
michael@0 352 NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
michael@0 353 DebugOnly<nsresult> rv;
michael@0 354
michael@0 355 if (!syncConn) {
michael@0 356 nsCOMPtr<mozIStoragePendingStatement> ps;
michael@0 357 rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr,
michael@0 358 getter_AddRefs(ps));
michael@0 359 } else {
michael@0 360 rv = connections[i]->ExecuteSimpleSQL(shrinkPragma);
michael@0 361 }
michael@0 362
michael@0 363 MOZ_ASSERT(NS_SUCCEEDED(rv),
michael@0 364 "Should have been able to purge sqlite caches");
michael@0 365 }
michael@0 366 }
michael@0 367 }
michael@0 368
michael@0 369 void
michael@0 370 Service::shutdown()
michael@0 371 {
michael@0 372 NS_IF_RELEASE(sXPConnect);
michael@0 373 }
michael@0 374
michael@0 375 sqlite3_vfs *ConstructTelemetryVFS();
michael@0 376
michael@0 377 #ifdef MOZ_STORAGE_MEMORY
michael@0 378
michael@0 379 namespace {
michael@0 380
michael@0 381 // By default, SQLite tracks the size of all its heap blocks by adding an extra
michael@0 382 // 8 bytes at the start of the block to hold the size. Unfortunately, this
michael@0 383 // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
michael@0 384 // allocator, wasting memory. For example, a request for 1024 bytes has 8
michael@0 385 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
michael@0 386 // to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
michael@0 387 //
michael@0 388 // So we register jemalloc as the malloc implementation, which avoids this
michael@0 389 // 8-byte overhead, and thus a lot of waste. This requires us to provide a
michael@0 390 // function, sqliteMemRoundup(), which computes the actual size that will be
michael@0 391 // allocated for a given request. SQLite uses this function before all
michael@0 392 // allocations, and may be able to use any excess bytes caused by the rounding.
michael@0 393 //
michael@0 394 // Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size
michael@0 395 // are necessary because the sqlite_mem_methods type signatures differ slightly
michael@0 396 // from the standard ones -- they use int instead of size_t. But we don't need
michael@0 397 // a wrapper for moz_free.
michael@0 398
michael@0 399 #ifdef MOZ_DMD
michael@0 400
michael@0 401 // sqlite does its own memory accounting, and we use its numbers in our memory
michael@0 402 // reporters. But we don't want sqlite's heap blocks to show up in DMD's
michael@0 403 // output as unreported, so we mark them as reported when they're allocated and
michael@0 404 // mark them as unreported when they are freed.
michael@0 405 //
michael@0 406 // In other words, we are marking all sqlite heap blocks as reported even
michael@0 407 // though we're not reporting them ourselves. Instead we're trusting that
michael@0 408 // sqlite is fully and correctly accounting for all of its heap blocks via its
michael@0 409 // own memory accounting. Well, we don't have to trust it entirely, because
michael@0 410 // it's easy to keep track (while doing this DMD-specific marking) of exactly
michael@0 411 // how much memory SQLite is using. And we can compare that against what
michael@0 412 // SQLite reports it is using.
michael@0 413
michael@0 414 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
michael@0 415 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
michael@0 416
michael@0 417 #endif
michael@0 418
michael@0 419 static void *sqliteMemMalloc(int n)
michael@0 420 {
michael@0 421 void* p = ::moz_malloc(n);
michael@0 422 #ifdef MOZ_DMD
michael@0 423 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
michael@0 424 #endif
michael@0 425 return p;
michael@0 426 }
michael@0 427
michael@0 428 static void sqliteMemFree(void *p)
michael@0 429 {
michael@0 430 #ifdef MOZ_DMD
michael@0 431 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
michael@0 432 #endif
michael@0 433 ::moz_free(p);
michael@0 434 }
michael@0 435
michael@0 436 static void *sqliteMemRealloc(void *p, int n)
michael@0 437 {
michael@0 438 #ifdef MOZ_DMD
michael@0 439 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
michael@0 440 void *pnew = ::moz_realloc(p, n);
michael@0 441 if (pnew) {
michael@0 442 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
michael@0 443 } else {
michael@0 444 // realloc failed; undo the SqliteMallocSizeOfOnFree from above
michael@0 445 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
michael@0 446 }
michael@0 447 return pnew;
michael@0 448 #else
michael@0 449 return ::moz_realloc(p, n);
michael@0 450 #endif
michael@0 451 }
michael@0 452
michael@0 453 static int sqliteMemSize(void *p)
michael@0 454 {
michael@0 455 return ::moz_malloc_usable_size(p);
michael@0 456 }
michael@0 457
michael@0 458 static int sqliteMemRoundup(int n)
michael@0 459 {
michael@0 460 n = malloc_good_size(n);
michael@0 461
michael@0 462 // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
michael@0 463 // allocations be 8-aligned. So we round up sub-8 requests to 8. This
michael@0 464 // wastes a small amount of memory but is obviously safe.
michael@0 465 return n <= 8 ? 8 : n;
michael@0 466 }
michael@0 467
michael@0 468 static int sqliteMemInit(void *p)
michael@0 469 {
michael@0 470 return 0;
michael@0 471 }
michael@0 472
michael@0 473 static void sqliteMemShutdown(void *p)
michael@0 474 {
michael@0 475 }
michael@0 476
michael@0 477 const sqlite3_mem_methods memMethods = {
michael@0 478 &sqliteMemMalloc,
michael@0 479 &sqliteMemFree,
michael@0 480 &sqliteMemRealloc,
michael@0 481 &sqliteMemSize,
michael@0 482 &sqliteMemRoundup,
michael@0 483 &sqliteMemInit,
michael@0 484 &sqliteMemShutdown,
michael@0 485 nullptr
michael@0 486 };
michael@0 487
michael@0 488 } // anonymous namespace
michael@0 489
michael@0 490 #endif // MOZ_STORAGE_MEMORY
michael@0 491
michael@0 492 static const char* sObserverTopics[] = {
michael@0 493 "memory-pressure",
michael@0 494 "xpcom-shutdown",
michael@0 495 "xpcom-shutdown-threads"
michael@0 496 };
michael@0 497
michael@0 498 nsresult
michael@0 499 Service::initialize()
michael@0 500 {
michael@0 501 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
michael@0 502
michael@0 503 int rc;
michael@0 504
michael@0 505 #ifdef MOZ_STORAGE_MEMORY
michael@0 506 rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
michael@0 507 if (rc != SQLITE_OK)
michael@0 508 return convertResultCode(rc);
michael@0 509 #endif
michael@0 510
michael@0 511 // Explicitly initialize sqlite3. Although this is implicitly called by
michael@0 512 // various sqlite3 functions (and the sqlite3_open calls in our case),
michael@0 513 // the documentation suggests calling this directly. So we do.
michael@0 514 rc = ::sqlite3_initialize();
michael@0 515 if (rc != SQLITE_OK)
michael@0 516 return convertResultCode(rc);
michael@0 517
michael@0 518 mSqliteVFS = ConstructTelemetryVFS();
michael@0 519 if (mSqliteVFS) {
michael@0 520 rc = sqlite3_vfs_register(mSqliteVFS, 1);
michael@0 521 if (rc != SQLITE_OK)
michael@0 522 return convertResultCode(rc);
michael@0 523 } else {
michael@0 524 NS_WARNING("Failed to register telemetry VFS");
michael@0 525 }
michael@0 526
michael@0 527 // Register for xpcom-shutdown so we can cleanup after ourselves. The
michael@0 528 // observer service can only be used on the main thread.
michael@0 529 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
michael@0 530 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
michael@0 531
michael@0 532 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
michael@0 533 nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
michael@0 534 if (NS_WARN_IF(NS_FAILED(rv))) {
michael@0 535 return rv;
michael@0 536 }
michael@0 537 }
michael@0 538
michael@0 539 // We cache XPConnect for our language helpers. XPConnect can only be
michael@0 540 // used on the main thread.
michael@0 541 (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
michael@0 542
michael@0 543 // We need to obtain the toolkit.storage.synchronous preferences on the main
michael@0 544 // thread because the preference service can only be accessed there. This
michael@0 545 // is cached in the service for all future Open[Unshared]Database calls.
michael@0 546 sSynchronousPref =
michael@0 547 Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
michael@0 548
michael@0 549 // We need to obtain the toolkit.storage.pageSize preferences on the main
michael@0 550 // thread because the preference service can only be accessed there. This
michael@0 551 // is cached in the service for all future Open[Unshared]Database calls.
michael@0 552 sDefaultPageSize =
michael@0 553 Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
michael@0 554
michael@0 555 mozilla::RegisterWeakMemoryReporter(this);
michael@0 556 mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
michael@0 557
michael@0 558 return NS_OK;
michael@0 559 }
michael@0 560
michael@0 561 int
michael@0 562 Service::localeCompareStrings(const nsAString &aStr1,
michael@0 563 const nsAString &aStr2,
michael@0 564 int32_t aComparisonStrength)
michael@0 565 {
michael@0 566 // The implementation of nsICollation.CompareString() is platform-dependent.
michael@0 567 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
michael@0 568 // but it's more difficult to tell. We therefore synchronize this method.
michael@0 569 MutexAutoLock mutex(mMutex);
michael@0 570
michael@0 571 nsICollation *coll = getLocaleCollation();
michael@0 572 if (!coll) {
michael@0 573 NS_ERROR("Storage service has no collation");
michael@0 574 return 0;
michael@0 575 }
michael@0 576
michael@0 577 int32_t res;
michael@0 578 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
michael@0 579 if (NS_FAILED(rv)) {
michael@0 580 NS_ERROR("Collation compare string failed");
michael@0 581 return 0;
michael@0 582 }
michael@0 583
michael@0 584 return res;
michael@0 585 }
michael@0 586
michael@0 587 nsICollation *
michael@0 588 Service::getLocaleCollation()
michael@0 589 {
michael@0 590 mMutex.AssertCurrentThreadOwns();
michael@0 591
michael@0 592 if (mLocaleCollation)
michael@0 593 return mLocaleCollation;
michael@0 594
michael@0 595 nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
michael@0 596 if (!svc) {
michael@0 597 NS_WARNING("Could not get locale service");
michael@0 598 return nullptr;
michael@0 599 }
michael@0 600
michael@0 601 nsCOMPtr<nsILocale> appLocale;
michael@0 602 nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
michael@0 603 if (NS_FAILED(rv)) {
michael@0 604 NS_WARNING("Could not get application locale");
michael@0 605 return nullptr;
michael@0 606 }
michael@0 607
michael@0 608 nsCOMPtr<nsICollationFactory> collFact =
michael@0 609 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
michael@0 610 if (!collFact) {
michael@0 611 NS_WARNING("Could not create collation factory");
michael@0 612 return nullptr;
michael@0 613 }
michael@0 614
michael@0 615 rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
michael@0 616 if (NS_FAILED(rv)) {
michael@0 617 NS_WARNING("Could not create collation");
michael@0 618 return nullptr;
michael@0 619 }
michael@0 620
michael@0 621 return mLocaleCollation;
michael@0 622 }
michael@0 623
michael@0 624 ////////////////////////////////////////////////////////////////////////////////
michael@0 625 //// mozIStorageService
michael@0 626
michael@0 627
michael@0 628 NS_IMETHODIMP
michael@0 629 Service::OpenSpecialDatabase(const char *aStorageKey,
michael@0 630 mozIStorageConnection **_connection)
michael@0 631 {
michael@0 632 nsresult rv;
michael@0 633
michael@0 634 nsCOMPtr<nsIFile> storageFile;
michael@0 635 if (::strcmp(aStorageKey, "memory") == 0) {
michael@0 636 // just fall through with nullptr storageFile, this will cause the storage
michael@0 637 // connection to use a memory DB.
michael@0 638 }
michael@0 639 else {
michael@0 640 return NS_ERROR_INVALID_ARG;
michael@0 641 }
michael@0 642
michael@0 643 nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
michael@0 644
michael@0 645 rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
michael@0 646 NS_ENSURE_SUCCESS(rv, rv);
michael@0 647
michael@0 648 msc.forget(_connection);
michael@0 649 return NS_OK;
michael@0 650
michael@0 651 }
michael@0 652
michael@0 653 namespace {
michael@0 654
michael@0 655 class AsyncInitDatabase MOZ_FINAL : public nsRunnable
michael@0 656 {
michael@0 657 public:
michael@0 658 AsyncInitDatabase(Connection* aConnection,
michael@0 659 nsIFile* aStorageFile,
michael@0 660 int32_t aGrowthIncrement,
michael@0 661 mozIStorageCompletionCallback* aCallback)
michael@0 662 : mConnection(aConnection)
michael@0 663 , mStorageFile(aStorageFile)
michael@0 664 , mGrowthIncrement(aGrowthIncrement)
michael@0 665 , mCallback(aCallback)
michael@0 666 {
michael@0 667 MOZ_ASSERT(NS_IsMainThread());
michael@0 668 }
michael@0 669
michael@0 670 NS_IMETHOD Run()
michael@0 671 {
michael@0 672 MOZ_ASSERT(!NS_IsMainThread());
michael@0 673 nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
michael@0 674 : mConnection->initialize();
michael@0 675 if (NS_FAILED(rv)) {
michael@0 676 return DispatchResult(rv, nullptr);
michael@0 677 }
michael@0 678
michael@0 679 if (mGrowthIncrement >= 0) {
michael@0 680 // Ignore errors. In the future, we might wish to log them.
michael@0 681 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
michael@0 682 }
michael@0 683
michael@0 684 return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
michael@0 685 mConnection));
michael@0 686 }
michael@0 687
michael@0 688 private:
michael@0 689 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
michael@0 690 nsRefPtr<CallbackComplete> event =
michael@0 691 new CallbackComplete(aStatus,
michael@0 692 aValue,
michael@0 693 mCallback.forget());
michael@0 694 return NS_DispatchToMainThread(event);
michael@0 695 }
michael@0 696
michael@0 697 ~AsyncInitDatabase()
michael@0 698 {
michael@0 699 nsCOMPtr<nsIThread> thread;
michael@0 700 DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
michael@0 701 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 702 (void)NS_ProxyRelease(thread, mStorageFile);
michael@0 703
michael@0 704 // Handle ambiguous nsISupports inheritance.
michael@0 705 Connection *rawConnection = nullptr;
michael@0 706 mConnection.swap(rawConnection);
michael@0 707 (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
michael@0 708 rawConnection));
michael@0 709
michael@0 710 // Generally, the callback will be released by CallbackComplete.
michael@0 711 // However, if for some reason Run() is not executed, we still
michael@0 712 // need to ensure that it is released here.
michael@0 713 mozIStorageCompletionCallback *rawCallback = nullptr;
michael@0 714 mCallback.swap(rawCallback);
michael@0 715 (void)NS_ProxyRelease(thread, rawCallback);
michael@0 716 }
michael@0 717
michael@0 718 nsRefPtr<Connection> mConnection;
michael@0 719 nsCOMPtr<nsIFile> mStorageFile;
michael@0 720 int32_t mGrowthIncrement;
michael@0 721 nsRefPtr<mozIStorageCompletionCallback> mCallback;
michael@0 722 };
michael@0 723
michael@0 724 } // anonymous namespace
michael@0 725
michael@0 726 NS_IMETHODIMP
michael@0 727 Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
michael@0 728 nsIPropertyBag2 *aOptions,
michael@0 729 mozIStorageCompletionCallback *aCallback)
michael@0 730 {
michael@0 731 if (!NS_IsMainThread()) {
michael@0 732 return NS_ERROR_NOT_SAME_THREAD;
michael@0 733 }
michael@0 734 NS_ENSURE_ARG(aDatabaseStore);
michael@0 735 NS_ENSURE_ARG(aCallback);
michael@0 736
michael@0 737 nsCOMPtr<nsIFile> storageFile;
michael@0 738 int flags = SQLITE_OPEN_READWRITE;
michael@0 739
michael@0 740 nsCOMPtr<nsISupports> dbStore;
michael@0 741 nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
michael@0 742 if (NS_SUCCEEDED(rv)) {
michael@0 743 // Generally, aDatabaseStore holds the database nsIFile.
michael@0 744 storageFile = do_QueryInterface(dbStore, &rv);
michael@0 745 if (NS_FAILED(rv)) {
michael@0 746 return NS_ERROR_INVALID_ARG;
michael@0 747 }
michael@0 748
michael@0 749 rv = storageFile->Clone(getter_AddRefs(storageFile));
michael@0 750 MOZ_ASSERT(NS_SUCCEEDED(rv));
michael@0 751
michael@0 752 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
michael@0 753 flags |= SQLITE_OPEN_CREATE;
michael@0 754
michael@0 755 // Extract and apply the shared-cache option.
michael@0 756 bool shared = false;
michael@0 757 if (aOptions) {
michael@0 758 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
michael@0 759 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
michael@0 760 return NS_ERROR_INVALID_ARG;
michael@0 761 }
michael@0 762 }
michael@0 763 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
michael@0 764 } else {
michael@0 765 // Sometimes, however, it's a special database name.
michael@0 766 nsAutoCString keyString;
michael@0 767 rv = aDatabaseStore->GetAsACString(keyString);
michael@0 768 if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
michael@0 769 return NS_ERROR_INVALID_ARG;
michael@0 770 }
michael@0 771
michael@0 772 // Just fall through with nullptr storageFile, this will cause the storage
michael@0 773 // connection to use a memory DB.
michael@0 774 }
michael@0 775
michael@0 776 int32_t growthIncrement = -1;
michael@0 777 if (aOptions && storageFile) {
michael@0 778 rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
michael@0 779 &growthIncrement);
michael@0 780 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
michael@0 781 return NS_ERROR_INVALID_ARG;
michael@0 782 }
michael@0 783 }
michael@0 784
michael@0 785 // Create connection on this thread, but initialize it on its helper thread.
michael@0 786 nsRefPtr<Connection> msc = new Connection(this, flags, true);
michael@0 787 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
michael@0 788 MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
michael@0 789
michael@0 790 nsRefPtr<AsyncInitDatabase> asyncInit =
michael@0 791 new AsyncInitDatabase(msc,
michael@0 792 storageFile,
michael@0 793 growthIncrement,
michael@0 794 aCallback);
michael@0 795 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
michael@0 796 }
michael@0 797
michael@0 798 NS_IMETHODIMP
michael@0 799 Service::OpenDatabase(nsIFile *aDatabaseFile,
michael@0 800 mozIStorageConnection **_connection)
michael@0 801 {
michael@0 802 NS_ENSURE_ARG(aDatabaseFile);
michael@0 803
michael@0 804 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
michael@0 805 // reasons.
michael@0 806 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
michael@0 807 SQLITE_OPEN_CREATE;
michael@0 808 nsRefPtr<Connection> msc = new Connection(this, flags, false);
michael@0 809
michael@0 810 nsresult rv = msc->initialize(aDatabaseFile);
michael@0 811 NS_ENSURE_SUCCESS(rv, rv);
michael@0 812
michael@0 813 msc.forget(_connection);
michael@0 814 return NS_OK;
michael@0 815 }
michael@0 816
michael@0 817 NS_IMETHODIMP
michael@0 818 Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
michael@0 819 mozIStorageConnection **_connection)
michael@0 820 {
michael@0 821 NS_ENSURE_ARG(aDatabaseFile);
michael@0 822
michael@0 823 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
michael@0 824 // reasons.
michael@0 825 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
michael@0 826 SQLITE_OPEN_CREATE;
michael@0 827 nsRefPtr<Connection> msc = new Connection(this, flags, false);
michael@0 828
michael@0 829 nsresult rv = msc->initialize(aDatabaseFile);
michael@0 830 NS_ENSURE_SUCCESS(rv, rv);
michael@0 831
michael@0 832 msc.forget(_connection);
michael@0 833 return NS_OK;
michael@0 834 }
michael@0 835
michael@0 836 NS_IMETHODIMP
michael@0 837 Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
michael@0 838 mozIStorageConnection **_connection)
michael@0 839 {
michael@0 840 NS_ENSURE_ARG(aFileURL);
michael@0 841
michael@0 842 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
michael@0 843 // reasons.
michael@0 844 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
michael@0 845 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
michael@0 846 nsRefPtr<Connection> msc = new Connection(this, flags, false);
michael@0 847
michael@0 848 nsresult rv = msc->initialize(aFileURL);
michael@0 849 NS_ENSURE_SUCCESS(rv, rv);
michael@0 850
michael@0 851 msc.forget(_connection);
michael@0 852 return NS_OK;
michael@0 853 }
michael@0 854
michael@0 855 NS_IMETHODIMP
michael@0 856 Service::BackupDatabaseFile(nsIFile *aDBFile,
michael@0 857 const nsAString &aBackupFileName,
michael@0 858 nsIFile *aBackupParentDirectory,
michael@0 859 nsIFile **backup)
michael@0 860 {
michael@0 861 nsresult rv;
michael@0 862 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
michael@0 863 if (!parentDir) {
michael@0 864 // This argument is optional, and defaults to the same parent directory
michael@0 865 // as the current file.
michael@0 866 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
michael@0 867 NS_ENSURE_SUCCESS(rv, rv);
michael@0 868 }
michael@0 869
michael@0 870 nsCOMPtr<nsIFile> backupDB;
michael@0 871 rv = parentDir->Clone(getter_AddRefs(backupDB));
michael@0 872 NS_ENSURE_SUCCESS(rv, rv);
michael@0 873
michael@0 874 rv = backupDB->Append(aBackupFileName);
michael@0 875 NS_ENSURE_SUCCESS(rv, rv);
michael@0 876
michael@0 877 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
michael@0 878 NS_ENSURE_SUCCESS(rv, rv);
michael@0 879
michael@0 880 nsAutoString fileName;
michael@0 881 rv = backupDB->GetLeafName(fileName);
michael@0 882 NS_ENSURE_SUCCESS(rv, rv);
michael@0 883
michael@0 884 rv = backupDB->Remove(false);
michael@0 885 NS_ENSURE_SUCCESS(rv, rv);
michael@0 886
michael@0 887 backupDB.forget(backup);
michael@0 888
michael@0 889 return aDBFile->CopyTo(parentDir, fileName);
michael@0 890 }
michael@0 891
michael@0 892 ////////////////////////////////////////////////////////////////////////////////
michael@0 893 //// nsIObserver
michael@0 894
michael@0 895 NS_IMETHODIMP
michael@0 896 Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
michael@0 897 {
michael@0 898 if (strcmp(aTopic, "memory-pressure") == 0) {
michael@0 899 minimizeMemory();
michael@0 900 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
michael@0 901 shutdown();
michael@0 902 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
michael@0 903 nsCOMPtr<nsIObserverService> os =
michael@0 904 mozilla::services::GetObserverService();
michael@0 905
michael@0 906 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
michael@0 907 (void)os->RemoveObserver(this, sObserverTopics[i]);
michael@0 908 }
michael@0 909
michael@0 910 bool anyOpen = false;
michael@0 911 do {
michael@0 912 nsTArray<nsRefPtr<Connection> > connections;
michael@0 913 getConnections(connections);
michael@0 914 anyOpen = false;
michael@0 915 for (uint32_t i = 0; i < connections.Length(); i++) {
michael@0 916 nsRefPtr<Connection> &conn = connections[i];
michael@0 917 if (conn->isClosing()) {
michael@0 918 anyOpen = true;
michael@0 919 break;
michael@0 920 }
michael@0 921 }
michael@0 922 if (anyOpen) {
michael@0 923 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
michael@0 924 NS_ProcessNextEvent(thread);
michael@0 925 }
michael@0 926 } while (anyOpen);
michael@0 927
michael@0 928 if (gShutdownChecks == SCM_CRASH) {
michael@0 929 nsTArray<nsRefPtr<Connection> > connections;
michael@0 930 getConnections(connections);
michael@0 931 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
michael@0 932 if (!connections[i]->isClosed()) {
michael@0 933 MOZ_CRASH();
michael@0 934 }
michael@0 935 }
michael@0 936 }
michael@0 937 }
michael@0 938
michael@0 939 return NS_OK;
michael@0 940 }
michael@0 941
michael@0 942 } // namespace storage
michael@0 943 } // namespace mozilla

mercurial