1.1 --- /dev/null Thu Jan 01 00:00:00 1970 +0000 1.2 +++ b/storage/src/mozStorageService.cpp Wed Dec 31 06:09:35 2014 +0100 1.3 @@ -0,0 +1,943 @@ 1.4 +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- 1.5 + * vim: sw=2 ts=2 et lcs=trail\:.,tab\:>~ : 1.6 + * This Source Code Form is subject to the terms of the Mozilla Public 1.7 + * License, v. 2.0. If a copy of the MPL was not distributed with this 1.8 + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ 1.9 + 1.10 +#include "mozilla/Attributes.h" 1.11 +#include "mozilla/DebugOnly.h" 1.12 + 1.13 +#include "mozStorageService.h" 1.14 +#include "mozStorageConnection.h" 1.15 +#include "prinit.h" 1.16 +#include "nsAutoPtr.h" 1.17 +#include "nsCollationCID.h" 1.18 +#include "nsEmbedCID.h" 1.19 +#include "nsThreadUtils.h" 1.20 +#include "mozStoragePrivateHelpers.h" 1.21 +#include "nsILocale.h" 1.22 +#include "nsILocaleService.h" 1.23 +#include "nsIXPConnect.h" 1.24 +#include "nsIObserverService.h" 1.25 +#include "nsIPropertyBag2.h" 1.26 +#include "mozilla/Services.h" 1.27 +#include "mozilla/Preferences.h" 1.28 +#include "mozilla/LateWriteChecks.h" 1.29 +#include "mozIStorageCompletionCallback.h" 1.30 +#include "mozIStoragePendingStatement.h" 1.31 + 1.32 +#include "sqlite3.h" 1.33 + 1.34 +#ifdef SQLITE_OS_WIN 1.35 +// "windows.h" was included and it can #define lots of things we care about... 1.36 +#undef CompareString 1.37 +#endif 1.38 + 1.39 +#include "nsIPromptService.h" 1.40 + 1.41 +#ifdef MOZ_STORAGE_MEMORY 1.42 +# include "mozmemory.h" 1.43 +# ifdef MOZ_DMD 1.44 +# include "DMD.h" 1.45 +# endif 1.46 +#endif 1.47 + 1.48 +//////////////////////////////////////////////////////////////////////////////// 1.49 +//// Defines 1.50 + 1.51 +#define PREF_TS_SYNCHRONOUS "toolkit.storage.synchronous" 1.52 +#define PREF_TS_SYNCHRONOUS_DEFAULT 1 1.53 + 1.54 +#define PREF_TS_PAGESIZE "toolkit.storage.pageSize" 1.55 + 1.56 +// This value must be kept in sync with the value of SQLITE_DEFAULT_PAGE_SIZE in 1.57 +// db/sqlite3/src/Makefile.in. 1.58 +#define PREF_TS_PAGESIZE_DEFAULT 32768 1.59 + 1.60 +namespace mozilla { 1.61 +namespace storage { 1.62 + 1.63 +//////////////////////////////////////////////////////////////////////////////// 1.64 +//// Memory Reporting 1.65 + 1.66 +#ifdef MOZ_DMD 1.67 +static mozilla::Atomic<size_t> gSqliteMemoryUsed; 1.68 +#endif 1.69 + 1.70 +static int64_t 1.71 +StorageSQLiteDistinguishedAmount() 1.72 +{ 1.73 + return ::sqlite3_memory_used(); 1.74 +} 1.75 + 1.76 +/** 1.77 + * Passes a single SQLite memory statistic to a memory reporter callback. 1.78 + * 1.79 + * @param aHandleReport 1.80 + * The callback. 1.81 + * @param aData 1.82 + * The data for the callback. 1.83 + * @param aConn 1.84 + * The SQLite connection. 1.85 + * @param aPathHead 1.86 + * Head of the path for the memory report. 1.87 + * @param aKind 1.88 + * The memory report statistic kind, one of "stmt", "cache" or 1.89 + * "schema". 1.90 + * @param aDesc 1.91 + * The memory report description. 1.92 + * @param aOption 1.93 + * The SQLite constant for getting the measurement. 1.94 + * @param aTotal 1.95 + * The accumulator for the measurement. 1.96 + */ 1.97 +nsresult 1.98 +ReportConn(nsIHandleReportCallback *aHandleReport, 1.99 + nsISupports *aData, 1.100 + Connection *aConn, 1.101 + const nsACString &aPathHead, 1.102 + const nsACString &aKind, 1.103 + const nsACString &aDesc, 1.104 + int32_t aOption, 1.105 + size_t *aTotal) 1.106 +{ 1.107 + nsCString path(aPathHead); 1.108 + path.Append(aKind); 1.109 + path.AppendLiteral("-used"); 1.110 + 1.111 + int32_t val = aConn->getSqliteRuntimeStatus(aOption); 1.112 + nsresult rv = aHandleReport->Callback(EmptyCString(), path, 1.113 + nsIMemoryReporter::KIND_HEAP, 1.114 + nsIMemoryReporter::UNITS_BYTES, 1.115 + int64_t(val), aDesc, aData); 1.116 + NS_ENSURE_SUCCESS(rv, rv); 1.117 + *aTotal += val; 1.118 + 1.119 + return NS_OK; 1.120 +} 1.121 + 1.122 +// Warning: To get a Connection's measurements requires holding its lock. 1.123 +// There may be a delay getting the lock if another thread is accessing the 1.124 +// Connection. This isn't very nice if CollectReports is called from the main 1.125 +// thread! But at the time of writing this function is only called when 1.126 +// about:memory is loaded (not, for example, when telemetry pings occur) and 1.127 +// any delays in that case aren't so bad. 1.128 +NS_IMETHODIMP 1.129 +Service::CollectReports(nsIHandleReportCallback *aHandleReport, 1.130 + nsISupports *aData) 1.131 +{ 1.132 + nsresult rv; 1.133 + size_t totalConnSize = 0; 1.134 + { 1.135 + nsTArray<nsRefPtr<Connection> > connections; 1.136 + getConnections(connections); 1.137 + 1.138 + for (uint32_t i = 0; i < connections.Length(); i++) { 1.139 + nsRefPtr<Connection> &conn = connections[i]; 1.140 + 1.141 + // Someone may have closed the Connection, in which case we skip it. 1.142 + bool isReady; 1.143 + (void)conn->GetConnectionReady(&isReady); 1.144 + if (!isReady) { 1.145 + continue; 1.146 + } 1.147 + 1.148 + nsCString pathHead("explicit/storage/sqlite/"); 1.149 + pathHead.Append(conn->getFilename()); 1.150 + pathHead.AppendLiteral("/"); 1.151 + 1.152 + SQLiteMutexAutoLock lockedScope(conn->sharedDBMutex); 1.153 + 1.154 + NS_NAMED_LITERAL_CSTRING(stmtDesc, 1.155 + "Memory (approximate) used by all prepared statements used by " 1.156 + "connections to this database."); 1.157 + rv = ReportConn(aHandleReport, aData, conn, pathHead, 1.158 + NS_LITERAL_CSTRING("stmt"), stmtDesc, 1.159 + SQLITE_DBSTATUS_STMT_USED, &totalConnSize); 1.160 + NS_ENSURE_SUCCESS(rv, rv); 1.161 + 1.162 + NS_NAMED_LITERAL_CSTRING(cacheDesc, 1.163 + "Memory (approximate) used by all pager caches used by connections " 1.164 + "to this database."); 1.165 + rv = ReportConn(aHandleReport, aData, conn, pathHead, 1.166 + NS_LITERAL_CSTRING("cache"), cacheDesc, 1.167 + SQLITE_DBSTATUS_CACHE_USED, &totalConnSize); 1.168 + NS_ENSURE_SUCCESS(rv, rv); 1.169 + 1.170 + NS_NAMED_LITERAL_CSTRING(schemaDesc, 1.171 + "Memory (approximate) used to store the schema for all databases " 1.172 + "associated with connections to this database."); 1.173 + rv = ReportConn(aHandleReport, aData, conn, pathHead, 1.174 + NS_LITERAL_CSTRING("schema"), schemaDesc, 1.175 + SQLITE_DBSTATUS_SCHEMA_USED, &totalConnSize); 1.176 + NS_ENSURE_SUCCESS(rv, rv); 1.177 + } 1.178 + 1.179 +#ifdef MOZ_DMD 1.180 + if (::sqlite3_memory_used() != int64_t(gSqliteMemoryUsed)) { 1.181 + NS_WARNING("memory consumption reported by SQLite doesn't match " 1.182 + "our measurements"); 1.183 + } 1.184 +#endif 1.185 + } 1.186 + 1.187 + int64_t other = ::sqlite3_memory_used() - totalConnSize; 1.188 + 1.189 + rv = aHandleReport->Callback( 1.190 + EmptyCString(), 1.191 + NS_LITERAL_CSTRING("explicit/storage/sqlite/other"), 1.192 + KIND_HEAP, UNITS_BYTES, other, 1.193 + NS_LITERAL_CSTRING("All unclassified sqlite memory."), 1.194 + aData); 1.195 + NS_ENSURE_SUCCESS(rv, rv); 1.196 + 1.197 + return NS_OK; 1.198 +} 1.199 + 1.200 +//////////////////////////////////////////////////////////////////////////////// 1.201 +//// Service 1.202 + 1.203 +NS_IMPL_ISUPPORTS( 1.204 + Service, 1.205 + mozIStorageService, 1.206 + nsIObserver, 1.207 + nsIMemoryReporter 1.208 +) 1.209 + 1.210 +Service *Service::gService = nullptr; 1.211 + 1.212 +Service * 1.213 +Service::getSingleton() 1.214 +{ 1.215 + if (gService) { 1.216 + NS_ADDREF(gService); 1.217 + return gService; 1.218 + } 1.219 + 1.220 + // Ensure that we are using the same version of SQLite that we compiled with 1.221 + // or newer. Our configure check ensures we are using a new enough version 1.222 + // at compile time. 1.223 + if (SQLITE_VERSION_NUMBER > ::sqlite3_libversion_number()) { 1.224 + nsCOMPtr<nsIPromptService> ps(do_GetService(NS_PROMPTSERVICE_CONTRACTID)); 1.225 + if (ps) { 1.226 + nsAutoString title, message; 1.227 + title.AppendLiteral("SQLite Version Error"); 1.228 + message.AppendLiteral("The application has been updated, but your version " 1.229 + "of SQLite is too old and the application cannot " 1.230 + "run."); 1.231 + (void)ps->Alert(nullptr, title.get(), message.get()); 1.232 + } 1.233 + ::PR_Abort(); 1.234 + } 1.235 + 1.236 + // The first reference to the storage service must be obtained on the 1.237 + // main thread. 1.238 + NS_ENSURE_TRUE(NS_IsMainThread(), nullptr); 1.239 + gService = new Service(); 1.240 + if (gService) { 1.241 + NS_ADDREF(gService); 1.242 + if (NS_FAILED(gService->initialize())) 1.243 + NS_RELEASE(gService); 1.244 + } 1.245 + 1.246 + return gService; 1.247 +} 1.248 + 1.249 +nsIXPConnect *Service::sXPConnect = nullptr; 1.250 + 1.251 +// static 1.252 +already_AddRefed<nsIXPConnect> 1.253 +Service::getXPConnect() 1.254 +{ 1.255 + NS_PRECONDITION(NS_IsMainThread(), 1.256 + "Must only get XPConnect on the main thread!"); 1.257 + NS_PRECONDITION(gService, 1.258 + "Can not get XPConnect without an instance of our service!"); 1.259 + 1.260 + // If we've been shutdown, sXPConnect will be null. To prevent leaks, we do 1.261 + // not cache the service after this point. 1.262 + nsCOMPtr<nsIXPConnect> xpc(sXPConnect); 1.263 + if (!xpc) 1.264 + xpc = do_GetService(nsIXPConnect::GetCID()); 1.265 + NS_ASSERTION(xpc, "Could not get XPConnect!"); 1.266 + return xpc.forget(); 1.267 +} 1.268 + 1.269 +int32_t Service::sSynchronousPref; 1.270 + 1.271 +// static 1.272 +int32_t 1.273 +Service::getSynchronousPref() 1.274 +{ 1.275 + return sSynchronousPref; 1.276 +} 1.277 + 1.278 +int32_t Service::sDefaultPageSize = PREF_TS_PAGESIZE_DEFAULT; 1.279 + 1.280 +Service::Service() 1.281 +: mMutex("Service::mMutex") 1.282 +, mSqliteVFS(nullptr) 1.283 +, mRegistrationMutex("Service::mRegistrationMutex") 1.284 +, mConnections() 1.285 +{ 1.286 +} 1.287 + 1.288 +Service::~Service() 1.289 +{ 1.290 + mozilla::UnregisterWeakMemoryReporter(this); 1.291 + mozilla::UnregisterStorageSQLiteDistinguishedAmount(); 1.292 + 1.293 + int rc = sqlite3_vfs_unregister(mSqliteVFS); 1.294 + if (rc != SQLITE_OK) 1.295 + NS_WARNING("Failed to unregister sqlite vfs wrapper."); 1.296 + 1.297 + // Shutdown the sqlite3 API. Warn if shutdown did not turn out okay, but 1.298 + // there is nothing actionable we can do in that case. 1.299 + rc = ::sqlite3_shutdown(); 1.300 + if (rc != SQLITE_OK) 1.301 + NS_WARNING("sqlite3 did not shutdown cleanly."); 1.302 + 1.303 + DebugOnly<bool> shutdownObserved = !sXPConnect; 1.304 + NS_ASSERTION(shutdownObserved, "Shutdown was not observed!"); 1.305 + 1.306 + gService = nullptr; 1.307 + delete mSqliteVFS; 1.308 + mSqliteVFS = nullptr; 1.309 +} 1.310 + 1.311 +void 1.312 +Service::registerConnection(Connection *aConnection) 1.313 +{ 1.314 + mRegistrationMutex.AssertNotCurrentThreadOwns(); 1.315 + MutexAutoLock mutex(mRegistrationMutex); 1.316 + (void)mConnections.AppendElement(aConnection); 1.317 +} 1.318 + 1.319 +void 1.320 +Service::unregisterConnection(Connection *aConnection) 1.321 +{ 1.322 + // If this is the last Connection it might be the only thing keeping Service 1.323 + // alive. So ensure that Service is destroyed only after the Connection is 1.324 + // cleanly unregistered and destroyed. 1.325 + nsRefPtr<Service> kungFuDeathGrip(this); 1.326 + { 1.327 + mRegistrationMutex.AssertNotCurrentThreadOwns(); 1.328 + MutexAutoLock mutex(mRegistrationMutex); 1.329 + DebugOnly<bool> removed = mConnections.RemoveElement(aConnection); 1.330 + // Assert if we try to unregister a non-existent connection. 1.331 + MOZ_ASSERT(removed); 1.332 + } 1.333 +} 1.334 + 1.335 +void 1.336 +Service::getConnections(/* inout */ nsTArray<nsRefPtr<Connection> >& aConnections) 1.337 +{ 1.338 + mRegistrationMutex.AssertNotCurrentThreadOwns(); 1.339 + MutexAutoLock mutex(mRegistrationMutex); 1.340 + aConnections.Clear(); 1.341 + aConnections.AppendElements(mConnections); 1.342 +} 1.343 + 1.344 +void 1.345 +Service::minimizeMemory() 1.346 +{ 1.347 + nsTArray<nsRefPtr<Connection> > connections; 1.348 + getConnections(connections); 1.349 + 1.350 + for (uint32_t i = 0; i < connections.Length(); i++) { 1.351 + nsRefPtr<Connection> conn = connections[i]; 1.352 + if (conn->connectionReady()) { 1.353 + NS_NAMED_LITERAL_CSTRING(shrinkPragma, "PRAGMA shrink_memory"); 1.354 + nsCOMPtr<mozIStorageConnection> syncConn = do_QueryInterface( 1.355 + NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, conn)); 1.356 + DebugOnly<nsresult> rv; 1.357 + 1.358 + if (!syncConn) { 1.359 + nsCOMPtr<mozIStoragePendingStatement> ps; 1.360 + rv = connections[i]->ExecuteSimpleSQLAsync(shrinkPragma, nullptr, 1.361 + getter_AddRefs(ps)); 1.362 + } else { 1.363 + rv = connections[i]->ExecuteSimpleSQL(shrinkPragma); 1.364 + } 1.365 + 1.366 + MOZ_ASSERT(NS_SUCCEEDED(rv), 1.367 + "Should have been able to purge sqlite caches"); 1.368 + } 1.369 + } 1.370 +} 1.371 + 1.372 +void 1.373 +Service::shutdown() 1.374 +{ 1.375 + NS_IF_RELEASE(sXPConnect); 1.376 +} 1.377 + 1.378 +sqlite3_vfs *ConstructTelemetryVFS(); 1.379 + 1.380 +#ifdef MOZ_STORAGE_MEMORY 1.381 + 1.382 +namespace { 1.383 + 1.384 +// By default, SQLite tracks the size of all its heap blocks by adding an extra 1.385 +// 8 bytes at the start of the block to hold the size. Unfortunately, this 1.386 +// causes a lot of 2^N-sized allocations to be rounded up by jemalloc 1.387 +// allocator, wasting memory. For example, a request for 1024 bytes has 8 1.388 +// bytes added, becoming a request for 1032 bytes, and jemalloc rounds this up 1.389 +// to 2048 bytes, wasting 1012 bytes. (See bug 676189 for more details.) 1.390 +// 1.391 +// So we register jemalloc as the malloc implementation, which avoids this 1.392 +// 8-byte overhead, and thus a lot of waste. This requires us to provide a 1.393 +// function, sqliteMemRoundup(), which computes the actual size that will be 1.394 +// allocated for a given request. SQLite uses this function before all 1.395 +// allocations, and may be able to use any excess bytes caused by the rounding. 1.396 +// 1.397 +// Note: the wrappers for moz_malloc, moz_realloc and moz_malloc_usable_size 1.398 +// are necessary because the sqlite_mem_methods type signatures differ slightly 1.399 +// from the standard ones -- they use int instead of size_t. But we don't need 1.400 +// a wrapper for moz_free. 1.401 + 1.402 +#ifdef MOZ_DMD 1.403 + 1.404 +// sqlite does its own memory accounting, and we use its numbers in our memory 1.405 +// reporters. But we don't want sqlite's heap blocks to show up in DMD's 1.406 +// output as unreported, so we mark them as reported when they're allocated and 1.407 +// mark them as unreported when they are freed. 1.408 +// 1.409 +// In other words, we are marking all sqlite heap blocks as reported even 1.410 +// though we're not reporting them ourselves. Instead we're trusting that 1.411 +// sqlite is fully and correctly accounting for all of its heap blocks via its 1.412 +// own memory accounting. Well, we don't have to trust it entirely, because 1.413 +// it's easy to keep track (while doing this DMD-specific marking) of exactly 1.414 +// how much memory SQLite is using. And we can compare that against what 1.415 +// SQLite reports it is using. 1.416 + 1.417 +MOZ_DEFINE_MALLOC_SIZE_OF_ON_ALLOC(SqliteMallocSizeOfOnAlloc) 1.418 +MOZ_DEFINE_MALLOC_SIZE_OF_ON_FREE(SqliteMallocSizeOfOnFree) 1.419 + 1.420 +#endif 1.421 + 1.422 +static void *sqliteMemMalloc(int n) 1.423 +{ 1.424 + void* p = ::moz_malloc(n); 1.425 +#ifdef MOZ_DMD 1.426 + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); 1.427 +#endif 1.428 + return p; 1.429 +} 1.430 + 1.431 +static void sqliteMemFree(void *p) 1.432 +{ 1.433 +#ifdef MOZ_DMD 1.434 + gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); 1.435 +#endif 1.436 + ::moz_free(p); 1.437 +} 1.438 + 1.439 +static void *sqliteMemRealloc(void *p, int n) 1.440 +{ 1.441 +#ifdef MOZ_DMD 1.442 + gSqliteMemoryUsed -= SqliteMallocSizeOfOnFree(p); 1.443 + void *pnew = ::moz_realloc(p, n); 1.444 + if (pnew) { 1.445 + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(pnew); 1.446 + } else { 1.447 + // realloc failed; undo the SqliteMallocSizeOfOnFree from above 1.448 + gSqliteMemoryUsed += SqliteMallocSizeOfOnAlloc(p); 1.449 + } 1.450 + return pnew; 1.451 +#else 1.452 + return ::moz_realloc(p, n); 1.453 +#endif 1.454 +} 1.455 + 1.456 +static int sqliteMemSize(void *p) 1.457 +{ 1.458 + return ::moz_malloc_usable_size(p); 1.459 +} 1.460 + 1.461 +static int sqliteMemRoundup(int n) 1.462 +{ 1.463 + n = malloc_good_size(n); 1.464 + 1.465 + // jemalloc can return blocks of size 2 and 4, but SQLite requires that all 1.466 + // allocations be 8-aligned. So we round up sub-8 requests to 8. This 1.467 + // wastes a small amount of memory but is obviously safe. 1.468 + return n <= 8 ? 8 : n; 1.469 +} 1.470 + 1.471 +static int sqliteMemInit(void *p) 1.472 +{ 1.473 + return 0; 1.474 +} 1.475 + 1.476 +static void sqliteMemShutdown(void *p) 1.477 +{ 1.478 +} 1.479 + 1.480 +const sqlite3_mem_methods memMethods = { 1.481 + &sqliteMemMalloc, 1.482 + &sqliteMemFree, 1.483 + &sqliteMemRealloc, 1.484 + &sqliteMemSize, 1.485 + &sqliteMemRoundup, 1.486 + &sqliteMemInit, 1.487 + &sqliteMemShutdown, 1.488 + nullptr 1.489 +}; 1.490 + 1.491 +} // anonymous namespace 1.492 + 1.493 +#endif // MOZ_STORAGE_MEMORY 1.494 + 1.495 +static const char* sObserverTopics[] = { 1.496 + "memory-pressure", 1.497 + "xpcom-shutdown", 1.498 + "xpcom-shutdown-threads" 1.499 +}; 1.500 + 1.501 +nsresult 1.502 +Service::initialize() 1.503 +{ 1.504 + MOZ_ASSERT(NS_IsMainThread(), "Must be initialized on the main thread"); 1.505 + 1.506 + int rc; 1.507 + 1.508 +#ifdef MOZ_STORAGE_MEMORY 1.509 + rc = ::sqlite3_config(SQLITE_CONFIG_MALLOC, &memMethods); 1.510 + if (rc != SQLITE_OK) 1.511 + return convertResultCode(rc); 1.512 +#endif 1.513 + 1.514 + // Explicitly initialize sqlite3. Although this is implicitly called by 1.515 + // various sqlite3 functions (and the sqlite3_open calls in our case), 1.516 + // the documentation suggests calling this directly. So we do. 1.517 + rc = ::sqlite3_initialize(); 1.518 + if (rc != SQLITE_OK) 1.519 + return convertResultCode(rc); 1.520 + 1.521 + mSqliteVFS = ConstructTelemetryVFS(); 1.522 + if (mSqliteVFS) { 1.523 + rc = sqlite3_vfs_register(mSqliteVFS, 1); 1.524 + if (rc != SQLITE_OK) 1.525 + return convertResultCode(rc); 1.526 + } else { 1.527 + NS_WARNING("Failed to register telemetry VFS"); 1.528 + } 1.529 + 1.530 + // Register for xpcom-shutdown so we can cleanup after ourselves. The 1.531 + // observer service can only be used on the main thread. 1.532 + nsCOMPtr<nsIObserverService> os = mozilla::services::GetObserverService(); 1.533 + NS_ENSURE_TRUE(os, NS_ERROR_FAILURE); 1.534 + 1.535 + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { 1.536 + nsresult rv = os->AddObserver(this, sObserverTopics[i], false); 1.537 + if (NS_WARN_IF(NS_FAILED(rv))) { 1.538 + return rv; 1.539 + } 1.540 + } 1.541 + 1.542 + // We cache XPConnect for our language helpers. XPConnect can only be 1.543 + // used on the main thread. 1.544 + (void)CallGetService(nsIXPConnect::GetCID(), &sXPConnect); 1.545 + 1.546 + // We need to obtain the toolkit.storage.synchronous preferences on the main 1.547 + // thread because the preference service can only be accessed there. This 1.548 + // is cached in the service for all future Open[Unshared]Database calls. 1.549 + sSynchronousPref = 1.550 + Preferences::GetInt(PREF_TS_SYNCHRONOUS, PREF_TS_SYNCHRONOUS_DEFAULT); 1.551 + 1.552 + // We need to obtain the toolkit.storage.pageSize preferences on the main 1.553 + // thread because the preference service can only be accessed there. This 1.554 + // is cached in the service for all future Open[Unshared]Database calls. 1.555 + sDefaultPageSize = 1.556 + Preferences::GetInt(PREF_TS_PAGESIZE, PREF_TS_PAGESIZE_DEFAULT); 1.557 + 1.558 + mozilla::RegisterWeakMemoryReporter(this); 1.559 + mozilla::RegisterStorageSQLiteDistinguishedAmount(StorageSQLiteDistinguishedAmount); 1.560 + 1.561 + return NS_OK; 1.562 +} 1.563 + 1.564 +int 1.565 +Service::localeCompareStrings(const nsAString &aStr1, 1.566 + const nsAString &aStr2, 1.567 + int32_t aComparisonStrength) 1.568 +{ 1.569 + // The implementation of nsICollation.CompareString() is platform-dependent. 1.570 + // On Linux it's not thread-safe. It may not be on Windows and OS X either, 1.571 + // but it's more difficult to tell. We therefore synchronize this method. 1.572 + MutexAutoLock mutex(mMutex); 1.573 + 1.574 + nsICollation *coll = getLocaleCollation(); 1.575 + if (!coll) { 1.576 + NS_ERROR("Storage service has no collation"); 1.577 + return 0; 1.578 + } 1.579 + 1.580 + int32_t res; 1.581 + nsresult rv = coll->CompareString(aComparisonStrength, aStr1, aStr2, &res); 1.582 + if (NS_FAILED(rv)) { 1.583 + NS_ERROR("Collation compare string failed"); 1.584 + return 0; 1.585 + } 1.586 + 1.587 + return res; 1.588 +} 1.589 + 1.590 +nsICollation * 1.591 +Service::getLocaleCollation() 1.592 +{ 1.593 + mMutex.AssertCurrentThreadOwns(); 1.594 + 1.595 + if (mLocaleCollation) 1.596 + return mLocaleCollation; 1.597 + 1.598 + nsCOMPtr<nsILocaleService> svc(do_GetService(NS_LOCALESERVICE_CONTRACTID)); 1.599 + if (!svc) { 1.600 + NS_WARNING("Could not get locale service"); 1.601 + return nullptr; 1.602 + } 1.603 + 1.604 + nsCOMPtr<nsILocale> appLocale; 1.605 + nsresult rv = svc->GetApplicationLocale(getter_AddRefs(appLocale)); 1.606 + if (NS_FAILED(rv)) { 1.607 + NS_WARNING("Could not get application locale"); 1.608 + return nullptr; 1.609 + } 1.610 + 1.611 + nsCOMPtr<nsICollationFactory> collFact = 1.612 + do_CreateInstance(NS_COLLATIONFACTORY_CONTRACTID); 1.613 + if (!collFact) { 1.614 + NS_WARNING("Could not create collation factory"); 1.615 + return nullptr; 1.616 + } 1.617 + 1.618 + rv = collFact->CreateCollation(appLocale, getter_AddRefs(mLocaleCollation)); 1.619 + if (NS_FAILED(rv)) { 1.620 + NS_WARNING("Could not create collation"); 1.621 + return nullptr; 1.622 + } 1.623 + 1.624 + return mLocaleCollation; 1.625 +} 1.626 + 1.627 +//////////////////////////////////////////////////////////////////////////////// 1.628 +//// mozIStorageService 1.629 + 1.630 + 1.631 +NS_IMETHODIMP 1.632 +Service::OpenSpecialDatabase(const char *aStorageKey, 1.633 + mozIStorageConnection **_connection) 1.634 +{ 1.635 + nsresult rv; 1.636 + 1.637 + nsCOMPtr<nsIFile> storageFile; 1.638 + if (::strcmp(aStorageKey, "memory") == 0) { 1.639 + // just fall through with nullptr storageFile, this will cause the storage 1.640 + // connection to use a memory DB. 1.641 + } 1.642 + else { 1.643 + return NS_ERROR_INVALID_ARG; 1.644 + } 1.645 + 1.646 + nsRefPtr<Connection> msc = new Connection(this, SQLITE_OPEN_READWRITE, false); 1.647 + 1.648 + rv = storageFile ? msc->initialize(storageFile) : msc->initialize(); 1.649 + NS_ENSURE_SUCCESS(rv, rv); 1.650 + 1.651 + msc.forget(_connection); 1.652 + return NS_OK; 1.653 + 1.654 +} 1.655 + 1.656 +namespace { 1.657 + 1.658 +class AsyncInitDatabase MOZ_FINAL : public nsRunnable 1.659 +{ 1.660 +public: 1.661 + AsyncInitDatabase(Connection* aConnection, 1.662 + nsIFile* aStorageFile, 1.663 + int32_t aGrowthIncrement, 1.664 + mozIStorageCompletionCallback* aCallback) 1.665 + : mConnection(aConnection) 1.666 + , mStorageFile(aStorageFile) 1.667 + , mGrowthIncrement(aGrowthIncrement) 1.668 + , mCallback(aCallback) 1.669 + { 1.670 + MOZ_ASSERT(NS_IsMainThread()); 1.671 + } 1.672 + 1.673 + NS_IMETHOD Run() 1.674 + { 1.675 + MOZ_ASSERT(!NS_IsMainThread()); 1.676 + nsresult rv = mStorageFile ? mConnection->initialize(mStorageFile) 1.677 + : mConnection->initialize(); 1.678 + if (NS_FAILED(rv)) { 1.679 + return DispatchResult(rv, nullptr); 1.680 + } 1.681 + 1.682 + if (mGrowthIncrement >= 0) { 1.683 + // Ignore errors. In the future, we might wish to log them. 1.684 + (void)mConnection->SetGrowthIncrement(mGrowthIncrement, EmptyCString()); 1.685 + } 1.686 + 1.687 + return DispatchResult(NS_OK, NS_ISUPPORTS_CAST(mozIStorageAsyncConnection*, 1.688 + mConnection)); 1.689 + } 1.690 + 1.691 +private: 1.692 + nsresult DispatchResult(nsresult aStatus, nsISupports* aValue) { 1.693 + nsRefPtr<CallbackComplete> event = 1.694 + new CallbackComplete(aStatus, 1.695 + aValue, 1.696 + mCallback.forget()); 1.697 + return NS_DispatchToMainThread(event); 1.698 + } 1.699 + 1.700 + ~AsyncInitDatabase() 1.701 + { 1.702 + nsCOMPtr<nsIThread> thread; 1.703 + DebugOnly<nsresult> rv = NS_GetMainThread(getter_AddRefs(thread)); 1.704 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.705 + (void)NS_ProxyRelease(thread, mStorageFile); 1.706 + 1.707 + // Handle ambiguous nsISupports inheritance. 1.708 + Connection *rawConnection = nullptr; 1.709 + mConnection.swap(rawConnection); 1.710 + (void)NS_ProxyRelease(thread, NS_ISUPPORTS_CAST(mozIStorageConnection *, 1.711 + rawConnection)); 1.712 + 1.713 + // Generally, the callback will be released by CallbackComplete. 1.714 + // However, if for some reason Run() is not executed, we still 1.715 + // need to ensure that it is released here. 1.716 + mozIStorageCompletionCallback *rawCallback = nullptr; 1.717 + mCallback.swap(rawCallback); 1.718 + (void)NS_ProxyRelease(thread, rawCallback); 1.719 + } 1.720 + 1.721 + nsRefPtr<Connection> mConnection; 1.722 + nsCOMPtr<nsIFile> mStorageFile; 1.723 + int32_t mGrowthIncrement; 1.724 + nsRefPtr<mozIStorageCompletionCallback> mCallback; 1.725 +}; 1.726 + 1.727 +} // anonymous namespace 1.728 + 1.729 +NS_IMETHODIMP 1.730 +Service::OpenAsyncDatabase(nsIVariant *aDatabaseStore, 1.731 + nsIPropertyBag2 *aOptions, 1.732 + mozIStorageCompletionCallback *aCallback) 1.733 +{ 1.734 + if (!NS_IsMainThread()) { 1.735 + return NS_ERROR_NOT_SAME_THREAD; 1.736 + } 1.737 + NS_ENSURE_ARG(aDatabaseStore); 1.738 + NS_ENSURE_ARG(aCallback); 1.739 + 1.740 + nsCOMPtr<nsIFile> storageFile; 1.741 + int flags = SQLITE_OPEN_READWRITE; 1.742 + 1.743 + nsCOMPtr<nsISupports> dbStore; 1.744 + nsresult rv = aDatabaseStore->GetAsISupports(getter_AddRefs(dbStore)); 1.745 + if (NS_SUCCEEDED(rv)) { 1.746 + // Generally, aDatabaseStore holds the database nsIFile. 1.747 + storageFile = do_QueryInterface(dbStore, &rv); 1.748 + if (NS_FAILED(rv)) { 1.749 + return NS_ERROR_INVALID_ARG; 1.750 + } 1.751 + 1.752 + rv = storageFile->Clone(getter_AddRefs(storageFile)); 1.753 + MOZ_ASSERT(NS_SUCCEEDED(rv)); 1.754 + 1.755 + // Ensure that SQLITE_OPEN_CREATE is passed in for compatibility reasons. 1.756 + flags |= SQLITE_OPEN_CREATE; 1.757 + 1.758 + // Extract and apply the shared-cache option. 1.759 + bool shared = false; 1.760 + if (aOptions) { 1.761 + rv = aOptions->GetPropertyAsBool(NS_LITERAL_STRING("shared"), &shared); 1.762 + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { 1.763 + return NS_ERROR_INVALID_ARG; 1.764 + } 1.765 + } 1.766 + flags |= shared ? SQLITE_OPEN_SHAREDCACHE : SQLITE_OPEN_PRIVATECACHE; 1.767 + } else { 1.768 + // Sometimes, however, it's a special database name. 1.769 + nsAutoCString keyString; 1.770 + rv = aDatabaseStore->GetAsACString(keyString); 1.771 + if (NS_FAILED(rv) || !keyString.EqualsLiteral("memory")) { 1.772 + return NS_ERROR_INVALID_ARG; 1.773 + } 1.774 + 1.775 + // Just fall through with nullptr storageFile, this will cause the storage 1.776 + // connection to use a memory DB. 1.777 + } 1.778 + 1.779 + int32_t growthIncrement = -1; 1.780 + if (aOptions && storageFile) { 1.781 + rv = aOptions->GetPropertyAsInt32(NS_LITERAL_STRING("growthIncrement"), 1.782 + &growthIncrement); 1.783 + if (NS_FAILED(rv) && rv != NS_ERROR_NOT_AVAILABLE) { 1.784 + return NS_ERROR_INVALID_ARG; 1.785 + } 1.786 + } 1.787 + 1.788 + // Create connection on this thread, but initialize it on its helper thread. 1.789 + nsRefPtr<Connection> msc = new Connection(this, flags, true); 1.790 + nsCOMPtr<nsIEventTarget> target = msc->getAsyncExecutionTarget(); 1.791 + MOZ_ASSERT(target, "Cannot initialize a connection that has been closed already"); 1.792 + 1.793 + nsRefPtr<AsyncInitDatabase> asyncInit = 1.794 + new AsyncInitDatabase(msc, 1.795 + storageFile, 1.796 + growthIncrement, 1.797 + aCallback); 1.798 + return target->Dispatch(asyncInit, nsIEventTarget::DISPATCH_NORMAL); 1.799 +} 1.800 + 1.801 +NS_IMETHODIMP 1.802 +Service::OpenDatabase(nsIFile *aDatabaseFile, 1.803 + mozIStorageConnection **_connection) 1.804 +{ 1.805 + NS_ENSURE_ARG(aDatabaseFile); 1.806 + 1.807 + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility 1.808 + // reasons. 1.809 + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | 1.810 + SQLITE_OPEN_CREATE; 1.811 + nsRefPtr<Connection> msc = new Connection(this, flags, false); 1.812 + 1.813 + nsresult rv = msc->initialize(aDatabaseFile); 1.814 + NS_ENSURE_SUCCESS(rv, rv); 1.815 + 1.816 + msc.forget(_connection); 1.817 + return NS_OK; 1.818 +} 1.819 + 1.820 +NS_IMETHODIMP 1.821 +Service::OpenUnsharedDatabase(nsIFile *aDatabaseFile, 1.822 + mozIStorageConnection **_connection) 1.823 +{ 1.824 + NS_ENSURE_ARG(aDatabaseFile); 1.825 + 1.826 + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility 1.827 + // reasons. 1.828 + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_PRIVATECACHE | 1.829 + SQLITE_OPEN_CREATE; 1.830 + nsRefPtr<Connection> msc = new Connection(this, flags, false); 1.831 + 1.832 + nsresult rv = msc->initialize(aDatabaseFile); 1.833 + NS_ENSURE_SUCCESS(rv, rv); 1.834 + 1.835 + msc.forget(_connection); 1.836 + return NS_OK; 1.837 +} 1.838 + 1.839 +NS_IMETHODIMP 1.840 +Service::OpenDatabaseWithFileURL(nsIFileURL *aFileURL, 1.841 + mozIStorageConnection **_connection) 1.842 +{ 1.843 + NS_ENSURE_ARG(aFileURL); 1.844 + 1.845 + // Always ensure that SQLITE_OPEN_CREATE is passed in for compatibility 1.846 + // reasons. 1.847 + int flags = SQLITE_OPEN_READWRITE | SQLITE_OPEN_SHAREDCACHE | 1.848 + SQLITE_OPEN_CREATE | SQLITE_OPEN_URI; 1.849 + nsRefPtr<Connection> msc = new Connection(this, flags, false); 1.850 + 1.851 + nsresult rv = msc->initialize(aFileURL); 1.852 + NS_ENSURE_SUCCESS(rv, rv); 1.853 + 1.854 + msc.forget(_connection); 1.855 + return NS_OK; 1.856 +} 1.857 + 1.858 +NS_IMETHODIMP 1.859 +Service::BackupDatabaseFile(nsIFile *aDBFile, 1.860 + const nsAString &aBackupFileName, 1.861 + nsIFile *aBackupParentDirectory, 1.862 + nsIFile **backup) 1.863 +{ 1.864 + nsresult rv; 1.865 + nsCOMPtr<nsIFile> parentDir = aBackupParentDirectory; 1.866 + if (!parentDir) { 1.867 + // This argument is optional, and defaults to the same parent directory 1.868 + // as the current file. 1.869 + rv = aDBFile->GetParent(getter_AddRefs(parentDir)); 1.870 + NS_ENSURE_SUCCESS(rv, rv); 1.871 + } 1.872 + 1.873 + nsCOMPtr<nsIFile> backupDB; 1.874 + rv = parentDir->Clone(getter_AddRefs(backupDB)); 1.875 + NS_ENSURE_SUCCESS(rv, rv); 1.876 + 1.877 + rv = backupDB->Append(aBackupFileName); 1.878 + NS_ENSURE_SUCCESS(rv, rv); 1.879 + 1.880 + rv = backupDB->CreateUnique(nsIFile::NORMAL_FILE_TYPE, 0600); 1.881 + NS_ENSURE_SUCCESS(rv, rv); 1.882 + 1.883 + nsAutoString fileName; 1.884 + rv = backupDB->GetLeafName(fileName); 1.885 + NS_ENSURE_SUCCESS(rv, rv); 1.886 + 1.887 + rv = backupDB->Remove(false); 1.888 + NS_ENSURE_SUCCESS(rv, rv); 1.889 + 1.890 + backupDB.forget(backup); 1.891 + 1.892 + return aDBFile->CopyTo(parentDir, fileName); 1.893 +} 1.894 + 1.895 +//////////////////////////////////////////////////////////////////////////////// 1.896 +//// nsIObserver 1.897 + 1.898 +NS_IMETHODIMP 1.899 +Service::Observe(nsISupports *, const char *aTopic, const char16_t *) 1.900 +{ 1.901 + if (strcmp(aTopic, "memory-pressure") == 0) { 1.902 + minimizeMemory(); 1.903 + } else if (strcmp(aTopic, "xpcom-shutdown") == 0) { 1.904 + shutdown(); 1.905 + } else if (strcmp(aTopic, "xpcom-shutdown-threads") == 0) { 1.906 + nsCOMPtr<nsIObserverService> os = 1.907 + mozilla::services::GetObserverService(); 1.908 + 1.909 + for (size_t i = 0; i < ArrayLength(sObserverTopics); ++i) { 1.910 + (void)os->RemoveObserver(this, sObserverTopics[i]); 1.911 + } 1.912 + 1.913 + bool anyOpen = false; 1.914 + do { 1.915 + nsTArray<nsRefPtr<Connection> > connections; 1.916 + getConnections(connections); 1.917 + anyOpen = false; 1.918 + for (uint32_t i = 0; i < connections.Length(); i++) { 1.919 + nsRefPtr<Connection> &conn = connections[i]; 1.920 + if (conn->isClosing()) { 1.921 + anyOpen = true; 1.922 + break; 1.923 + } 1.924 + } 1.925 + if (anyOpen) { 1.926 + nsCOMPtr<nsIThread> thread = do_GetCurrentThread(); 1.927 + NS_ProcessNextEvent(thread); 1.928 + } 1.929 + } while (anyOpen); 1.930 + 1.931 + if (gShutdownChecks == SCM_CRASH) { 1.932 + nsTArray<nsRefPtr<Connection> > connections; 1.933 + getConnections(connections); 1.934 + for (uint32_t i = 0, n = connections.Length(); i < n; i++) { 1.935 + if (!connections[i]->isClosed()) { 1.936 + MOZ_CRASH(); 1.937 + } 1.938 + } 1.939 + } 1.940 + } 1.941 + 1.942 + return NS_OK; 1.943 +} 1.944 + 1.945 +} // namespace storage 1.946 +} // namespace mozilla