summaryrefslogtreecommitdiffstats
path: root/toolkit/xre/AutoSQLiteLifetime.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'toolkit/xre/AutoSQLiteLifetime.cpp')
-rw-r--r--toolkit/xre/AutoSQLiteLifetime.cpp156
1 files changed, 156 insertions, 0 deletions
diff --git a/toolkit/xre/AutoSQLiteLifetime.cpp b/toolkit/xre/AutoSQLiteLifetime.cpp
new file mode 100644
index 0000000000..4500ec1bfe
--- /dev/null
+++ b/toolkit/xre/AutoSQLiteLifetime.cpp
@@ -0,0 +1,156 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsDebug.h"
+#include "AutoSQLiteLifetime.h"
+#include "sqlite3.h"
+
+#ifdef MOZ_MEMORY
+# include "mozmemory.h"
+# ifdef MOZ_DMD
+# include "nsIMemoryReporter.h"
+# include "DMD.h"
+
+namespace mozilla {
+namespace storage {
+extern mozilla::Atomic<size_t> gSqliteMemoryUsed;
+}
+} // namespace mozilla
+
+using mozilla::storage::gSqliteMemoryUsed;
+
+# endif
+
+namespace {
+
+// By default, SQLite tracks the size of all its heap blocks by adding an extra
+// 8 bytes at the start of the block to hold the size. Unfortunately, this
+// causes a lot of 2^N-sized allocations to be rounded up by jemalloc
+// allocator, wasting memory. For example, a request for 1024 bytes has 8
+// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up
+// to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.)
+//
+// So we register jemalloc as the malloc implementation, which avoids this
+// 8-byte overhead, and thus a lot of waste. This requires us to provide a
+// function, sqliteMemRoundup(), which computes the actual size that will be
+// allocated for a given request. SQLite uses this function before all
+// allocations, and may be able to use any excess bytes caused by the rounding.
+//
+// Note: the wrappers for malloc, realloc and moz_malloc_usable_size are
+// necessary because the sqlite_mem_methods type signatures differ slightly
+// from the standard ones -- they use int instead of size_t. But we don't need
+// a wrapper for free.
+
+# ifdef MOZ_DMD
+
+// sqlite does its own memory accounting, and we use its numbers in our memory
+// reporters. But we don't want sqlite's heap blocks to show up in DMD's
+// output as unreported, so we mark them as reported when they're allocated and
+// mark them as unreported when they are freed.
+//
+// In other words, we are marking all sqlite heap blocks as reported even
+// though we're not reporting them ourselves. Instead we're trusting that
+// sqlite is fully and correctly accounting for all of its heap blocks via its
+// own memory accounting. Well, we don't have to trust it entirely, because
+// it's easy to keep track (while doing this DMD-specific marking) of exactly
+// how much memory SQLite is using. And we can compare that against what
+// SQLite reports it is using.
+
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc)
+MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree)
+
+# endif
+
+static void* sqliteMemMalloc(int n) {
+ void* p = ::malloc(n);
+# ifdef MOZ_DMD
+ gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
+# endif
+ return p;
+}
+
+static void sqliteMemFree(void* p) {
+# ifdef MOZ_DMD
+ gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
+# endif
+ ::free(p);
+}
+
+static void* sqliteMemRealloc(void* p, int n) {
+# ifdef MOZ_DMD
+ gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p);
+ void* pnew = ::realloc(p, n);
+ if (pnew) {
+ gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew);
+ } else {
+ // realloc failed; undo the SqliteMallocSizeOfOnFree from above
+ gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p);
+ }
+ return pnew;
+# else
+ return ::realloc(p, n);
+# endif
+}
+
+static int sqliteMemSize(void* p) { return ::moz_malloc_usable_size(p); }
+
+static int sqliteMemRoundup(int n) {
+ n = malloc_good_size(n);
+
+ // jemalloc can return blocks of size 2 and 4, but SQLite requires that all
+ // allocations be 8-aligned. So we round up sub-8 requests to 8. This
+ // wastes a small amount of memory but is obviously safe.
+ return n <= 8 ? 8 : n;
+}
+
+static int sqliteMemInit(void* p) { return 0; }
+
+static void sqliteMemShutdown(void* p) {}
+
+const sqlite3_mem_methods memMethods = {
+ &sqliteMemMalloc, &sqliteMemFree, &sqliteMemRealloc, &sqliteMemSize,
+ &sqliteMemRoundup, &sqliteMemInit, &sqliteMemShutdown, nullptr};
+
+} // namespace
+
+#endif // MOZ_MEMORY
+
+namespace mozilla {
+
+AutoSQLiteLifetime::AutoSQLiteLifetime() {
+ if (++AutoSQLiteLifetime::sSingletonEnforcer != 1) {
+ MOZ_CRASH("multiple instances of AutoSQLiteLifetime constructed!");
+ }
+
+#ifdef MOZ_MEMORY
+ sResult = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods);
+#else
+ sResult = SQLITE_OK;
+#endif
+
+ if (sResult == SQLITE_OK) {
+ // TODO (bug 1191405): do not preallocate the connections caches until we
+ // have figured the impact on our consumers and memory.
+ sqlite3_config(SQLITE_CONFIG_PAGECACHE, NULL, 0, 0);
+
+ // Explicitly initialize sqlite3. Although this is implicitly called by
+ // various sqlite3 functions (and the sqlite3_open calls in our case),
+ // the documentation suggests calling this directly. So we do.
+ sResult = ::sqlite3_initialize();
+ }
+}
+
+AutoSQLiteLifetime::~AutoSQLiteLifetime() {
+ // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but
+ // there is nothing actionable we can do in that case.
+ sResult = ::sqlite3_shutdown();
+ NS_WARNING_ASSERTION(sResult == SQLITE_OK,
+ "sqlite3 did not shutdown cleanly.");
+}
+
+int AutoSQLiteLifetime::sSingletonEnforcer = 0;
+int AutoSQLiteLifetime::sResult = SQLITE_MISUSE;
+
+} // namespace mozilla