Backed out changeset 2284c3e8c336 (bug 1215092)
[gecko.git] / storage / mozStorageService.cpp
blob5370cf43072121f1b0d6ec2b56b1e36f7839aaa7
1 /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
2 * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ :
3 * This Source Code Form is subject to the terms of the Mozilla Public
4 * License, v. 2.0. If a copy of the MPL was not distributed with this
5 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
7 #include "mozilla/Attributes.h"
8 #include "mozilla/DebugOnly.h"
10 #include "mozStorageService.h"
11 #include "mozStorageConnection.h"
12 #include "prinit.h"
13 #include "nsAutoPtr.h"
14 #include "nsCollationCID.h"
15 #include "nsEmbedCID.h"
16 #include "nsThreadUtils.h"
17 #include "mozStoragePrivateHelpers.h"
18 #include "nsILocale.h"
19 #include "nsILocaleService.h"
20 #include "nsIXPConnect.h"
21 #include "nsIObserverService.h"
22 #include "nsIPropertyBag2.h"
23 #include "mozilla/Services.h"
24 #include "mozilla/Preferences.h"
25 #include "mozilla/LateWriteChecks.h"
26 #include "mozIStorageCompletionCallback.h"
27 #include "mozIStoragePendingStatement.h"
29 #include "sqlite3.h"
31 #ifdef SQLITE_OS_WIN
32 // "windows.h" was included and it can #define lots of things we care about...
33 #undef CompareString
34 #endif
36 #include "nsIPromptService.h"
38 #ifdef MOZ_STORAGE_MEMORY
39 # include "mozmemory.h"
40 # ifdef MOZ_DMD
41 # include "DMD.h"
42 # endif
43 #endif
45 ////////////////////////////////////////////////////////////////////////////////
46 //// Defines
48 #define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous"
49 #define PREF_TS_SYNCHRONOUS_DEFAULT 1
51 #define PREF_TS_PAGESIZE "toolkit.storage.pageSize"
53 // This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in
54 // db/sqlite3/src/Makefile.in.
55 #define PREF_TS_PAGESIZE_DEFAULT 32768
57 namespace mozilla {
58 namespace storage {
60 ////////////////////////////////////////////////////////////////////////////////
61 //// Memory Reporting
63 #ifdef MOZ_DMD
64 static mozilla::Atomic<size_t> gSqliteMemoryUsed;
65 #endif
67 static int64_t
68 StorageSQLiteDistinguishedAmount()
70 return ::sqlite3_memory_used();
73 /**
74 * Passes a single SQLite memory statistic to a memory reporter callback.
76 * @param aHandleReport
77 * The callback.
78 * @param aData
79 * The data for the callback.
80 * @param aConn
81 * The SQLite connection.
82 * @param aPathHead
83 * Head of the path for the memory report.
84 * @param aKind
85 * The memory report statistic kind, one of "stmt", "cache" or
86 * "schema".
87 * @param aDesc
88 * The memory report description.
89 * @param aOption
90 * The SQLite constant for getting the measurement.
91 * @param aTotal
92 * The accumulator for the measurement.
94 nsresult
95 ReportConn(nsIHandleReportCallback *aHandleReport,
96 nsISupports *aData,
97 Connection *aConn,
98 const nsACString &aPathHead,
99 const nsACString &aKind,
100 const nsACString &aDesc,
101 int32_t aOption,
102 size_t *aTotal)
104 nsCString path(aPathHead);
105 path.Append(aKind);
106 path.AppendLiteral("-used");
108 int32_t val = aConn->getSqliteRuntimeStatus(aOption);
109 nsresult rv = aHandleReport->Callback(EmptyCString(), path,
110 nsIMemoryReporter::KIND_HEAP,
111 nsIMemoryReporter::UNITS_BYTES,
112 int64_t(val), aDesc, aData);
113 NS_ENSURE_SUCCESS(rv, rv);
114 *aTotal += val;
116 return NS_OK;
119 // Warning: To get a Connection's measurements requires holding its lock.
120 // There may be a delay getting the lock if another thread is accessing the
121 // Connection. This isn't very nice if CollectReports is called from the main
122 // thread! But at the time of writing this function is only called when
123 // about:memory is loaded (not, for example, when telemetry pings occur) and
124 // any delays in that case aren't so bad.
125 NS_IMETHODIMP
126 Service::CollectReports(nsIHandleReportCallback *aHandleReport,
127 nsISupports *aData, bool aAnonymize)
129 nsresult rv;
130 size_t totalConnSize = 0;
132 nsTArray<RefPtr<Connection> > connections;
133 getConnections(connections);
135 for (uint32_t i = 0; i < connections.Length(); i++) {
136 RefPtr<Connection> &conn = connections[i];
138 // Someone may have closed the Connection, in which case we skip it.
139 bool isReady;
140 (void)conn->GetConnectionReady(&isReady);
141 if (!isReady) {
142 continue;
145 nsCString pathHead("explicit/storage/sqlite/");
146 // This filename isn't privacy-sensitive, and so is never anonymized.
147 pathHead.Append(conn->getFilename());
148 pathHead.Append('/');
150 SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex);
152 NS_NAMED_LITERAL_CSTRING(stmtDesc,
153 "Memory (approximate) used by all prepared statements used by "
154 "connections to this database.");
155 rv = ReportConn(aHandleReport, aData, conn, pathHead,
156 NS_LITERAL_CSTRING("stmt"), stmtDesc,
157 SQLITE_DBSTATUS_STMT_USED, &totalConnSize);
158 NS_ENSURE_SUCCESS(rv, rv);
160 NS_NAMED_LITERAL_CSTRING(cacheDesc,
161 "Memory (approximate) used by all pager caches used by connections "
162 "to this database.");
163 rv = ReportConn(aHandleReport, aData, conn, pathHead,
164 NS_LITERAL_CSTRING("cache"), cacheDesc,
165 SQLITE_DBSTATUS_CACHE_USED, &totalConnSize);
166 NS_ENSURE_SUCCESS(rv, rv);
168 NS_NAMED_LITERAL_CSTRING(schemaDesc,
169 "Memory (approximate) used to store the schema for all databases "
170 "associated with connections to this database.");
171 rv = ReportConn(aHandleReport, aData, conn, pathHead,
172 NS_LITERAL_CSTRING("schema"), schemaDesc,
173 SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize);
174 NS_ENSURE_SUCCESS(rv, rv);
177 #ifdef MOZ_DMD
178 if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) {
179 NS_WARNING("memory consumption reported by SQLite doesn't match "
180 "our measurements");
182 #endif
185 int64_t other = ::sqlite3_memory_used() - totalConnSize;
187 rv = aHandleReport->Callback(
188 EmptyCString(),
189 NS_LITERAL_CSTRING("explicit/storage/sqlite/other"),
190 KIND_HEAP, UNITS_BYTES, other,
191 NS_LITERAL_CSTRING("All unclassified sqlite memory."),
192 aData);
193 NS_ENSURE_SUCCESS(rv, rv);
195 return NS_OK;
198 ////////////////////////////////////////////////////////////////////////////////
199 //// Service
201 NS_IMPL_ISUPPORTS(
202 Service,
203 mozIStorageService,
204 nsIObserver,
205 nsIMemoryReporter
208 Service *Service::gService = nullptr;
210 Service *
211 Service::getSingleton()
213 if (gService) {
214 NS_ADDREF(gService);
215 return gService;
218 // Ensure that we are using the same version of SQLite that we compiled with
219 // or newer. Our configure check ensures we are using a new enough version
220 // at compile time.
221 if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) {
222 nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID));
223 if (ps) {
224 nsAutoString title, message;
225 title.AppendLiteral("SQLite Version Error");
226 message.AppendLiteral("The application has been updated, but your version "
227 "of SQLite is too old and the application cannot "
228 "run.");
229 (void)ps->Alert(nullptr, title.get(), message.get());
231 ::PR_Abort();
234 // The first reference to the storage service must be obtained on the
235 // main thread.
236 NS_ENSURE_TRUE(NS_IsMainThread(), nullptr);
237 gService = new Service();
238 if (gService) {
239 NS_ADDREF(gService);
240 if (NS_FAILED(gService->initialize()))
241 NS_RELEASE(gService);
244 return gService;
247 nsIXPConnect *Service::sXPConnect = nullptr;
249 // static
250 already_AddRefed<nsIXPConnect>
251 Service::getXPConnect()
253 NS_PRECONDITION(NS_IsMainThread(),
254 "Must only get XPConnect on the main thread!");
255 NS_PRECONDITION(gService,
256 "Can not get XPConnect without an instance of our service!");
258 // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do
259 // not cache the service after this point.
260 nsCOMPtr<nsIXPConnect> xpc(sXPConnect);
261 if (!xpc)
262 xpc = do_GetService(nsIXPConnect::GetCID());
263 NS_ASSERTION(xpc, "Could not get XPConnect!");
264 return xpc.forget();
267 int32_t Service::sSynchronousPref;
269 // static
270 int32_t
271 Service::getSynchronousPref()
273 return sSynchronousPref;
276 int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT;
278 Service::Service()
279 : mMutex("Service::mMutex")
280 , mSqliteVFS(nullptr)
281 , mRegistrationMutex("Service::mRegistrationMutex")
282 , mConnections()
286 Service::~Service()
288 mozilla::UnregisterWeakMemoryReporter(this);
289 mozilla::UnregisterStorageSQLiteDistinguishedAmount();
291 int rc = sqlite3_vfs_unregister(mSqliteVFS);
292 if (rc != SQLITE_OK)
293 NS_WARNING("Failed to unregister sqlite vfs wrapper.");
295 // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
296 // there is nothing actionable we can do in that case.
297 rc = ::sqlite3_shutdown();
298 if (rc != SQLITE_OK)
299 NS_WARNING("sqlite3 did not shutdown cleanly.");
301 DebugOnly<bool> shutdownObserved = !sXPConnect;
302 NS_ASSERTION(shutdownObserved, "Shutdown was not observed!");
304 gService = nullptr;
305 delete mSqliteVFS;
306 mSqliteVFS = nullptr;
309 void
310 Service::registerConnection(Connection *aConnection)
312 mRegistrationMutex.AssertNotCurrentThreadOwns();
313 MutexAutoLock mutex(mRegistrationMutex);
314 (void)mConnections.AppendElement(aConnection);
317 void
318 Service::unregisterConnection(Connection *aConnection)
320 // If this is the last Connection it might be the only thing keeping Service
321 // alive. So ensure that Service is destroyed only after the Connection is
322 // cleanly unregistered and destroyed.
323 RefPtr<Service> kungFuDeathGrip(this);
325 mRegistrationMutex.AssertNotCurrentThreadOwns();
326 MutexAutoLock mutex(mRegistrationMutex);
328 for (uint32_t i = 0 ; i < mConnections.Length(); ++i) {
329 if (mConnections[i] == aConnection) {
330 nsCOMPtr<nsIThread> thread = mConnections[i]->threadOpenedOn;
332 // Ensure the connection is released on its opening thread. Note, we
333 // must use .forget().take() so that we can manually cast to an
334 // unambiguous nsISupports type.
335 NS_ProxyRelease(thread,
336 static_cast<mozIStorageConnection*>(mConnections[i].forget().take()));
338 mConnections.RemoveElementAt(i);
339 return;
343 MOZ_ASSERT_UNREACHABLE("Attempt to unregister unknown storage connection!");
347 void
348 Service::getConnections(/* inout */ nsTArray<RefPtr<Connection> >& aConnections)
350 mRegistrationMutex.AssertNotCurrentThreadOwns();
351 MutexAutoLock mutex(mRegistrationMutex);
352 aConnections.Clear();
353 aConnections.AppendElements(mConnections);
356 void
357 Service::minimizeMemory()
359 nsTArray<RefPtr<Connection> > connections;
360 getConnections(connections);
362 for (uint32_t i = 0; i < connections.Length(); i++) {
363 RefPtr<Connection> conn = connections[i];
364 if (!conn->connectionReady())
365 continue;
367 NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory");
368 nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface(
369 NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn));
370 bool onOpenedThread = false;
372 if (!syncConn) {
373 // This is a mozIStorageAsyncConnection, it can only be used on the main
374 // thread, so we can do a straight API call.
375 nsCOMPtr<mozIStoragePendingStatement> ps;
376 DebugOnly<nsresult> rv =
377 conn->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, getter_AddRefs(ps));
378 MOZ_ASSERT(NS_SUCCEEDED(rv), "Should have purged sqlite caches");
379 } else if (NS_SUCCEEDED(conn->threadOpenedOn->IsOnCurrentThread(&onOpenedThread)) &&
380 onOpenedThread) {
381 // We are on the opener thread, so we can just proceed.
382 conn->ExecuteSimpleSQL(shrinkPragma);
383 } else {
384 // We are on the wrong thread, the query should be executed on the
385 // opener thread, so we must dispatch to it.
386 nsCOMPtr<nsIRunnable> event =
387 NS_NewRunnableMethodWithArg<const nsCString>(
388 conn, &Connection::ExecuteSimpleSQL, shrinkPragma);
389 conn->threadOpenedOn->Dispatch(event, NS_DISPATCH_NORMAL);
394 void
395 Service::shutdown()
397 NS_IF_RELEASE(sXPConnect);
400 sqlite3_vfs *ConstructTelemetryVFS();
402 #ifdef MOZ_STORAGE_MEMORY
404 namespace {
406 // By default, SQLite tracks the size of all its heap blocks by adding an extra
407 // 8 bytes at the start of the block to hold the size. Unfortunately, this
408 // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
409 // allocator, wasting memory. For example, a request for 1024 bytes has 8
410 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
411 // to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
413 // So we register jemalloc as the malloc implementation, which avoids this
414 // 8-byte overhead, and thus a lot of waste. This requires us to provide a
415 // function, sqliteMemRoundup(), which computes the actual size that will be
416 // allocated for a given request. SQLite uses this function before all
417 // allocations, and may be able to use any excess bytes caused by the rounding.
419 // Note: the wrappers for malloc, realloc and moz_malloc_usable_size are
420 // necessary because the sqlite_mem_methods type signatures differ slightly
421 // from the standard ones -- they use int instead of size_t. But we don't need
422 // a wrapper for free.
424 #ifdef MOZ_DMD
426 // sqlite does its own memory accounting, and we use its numbers in our memory
427 // reporters. But we don't want sqlite's heap blocks to show up in DMD's
428 // output as unreported, so we mark them as reported when they're allocated and
429 // mark them as unreported when they are freed.
431 // In other words, we are marking all sqlite heap blocks as reported even
432 // though we're not reporting them ourselves. Instead we're trusting that
433 // sqlite is fully and correctly accounting for all of its heap blocks via its
434 // own memory accounting. Well, we don't have to trust it entirely, because
435 // it's easy to keep track (while doing this DMD-specific marking) of exactly
436 // how much memory SQLite is using. And we can compare that against what
437 // SQLite reports it is using.
439 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
440 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
442 #endif
444 static void *sqliteMemMalloc(int n)
446 void* p = ::malloc(n);
447 #ifdef MOZ_DMD
448 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
449 #endif
450 return p;
453 static void sqliteMemFree(void *p)
455 #ifdef MOZ_DMD
456 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
457 #endif
458 ::free(p);
461 static void *sqliteMemRealloc(void *p, int n)
463 #ifdef MOZ_DMD
464 gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
465 void *pnew = ::realloc(p, n);
466 if (pnew) {
467 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
468 } else {
469 // realloc failed; undo the SqliteMallocSizeOfOnFree from above
470 gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
472 return pnew;
473 #else
474 return ::realloc(p, n);
475 #endif
478 static int sqliteMemSize(void *p)
480 return ::moz_malloc_usable_size(p);
483 static int sqliteMemRoundup(int n)
485 n = malloc_good_size(n);
487 // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
488 // allocations be 8-aligned. So we round up sub-8 requests to 8. This
489 // wastes a small amount of memory but is obviously safe.
490 return n <= 8 ? 8 : n;
493 static int sqliteMemInit(void *p)
495 return 0;
498 static void sqliteMemShutdown(void *p)
502 const sqlite3_mem_methods memMethods = {
503 &sqliteMemMalloc,
504 &sqliteMemFree,
505 &sqliteMemRealloc,
506 &sqliteMemSize,
507 &sqliteMemRoundup,
508 &sqliteMemInit,
509 &sqliteMemShutdown,
510 nullptr
513 } // namespace
515 #endif // MOZ_STORAGE_MEMORY
517 static const char* sObserverTopics[] = {
518 "memory-pressure",
519 "xpcom-shutdown",
520 "xpcom-shutdown-threads"
523 nsresult
524 Service::initialize()
526 MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread");
528 int rc;
530 #ifdef MOZ_STORAGE_MEMORY
531 rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
532 if (rc != SQLITE_OK)
533 return convertResultCode(rc);
534 #endif
536 // TODO (bug 1191405): do not preallocate the connections caches until we
537 // have figured the impact on our consumers and memory.
538 sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0);
540 // Explicitly initialize sqlite3. Although this is implicitly called by
541 // various sqlite3 functions (and the sqlite3_open calls in our case),
542 // the documentation suggests calling this directly. So we do.
543 rc = ::sqlite3_initialize();
544 if (rc != SQLITE_OK)
545 return convertResultCode(rc);
547 mSqliteVFS = ConstructTelemetryVFS();
548 if (mSqliteVFS) {
549 rc = sqlite3_vfs_register(mSqliteVFS, 1);
550 if (rc != SQLITE_OK)
551 return convertResultCode(rc);
552 } else {
553 NS_WARNING("Failed to register telemetry VFS");
556 // Register for xpcom-shutdown so we can cleanup after ourselves. The
557 // observer service can only be used on the main thread.
558 nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService();
559 NS_ENSURE_TRUE(os, NS_ERROR_FAILURE);
561 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
562 nsresult rv = os->AddObserver(this, sObserverTopics[i], false);
563 if (NS_WARN_IF(NS_FAILED(rv))) {
564 return rv;
568 // We cache XPConnect for our language helpers. XPConnect can only be
569 // used on the main thread.
570 (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect);
572 // We need to obtain the toolkit.storage.synchronous preferences on the main
573 // thread because the preference service can only be accessed there. This
574 // is cached in the service for all future Open[Unshared]Database calls.
575 sSynchronousPref =
576 Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT);
578 // We need to obtain the toolkit.storage.pageSize preferences on the main
579 // thread because the preference service can only be accessed there. This
580 // is cached in the service for all future Open[Unshared]Database calls.
581 sDefaultPageSize =
582 Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT);
584 mozilla::RegisterWeakMemoryReporter(this);
585 mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount);
587 return NS_OK;
591 Service::localeCompareStrings(const nsAString &aStr1,
592 const nsAString &aStr2,
593 int32_t aComparisonStrength)
595 // The implementation of nsICollation.CompareString() is platform-dependent.
596 // On Linux it's not thread-safe. It may not be on Windows and OS X either,
597 // but it's more difficult to tell. We therefore synchronize this method.
598 MutexAutoLock mutex(mMutex);
600 nsICollation *coll = getLocaleCollation();
601 if (!coll) {
602 NS_ERROR("Storage service has no collation");
603 return 0;
606 int32_t res;
607 nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res);
608 if (NS_FAILED(rv)) {
609 NS_ERROR("Collation compare string failed");
610 return 0;
613 return res;
616 nsICollation *
617 Service::getLocaleCollation()
619 mMutex.AssertCurrentThreadOwns();
621 if (mLocaleCollation)
622 return mLocaleCollation;
624 nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID));
625 if (!svc) {
626 NS_WARNING("Could not get locale service");
627 return nullptr;
630 nsCOMPtr<nsILocale> appLocale;
631 nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale));
632 if (NS_FAILED(rv)) {
633 NS_WARNING("Could not get application locale");
634 return nullptr;
637 nsCOMPtr<nsICollationFactory> collFact =
638 do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID);
639 if (!collFact) {
640 NS_WARNING("Could not create collation factory");
641 return nullptr;
644 rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation));
645 if (NS_FAILED(rv)) {
646 NS_WARNING("Could not create collation");
647 return nullptr;
650 return mLocaleCollation;
653 ////////////////////////////////////////////////////////////////////////////////
654 //// mozIStorageService
657 NS_IMETHODIMP
658 Service::OpenSpecialDatabase(const char *aStorageKey,
659 mozIStorageConnection **_connection)
661 nsresult rv;
663 nsCOMPtr<nsIFile> storageFile;
664 if (::strcmp(aStorageKey, "memory") == 0) {
665 // just fall through with nullptr storageFile, this will cause the storage
666 // connection to use a memory DB.
668 else {
669 return NS_ERROR_INVALID_ARG;
672 RefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false);
674 rv = storageFile ? msc->initialize(storageFile) : msc->initialize();
675 NS_ENSURE_SUCCESS(rv, rv);
677 msc.forget(_connection);
678 return NS_OK;
682 namespace {
684 class AsyncInitDatabase final : public nsRunnable
686 public:
687 AsyncInitDatabase(Connection* aConnection,
688 nsIFile* aStorageFile,
689 int32_t aGrowthIncrement,
690 mozIStorageCompletionCallback* aCallback)
691 : mConnection(aConnection)
692 , mStorageFile(aStorageFile)
693 , mGrowthIncrement(aGrowthIncrement)
694 , mCallback(aCallback)
696 MOZ_ASSERT(NS_IsMainThread());
699 NS_IMETHOD Run()
701 MOZ_ASSERT(!NS_IsMainThread());
702 nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile)
703 : mConnection->initialize();
704 if (NS_FAILED(rv)) {
705 nsCOMPtr<nsIRunnable> closeRunnable =
706 NS_NewRunnableMethodWithArg<mozIStorageCompletionCallback*>(
707 mConnection.get(),
708 &Connection::AsyncClose,
709 nullptr);
710 MOZ_ASSERT(closeRunnable);
711 MOZ_ALWAYS_TRUE(NS_SUCCEEDED(NS_DispatchToMainThread(closeRunnable)));
713 return DispatchResult(rv, nullptr);
716 if (mGrowthIncrement >= 0) {
717 // Ignore errors. In the future, we might wish to log them.
718 (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString());
721 return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*,
722 mConnection));
725 private:
726 nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) {
727 RefPtr<CallbackComplete> event =
728 new CallbackComplete(aStatus,
729 aValue,
730 mCallback.forget());
731 return NS_DispatchToMainThread(event);
734 ~AsyncInitDatabase()
736 nsCOMPtr<nsIThread> thread;
737 DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread));
738 MOZ_ASSERT(NS_SUCCEEDED(rv));
739 (void)NS_ProxyRelease(thread, mStorageFile);
741 // Handle ambiguous nsISupports inheritance.
742 Connection *rawConnection = nullptr;
743 mConnection.swap(rawConnection);
744 (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *,
745 rawConnection));
747 // Generally, the callback will be released by CallbackComplete.
748 // However, if for some reason Run() is not executed, we still
749 // need to ensure that it is released here.
750 mozIStorageCompletionCallback *rawCallback = nullptr;
751 mCallback.swap(rawCallback);
752 (void)NS_ProxyRelease(thread, rawCallback);
755 RefPtr<Connection> mConnection;
756 nsCOMPtr<nsIFile> mStorageFile;
757 int32_t mGrowthIncrement;
758 RefPtr<mozIStorageCompletionCallback> mCallback;
761 } // namespace
763 NS_IMETHODIMP
764 Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore,
765 nsIPropertyBag2 *aOptions,
766 mozIStorageCompletionCallback *aCallback)
768 if (!NS_IsMainThread()) {
769 return NS_ERROR_NOT_SAME_THREAD;
771 NS_ENSURE_ARG(aDatabaseStore);
772 NS_ENSURE_ARG(aCallback);
774 nsCOMPtr<nsIFile> storageFile;
775 int flags = SQLITE_OPEN_READWRITE;
777 nsCOMPtr<nsISupports> dbStore;
778 nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore));
779 if (NS_SUCCEEDED(rv)) {
780 // Generally, aDatabaseStore holds the database nsIFile.
781 storageFile = do_QueryInterface(dbStore, &rv);
782 if (NS_FAILED(rv)) {
783 return NS_ERROR_INVALID_ARG;
786 rv = storageFile->Clone(getter_AddRefs(storageFile));
787 MOZ_ASSERT(NS_SUCCEEDED(rv));
789 // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons.
790 flags |= SQLITE_OPEN_CREATE;
792 // Extract and apply the shared-cache option.
793 bool shared = false;
794 if (aOptions) {
795 rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared);
796 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
797 return NS_ERROR_INVALID_ARG;
800 flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE;
801 } else {
802 // Sometimes, however, it's a special database name.
803 nsAutoCString keyString;
804 rv = aDatabaseStore->GetAsACString(keyString);
805 if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) {
806 return NS_ERROR_INVALID_ARG;
809 // Just fall through with nullptr storageFile, this will cause the storage
810 // connection to use a memory DB.
813 int32_t growthIncrement = -1;
814 if (aOptions && storageFile) {
815 rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"),
816 &growthIncrement);
817 if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) {
818 return NS_ERROR_INVALID_ARG;
822 // Create connection on this thread, but initialize it on its helper thread.
823 RefPtr<Connection> msc = new Connection(this, flags, true);
824 nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget();
825 MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already");
827 RefPtr<AsyncInitDatabase> asyncInit =
828 new AsyncInitDatabase(msc,
829 storageFile,
830 growthIncrement,
831 aCallback);
832 return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL);
835 NS_IMETHODIMP
836 Service::OpenDatabase(nsIFile *aDatabaseFile,
837 mozIStorageConnection **_connection)
839 NS_ENSURE_ARG(aDatabaseFile);
841 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
842 // reasons.
843 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
844 SQLITE_OPEN_CREATE;
845 RefPtr<Connection> msc = new Connection(this, flags, false);
847 nsresult rv = msc->initialize(aDatabaseFile);
848 NS_ENSURE_SUCCESS(rv, rv);
850 msc.forget(_connection);
851 return NS_OK;
854 NS_IMETHODIMP
855 Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile,
856 mozIStorageConnection **_connection)
858 NS_ENSURE_ARG(aDatabaseFile);
860 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
861 // reasons.
862 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE |
863 SQLITE_OPEN_CREATE;
864 RefPtr<Connection> msc = new Connection(this, flags, false);
866 nsresult rv = msc->initialize(aDatabaseFile);
867 NS_ENSURE_SUCCESS(rv, rv);
869 msc.forget(_connection);
870 return NS_OK;
873 NS_IMETHODIMP
874 Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL,
875 mozIStorageConnection **_connection)
877 NS_ENSURE_ARG(aFileURL);
879 // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility
880 // reasons.
881 int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE |
882 SQLITE_OPEN_CREATE | SQLITE_OPEN_URI;
883 RefPtr<Connection> msc = new Connection(this, flags, false);
885 nsresult rv = msc->initialize(aFileURL);
886 NS_ENSURE_SUCCESS(rv, rv);
888 msc.forget(_connection);
889 return NS_OK;
892 NS_IMETHODIMP
893 Service::BackupDatabaseFile(nsIFile *aDBFile,
894 const nsAString &aBackupFileName,
895 nsIFile *aBackupParentDirectory,
896 nsIFile **backup)
898 nsresult rv;
899 nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory;
900 if (!parentDir) {
901 // This argument is optional, and defaults to the same parent directory
902 // as the current file.
903 rv = aDBFile->GetParent(getter_AddRefs(parentDir));
904 NS_ENSURE_SUCCESS(rv, rv);
907 nsCOMPtr<nsIFile> backupDB;
908 rv = parentDir->Clone(getter_AddRefs(backupDB));
909 NS_ENSURE_SUCCESS(rv, rv);
911 rv = backupDB->Append(aBackupFileName);
912 NS_ENSURE_SUCCESS(rv, rv);
914 rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600);
915 NS_ENSURE_SUCCESS(rv, rv);
917 nsAutoString fileName;
918 rv = backupDB->GetLeafName(fileName);
919 NS_ENSURE_SUCCESS(rv, rv);
921 rv = backupDB->Remove(false);
922 NS_ENSURE_SUCCESS(rv, rv);
924 backupDB.forget(backup);
926 return aDBFile->CopyTo(parentDir, fileName);
929 ////////////////////////////////////////////////////////////////////////////////
930 //// nsIObserver
932 NS_IMETHODIMP
933 Service::Observe(nsISupports *, const char *aTopic, const char16_t *)
935 if (strcmp(aTopic, "memory-pressure") == 0) {
936 minimizeMemory();
937 } else if (strcmp(aTopic, "xpcom-shutdown") == 0) {
938 shutdown();
939 } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) {
940 nsCOMPtr<nsIObserverService> os =
941 mozilla::services::GetObserverService();
943 for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) {
944 (void)os->RemoveObserver(this, sObserverTopics[i]);
947 bool anyOpen = false;
948 do {
949 nsTArray<RefPtr<Connection> > connections;
950 getConnections(connections);
951 anyOpen = false;
952 for (uint32_t i = 0; i < connections.Length(); i++) {
953 RefPtr<Connection> &conn = connections[i];
954 if (conn->isClosing()) {
955 anyOpen = true;
956 break;
959 if (anyOpen) {
960 nsCOMPtr<nsIThread> thread = do_GetCurrentThread();
961 NS_ProcessNextEvent(thread);
963 } while (anyOpen);
965 if (gShutdownChecks == SCM_CRASH) {
966 nsTArray<RefPtr<Connection> > connections;
967 getConnections(connections);
968 for (uint32_t i = 0, n = connections.Length(); i < n; i++) {
969 if (!connections[i]->isClosed()) {
970 MOZ_CRASH();
976 return NS_OK;
979 } // namespace storage
980 } // namespace mozilla