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