gecko-dev/toolkit/xre/AutoSQLiteLifetime.cpp
Marco Bonardo 1b86f6b5be Bug 483318 - Implement binding arrays to mozIStorageStatementParams. r=asuth,lina,Standard8
Use `carray`` extension to bind array of numerics and strings, so we can
avoid large and slow IN clauses, and cache prepared statements having
a variable number of parameters.
The extension is statically loaded and available in every connection.
Consumers are encouraged to use the explicit `bindArrayXXX` methods, as
the generic `bindByIndex` and `bindByName` methods are too lenient,
especially from Javascript.
Note `carray`` only supports UTF8 encoded strings, the API will convert
the encoding when UTF16 is passed in.
These new variants are not exposed to Rust yet, as the existing comment
suggests they were intended for primitive types. It could be done in the
future, if necessary.

Differential Revision: https://phabricator.services.mozilla.com/D225334
2024-10-28 19:28:24 +00:00

168 lines
5.5 KiB
C++

/* -*- 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"
#include "sqlite3_static_ext.h"
#include "mozilla/DebugOnly.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!");
}
Init();
}
// static
void AutoSQLiteLifetime::Init() {
#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);
// Load the carray extension.
DebugOnly<int> srv =
::sqlite3_auto_extension((void (*)(void))sqlite3_carray_init);
MOZ_ASSERT(srv == SQLITE_OK, "Should succeed loading carray extension");
// 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