1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* This Source Code Form is subject to the terms of the Mozilla Public
3  * License, v. 2.0. If a copy of the MPL was not distributed with this
4  * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
5 
6 #include "nsDebug.h"
7 #include "AutoSQLiteLifetime.h"
8 #include "sqlite3.h"
9 
10 #ifdef MOZ_MEMORY
11 #  include "mozmemory.h"
12 #  ifdef MOZ_DMD
13 #    include "nsIMemoryReporter.h"
14 #    include "DMD.h"
15 
16 namespace mozilla {
17 namespace storage {
18 extern mozilla::Atomic<size_t> gSqliteMemoryUsed;
19 }
20 }  // namespace mozilla
21 
22 using mozilla::storage::gSqliteMemoryUsed;
23 
24 #  endif
25 
26 namespace {
27 
28 // By default, SQLite tracks the size of all its heap blocks by adding an extra
29 // 8 bytes at the start of the block to hold the size.  Unfortunately, this
30 // causes a lot of 2^N-sized allocations to be rounded up by jemalloc
31 // allocator, wasting memory.  For example, a request for 1024 bytes has 8
32 // bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
33 // to 2048 bytes, wasting 1012 bytes.  (See bug 676189 for more details.)
34 //
35 // So we register jemalloc as the malloc implementation, which avoids this
36 // 8-byte overhead, and thus a lot of waste.  This requires us to provide a
37 // function, sqliteMemRoundup(), which computes the actual size that will be
38 // allocated for a given request.  SQLite uses this function before all
39 // allocations, and may be able to use any excess bytes caused by the rounding.
40 //
41 // Note: the wrappers for malloc, realloc and moz_malloc_usable_size are
42 // necessary because the sqlite_mem_methods type signatures differ slightly
43 // from the standard ones -- they use int instead of size_t.  But we don't need
44 // a wrapper for free.
45 
46 #  ifdef MOZ_DMD
47 
48 // sqlite does its own memory accounting, and we use its numbers in our memory
49 // reporters.  But we don't want sqlite's heap blocks to show up in DMD's
50 // output as unreported, so we mark them as reported when they're allocated and
51 // mark them as unreported when they are freed.
52 //
53 // In other words, we are marking all sqlite heap blocks as reported even
54 // though we're not reporting them ourselves.  Instead we're trusting that
55 // sqlite is fully and correctly accounting for all of its heap blocks via its
56 // own memory accounting.  Well, we don't have to trust it entirely, because
57 // it's easy to keep track (while doing this DMD-specific marking) of exactly
58 // how much memory SQLite is using.  And we can compare that against what
59 // SQLite reports it is using.
60 
61 MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)62 MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
63 
64 #  endif
65 
66 static void* sqliteMemMalloc(int n) {
67   void* p = ::malloc(n);
68 #  ifdef MOZ_DMD
69   gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
70 #  endif
71   return p;
72 }
73 
sqliteMemFree(void * p)74 static void sqliteMemFree(void* p) {
75 #  ifdef MOZ_DMD
76   gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
77 #  endif
78   ::free(p);
79 }
80 
sqliteMemRealloc(void * p,int n)81 static void* sqliteMemRealloc(void* p, int n) {
82 #  ifdef MOZ_DMD
83   gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
84   void* pnew = ::realloc(p, n);
85   if (pnew) {
86     gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
87   } else {
88     // realloc failed;  undo the SqliteMallocSizeOfOnFree from above
89     gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
90   }
91   return pnew;
92 #  else
93   return ::realloc(p, n);
94 #  endif
95 }
96 
sqliteMemSize(void * p)97 static int sqliteMemSize(void* p) { return ::moz_malloc_usable_size(p); }
98 
sqliteMemRoundup(int n)99 static int sqliteMemRoundup(int n) {
100   n = malloc_good_size(n);
101 
102   // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
103   // allocations be 8-aligned.  So we round up sub-8 requests to 8.  This
104   // wastes a small amount of memory but is obviously safe.
105   return n <= 8 ? 8 : n;
106 }
107 
sqliteMemInit(void * p)108 static int sqliteMemInit(void* p) { return 0; }
109 
sqliteMemShutdown(void * p)110 static void sqliteMemShutdown(void* p) {}
111 
112 const sqlite3_mem_methods memMethods = {
113     &sqliteMemMalloc,  &sqliteMemFree, &sqliteMemRealloc,  &sqliteMemSize,
114     &sqliteMemRoundup, &sqliteMemInit, &sqliteMemShutdown, nullptr};
115 
116 }  // namespace
117 
118 #endif  // MOZ_MEMORY
119 
120 namespace mozilla {
121 
AutoSQLiteLifetime()122 AutoSQLiteLifetime::AutoSQLiteLifetime() {
123   if (++AutoSQLiteLifetime::sSingletonEnforcer != 1) {
124     MOZ_CRASH("multiple instances of AutoSQLiteLifetime constructed!");
125   }
126 
127 #ifdef MOZ_MEMORY
128   sResult = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
129 #else
130   sResult = SQLITE_OK;
131 #endif
132 
133   if (sResult == SQLITE_OK) {
134     // TODO (bug 1191405): do not preallocate the connections caches until we
135     // have figured the impact on our consumers and memory.
136     sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0);
137 
138     // Explicitly initialize sqlite3.  Although this is implicitly called by
139     // various sqlite3 functions (and the sqlite3_open calls in our case),
140     // the documentation suggests calling this directly.  So we do.
141     sResult = ::sqlite3_initialize();
142   }
143 }
144 
~AutoSQLiteLifetime()145 AutoSQLiteLifetime::~AutoSQLiteLifetime() {
146   // Shutdown the sqlite3 API.  Warn if shutdown did not turn out okay, but
147   // there is nothing actionable we can do in that case.
148   sResult = ::sqlite3_shutdown();
149   NS_WARNING_ASSERTION(sResult == SQLITE_OK,
150                        "sqlite3 did not shutdown cleanly.");
151 }
152 
153 int AutoSQLiteLifetime::sSingletonEnforcer = 0;
154 int AutoSQLiteLifetime::sResult = SQLITE_MISUSE;
155 
156 }  // namespace mozilla
157