LCOV - code coverage report
Current view: top level - storage - mozStorageService.cpp (source / functions) Hit Total Coverage
Test: output.info Lines: 92 351 26.2 %
Date: 2017-07-14 16:53:18 Functions: 18 40 45.0 %
Legend: Lines: hit not hit

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

Generated by: LCOV version 1.13