Line data Source code
1 : /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 : /* vim: set ts=8 sts=2 et sw=2 tw=80: */
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 "StorageDBThread.h"
8 : #include "StorageDBUpdater.h"
9 : #include "StorageUtils.h"
10 : #include "LocalStorageCache.h"
11 : #include "LocalStorageManager.h"
12 :
13 : #include "nsIEffectiveTLDService.h"
14 : #include "nsDirectoryServiceUtils.h"
15 : #include "nsAppDirectoryServiceDefs.h"
16 : #include "nsThreadUtils.h"
17 : #include "nsProxyRelease.h"
18 : #include "mozStorageCID.h"
19 : #include "mozStorageHelper.h"
20 : #include "mozIStorageService.h"
21 : #include "mozIStorageBindingParamsArray.h"
22 : #include "mozIStorageBindingParams.h"
23 : #include "mozIStorageValueArray.h"
24 : #include "mozIStorageFunction.h"
25 : #include "mozilla/BasePrincipal.h"
26 : #include "nsIObserverService.h"
27 : #include "nsVariant.h"
28 : #include "mozilla/IOInterposer.h"
29 : #include "mozilla/Services.h"
30 : #include "mozilla/Tokenizer.h"
31 : #include "GeckoProfiler.h"
32 :
33 : // How long we collect write oprerations
34 : // before they are flushed to the database
35 : // In milliseconds.
36 : #define FLUSHING_INTERVAL_MS 5000
37 :
38 : // Write Ahead Log's maximum size is 512KB
39 : #define MAX_WAL_SIZE_BYTES 512 * 1024
40 :
41 : // Current version of the database schema
42 : #define CURRENT_SCHEMA_VERSION 2
43 :
44 : namespace mozilla {
45 : namespace dom {
46 :
47 : using namespace StorageUtils;
48 :
49 : namespace { // anon
50 :
51 : // This is only a compatibility code for schema version 0. Returns the 'scope'
52 : // key in the schema version 0 format for the scope column.
53 : nsCString
54 0 : Scheme0Scope(LocalStorageCacheBridge* aCache)
55 : {
56 0 : nsCString result;
57 :
58 0 : nsCString suffix = aCache->OriginSuffix();
59 :
60 0 : OriginAttributes oa;
61 0 : if (!suffix.IsEmpty()) {
62 0 : DebugOnly<bool> success = oa.PopulateFromSuffix(suffix);
63 0 : MOZ_ASSERT(success);
64 : }
65 :
66 0 : if (oa.mAppId != nsIScriptSecurityManager::NO_APP_ID ||
67 0 : oa.mInIsolatedMozBrowser) {
68 0 : result.AppendInt(oa.mAppId);
69 0 : result.Append(':');
70 0 : result.Append(oa.mInIsolatedMozBrowser ? 't' : 'f');
71 0 : result.Append(':');
72 : }
73 :
74 : // If there is more than just appid and/or inbrowser stored in origin
75 : // attributes, put it to the schema 0 scope as well. We must do that
76 : // to keep the scope column unique (same resolution as schema 1 has
77 : // with originAttributes and originKey columns) so that switch between
78 : // schema 1 and 0 always works in both ways.
79 0 : nsAutoCString remaining;
80 0 : oa.mAppId = 0;
81 0 : oa.mInIsolatedMozBrowser = false;
82 0 : oa.CreateSuffix(remaining);
83 0 : if (!remaining.IsEmpty()) {
84 0 : MOZ_ASSERT(!suffix.IsEmpty());
85 :
86 0 : if (result.IsEmpty()) {
87 : // Must contain the old prefix, otherwise we won't search for the whole
88 : // origin attributes suffix.
89 0 : result.Append(NS_LITERAL_CSTRING("0:f:"));
90 : }
91 : // Append the whole origin attributes suffix despite we have already stored
92 : // appid and inbrowser. We are only looking for it when the scope string
93 : // starts with "$appid:$inbrowser:" (with whatever valid values).
94 : //
95 : // The OriginAttributes suffix is a string in a form like:
96 : // "^addonId=101&userContextId=5" and it's ensured it always starts with '^'
97 : // and never contains ':'. See OriginAttributes::CreateSuffix.
98 0 : result.Append(suffix);
99 0 : result.Append(':');
100 : }
101 :
102 0 : result.Append(aCache->OriginNoSuffix());
103 :
104 0 : return result;
105 : }
106 :
107 : } // anon
108 :
109 :
110 2 : StorageDBBridge::StorageDBBridge()
111 : {
112 2 : }
113 :
114 :
115 1 : StorageDBThread::StorageDBThread()
116 : : mThread(nullptr)
117 1 : , mThreadObserver(new ThreadObserver())
118 : , mStopIOThread(false)
119 : , mWALModeEnabled(false)
120 : , mDBReady(false)
121 : , mStatus(NS_OK)
122 : , mWorkerStatements(mWorkerConnection)
123 : , mReaderStatements(mReaderConnection)
124 : , mDirtyEpoch(0)
125 : , mFlushImmediately(false)
126 2 : , mPriorityCounter(0)
127 : {
128 1 : }
129 :
130 : nsresult
131 1 : StorageDBThread::Init()
132 : {
133 : nsresult rv;
134 :
135 : // Need to determine location on the main thread, since
136 : // NS_GetSpecialDirectory access the atom table that can
137 : // be accessed only on the main thread.
138 1 : rv = NS_GetSpecialDirectory(NS_APP_USER_PROFILE_50_DIR,
139 2 : getter_AddRefs(mDatabaseFile));
140 1 : NS_ENSURE_SUCCESS(rv, rv);
141 :
142 1 : rv = mDatabaseFile->Append(NS_LITERAL_STRING("webappsstore.sqlite"));
143 1 : NS_ENSURE_SUCCESS(rv, rv);
144 :
145 : // Ensure mozIStorageService init on the main thread first.
146 : nsCOMPtr<mozIStorageService> service =
147 2 : do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
148 1 : NS_ENSURE_SUCCESS(rv, rv);
149 :
150 : // Need to keep the lock to avoid setting mThread later then
151 : // the thread body executes.
152 2 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
153 :
154 1 : mThread = PR_CreateThread(PR_USER_THREAD, &StorageDBThread::ThreadFunc, this,
155 : PR_PRIORITY_LOW, PR_GLOBAL_THREAD,
156 : PR_JOINABLE_THREAD, 262144);
157 1 : if (!mThread) {
158 0 : return NS_ERROR_OUT_OF_MEMORY;
159 : }
160 :
161 1 : return NS_OK;
162 : }
163 :
164 : nsresult
165 0 : StorageDBThread::Shutdown()
166 : {
167 0 : if (!mThread) {
168 0 : return NS_ERROR_NOT_INITIALIZED;
169 : }
170 :
171 0 : Telemetry::AutoTimer<Telemetry::LOCALDOMSTORAGE_SHUTDOWN_DATABASE_MS> timer;
172 :
173 : {
174 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
175 :
176 : // After we stop, no other operations can be accepted
177 0 : mFlushImmediately = true;
178 0 : mStopIOThread = true;
179 0 : monitor.Notify();
180 : }
181 :
182 0 : PR_JoinThread(mThread);
183 0 : mThread = nullptr;
184 :
185 0 : return mStatus;
186 : }
187 :
188 : void
189 0 : StorageDBThread::SyncPreload(LocalStorageCacheBridge* aCache, bool aForceSync)
190 : {
191 0 : AUTO_PROFILER_LABEL("StorageDBThread::SyncPreload", STORAGE);
192 0 : if (!aForceSync && aCache->LoadedCount()) {
193 : // Preload already started for this cache, just wait for it to finish.
194 : // LoadWait will exit after LoadDone on the cache has been called.
195 0 : SetHigherPriority();
196 0 : aCache->LoadWait();
197 0 : SetDefaultPriority();
198 0 : return;
199 : }
200 :
201 : // Bypass sync load when an update is pending in the queue to write, we would
202 : // get incosistent data in the cache. Also don't allow sync main-thread
203 : // preload when DB open and init is still pending on the background thread.
204 0 : if (mDBReady && mWALModeEnabled) {
205 : bool pendingTasks;
206 : {
207 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
208 0 : pendingTasks = mPendingTasks.IsOriginUpdatePending(aCache->OriginSuffix(), aCache->OriginNoSuffix()) ||
209 0 : mPendingTasks.IsOriginClearPending(aCache->OriginSuffix(), aCache->OriginNoSuffix());
210 : }
211 :
212 0 : if (!pendingTasks) {
213 : // WAL is enabled, thus do the load synchronously on the main thread.
214 0 : DBOperation preload(DBOperation::opPreload, aCache);
215 0 : preload.PerformAndFinalize(this);
216 0 : return;
217 : }
218 : }
219 :
220 : // Need to go asynchronously since WAL is not allowed or scheduled updates
221 : // need to be flushed first.
222 : // Schedule preload for this cache as the first operation.
223 0 : nsresult rv = InsertDBOp(new DBOperation(DBOperation::opPreloadUrgent, aCache));
224 :
225 : // LoadWait exits after LoadDone of the cache has been called.
226 0 : if (NS_SUCCEEDED(rv)) {
227 0 : aCache->LoadWait();
228 : }
229 : }
230 :
231 : void
232 0 : StorageDBThread::AsyncFlush()
233 : {
234 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
235 0 : mFlushImmediately = true;
236 0 : monitor.Notify();
237 0 : }
238 :
239 : bool
240 0 : StorageDBThread::ShouldPreloadOrigin(const nsACString& aOrigin)
241 : {
242 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
243 0 : return mOriginsHavingData.Contains(aOrigin);
244 : }
245 :
246 : void
247 0 : StorageDBThread::GetOriginsHavingData(InfallibleTArray<nsCString>* aOrigins)
248 : {
249 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
250 0 : for (auto iter = mOriginsHavingData.Iter(); !iter.Done(); iter.Next()) {
251 0 : aOrigins->AppendElement(iter.Get()->GetKey());
252 : }
253 0 : }
254 :
255 : nsresult
256 3 : StorageDBThread::InsertDBOp(StorageDBThread::DBOperation* aOperation)
257 : {
258 6 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
259 :
260 : // Sentinel to don't forget to delete the operation when we exit early.
261 6 : nsAutoPtr<StorageDBThread::DBOperation> opScope(aOperation);
262 :
263 3 : if (NS_FAILED(mStatus)) {
264 0 : MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
265 0 : aOperation->Finalize(mStatus);
266 0 : return mStatus;
267 : }
268 :
269 3 : if (mStopIOThread) {
270 : // Thread use after shutdown demanded.
271 0 : MOZ_ASSERT(false);
272 : return NS_ERROR_NOT_INITIALIZED;
273 : }
274 :
275 3 : switch (aOperation->Type()) {
276 : case DBOperation::opPreload:
277 : case DBOperation::opPreloadUrgent:
278 2 : if (mPendingTasks.IsOriginUpdatePending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
279 : // If there is a pending update operation for the scope first do the flush
280 : // before we preload the cache. This may happen in an extremely rare case
281 : // when a child process throws away its cache before flush on the parent
282 : // has finished. If we would preloaded the cache as a priority operation
283 : // before the pending flush, we would have got an inconsistent cache
284 : // content.
285 0 : mFlushImmediately = true;
286 2 : } else if (mPendingTasks.IsOriginClearPending(aOperation->OriginSuffix(), aOperation->OriginNoSuffix())) {
287 : // The scope is scheduled to be cleared, so just quickly load as empty.
288 : // We need to do this to prevent load of the DB data before the scope has
289 : // actually been cleared from the database. Preloads are processed
290 : // immediately before update and clear operations on the database that are
291 : // flushed periodically in batches.
292 0 : MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
293 0 : aOperation->Finalize(NS_OK);
294 0 : return NS_OK;
295 : }
296 : MOZ_FALLTHROUGH;
297 :
298 : case DBOperation::opGetUsage:
299 3 : if (aOperation->Type() == DBOperation::opPreloadUrgent) {
300 0 : SetHigherPriority(); // Dropped back after urgent preload execution
301 0 : mPreloads.InsertElementAt(0, aOperation);
302 : } else {
303 3 : mPreloads.AppendElement(aOperation);
304 : }
305 :
306 : // DB operation adopted, don't delete it.
307 3 : opScope.forget();
308 :
309 : // Immediately start executing this.
310 3 : monitor.Notify();
311 3 : break;
312 :
313 : default:
314 : // Update operations are first collected, coalesced and then flushed
315 : // after a short time.
316 0 : mPendingTasks.Add(aOperation);
317 :
318 : // DB operation adopted, don't delete it.
319 0 : opScope.forget();
320 :
321 0 : ScheduleFlush();
322 0 : break;
323 : }
324 :
325 3 : return NS_OK;
326 : }
327 :
328 : void
329 0 : StorageDBThread::SetHigherPriority()
330 : {
331 0 : ++mPriorityCounter;
332 0 : PR_SetThreadPriority(mThread, PR_PRIORITY_URGENT);
333 0 : }
334 :
335 : void
336 0 : StorageDBThread::SetDefaultPriority()
337 : {
338 0 : if (--mPriorityCounter <= 0) {
339 0 : PR_SetThreadPriority(mThread, PR_PRIORITY_LOW);
340 : }
341 0 : }
342 :
343 : void
344 1 : StorageDBThread::ThreadFunc(void* aArg)
345 : {
346 1 : AutoProfilerRegisterThread registerThread("localStorage DB");
347 1 : NS_SetCurrentThreadName("localStorage DB");
348 1 : mozilla::IOInterposer::RegisterCurrentThread();
349 :
350 1 : StorageDBThread* thread = static_cast<StorageDBThread*>(aArg);
351 1 : thread->ThreadFunc();
352 0 : mozilla::IOInterposer::UnregisterCurrentThread();
353 0 : }
354 :
355 : void
356 1 : StorageDBThread::ThreadFunc()
357 : {
358 1 : nsresult rv = InitDatabase();
359 :
360 1 : MonitorAutoLock lockMonitor(mThreadObserver->GetMonitor());
361 :
362 1 : if (NS_FAILED(rv)) {
363 0 : mStatus = rv;
364 0 : mStopIOThread = true;
365 0 : return;
366 : }
367 :
368 : // Create an nsIThread for the current PRThread, so we can observe runnables
369 : // dispatched to it.
370 1 : nsCOMPtr<nsIThread> thread = NS_GetCurrentThread();
371 1 : nsCOMPtr<nsIThreadInternal> threadInternal = do_QueryInterface(thread);
372 1 : MOZ_ASSERT(threadInternal); // Should always succeed.
373 1 : threadInternal->SetObserver(mThreadObserver);
374 :
375 7 : while (MOZ_LIKELY(!mStopIOThread || mPreloads.Length() ||
376 : mPendingTasks.HasTasks() ||
377 : mThreadObserver->HasPendingEvents())) {
378 : // Process xpcom events first.
379 4 : while (MOZ_UNLIKELY(mThreadObserver->HasPendingEvents())) {
380 0 : mThreadObserver->ClearPendingEvents();
381 0 : MonitorAutoUnlock unlock(mThreadObserver->GetMonitor());
382 : bool processedEvent;
383 0 : do {
384 0 : rv = thread->ProcessNextEvent(false, &processedEvent);
385 0 : } while (NS_SUCCEEDED(rv) && processedEvent);
386 : }
387 :
388 4 : if (MOZ_UNLIKELY(TimeUntilFlush() == 0)) {
389 : // Flush time is up or flush has been forced, do it now.
390 0 : UnscheduleFlush();
391 0 : if (mPendingTasks.Prepare()) {
392 : {
393 0 : MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
394 0 : rv = mPendingTasks.Execute(this);
395 : }
396 :
397 0 : if (!mPendingTasks.Finalize(rv)) {
398 0 : mStatus = rv;
399 0 : NS_WARNING("localStorage DB access broken");
400 : }
401 : }
402 0 : NotifyFlushCompletion();
403 4 : } else if (MOZ_LIKELY(mPreloads.Length())) {
404 6 : nsAutoPtr<DBOperation> op(mPreloads[0]);
405 3 : mPreloads.RemoveElementAt(0);
406 : {
407 6 : MonitorAutoUnlock unlockMonitor(mThreadObserver->GetMonitor());
408 3 : op->PerformAndFinalize(this);
409 : }
410 :
411 3 : if (op->Type() == DBOperation::opPreloadUrgent) {
412 0 : SetDefaultPriority(); // urgent preload unscheduled
413 : }
414 1 : } else if (MOZ_UNLIKELY(!mStopIOThread)) {
415 1 : lockMonitor.Wait(TimeUntilFlush());
416 : }
417 : } // thread loop
418 :
419 0 : mStatus = ShutdownDatabase();
420 :
421 0 : if (threadInternal) {
422 0 : threadInternal->SetObserver(nullptr);
423 : }
424 : }
425 :
426 :
427 5 : NS_IMPL_ISUPPORTS(StorageDBThread::ThreadObserver, nsIThreadObserver)
428 :
429 : NS_IMETHODIMP
430 0 : StorageDBThread::ThreadObserver::OnDispatchedEvent(nsIThreadInternal* aThread)
431 : {
432 0 : MonitorAutoLock lock(mMonitor);
433 0 : mHasPendingEvents = true;
434 0 : lock.Notify();
435 0 : return NS_OK;
436 : }
437 :
438 : NS_IMETHODIMP
439 0 : StorageDBThread::ThreadObserver::OnProcessNextEvent(nsIThreadInternal* aThread,
440 : bool mayWait)
441 : {
442 0 : return NS_OK;
443 : }
444 :
445 : NS_IMETHODIMP
446 0 : StorageDBThread::ThreadObserver::AfterProcessNextEvent(nsIThreadInternal* aThread,
447 : bool eventWasProcessed)
448 : {
449 0 : return NS_OK;
450 : }
451 :
452 : nsresult
453 1 : StorageDBThread::OpenDatabaseConnection()
454 : {
455 : nsresult rv;
456 :
457 1 : MOZ_ASSERT(!NS_IsMainThread());
458 :
459 : nsCOMPtr<mozIStorageService> service
460 2 : = do_GetService(MOZ_STORAGE_SERVICE_CONTRACTID, &rv);
461 1 : NS_ENSURE_SUCCESS(rv, rv);
462 :
463 1 : rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
464 1 : if (rv == NS_ERROR_FILE_CORRUPTED) {
465 : // delete the db and try opening again
466 0 : rv = mDatabaseFile->Remove(false);
467 0 : NS_ENSURE_SUCCESS(rv, rv);
468 0 : rv = service->OpenUnsharedDatabase(mDatabaseFile, getter_AddRefs(mWorkerConnection));
469 : }
470 1 : NS_ENSURE_SUCCESS(rv, rv);
471 :
472 1 : return NS_OK;
473 : }
474 :
475 : nsresult
476 1 : StorageDBThread::OpenAndUpdateDatabase()
477 : {
478 : nsresult rv;
479 :
480 : // Here we are on the worker thread. This opens the worker connection.
481 1 : MOZ_ASSERT(!NS_IsMainThread());
482 :
483 1 : rv = OpenDatabaseConnection();
484 1 : NS_ENSURE_SUCCESS(rv, rv);
485 :
486 1 : rv = TryJournalMode();
487 1 : NS_ENSURE_SUCCESS(rv, rv);
488 :
489 1 : return NS_OK;
490 : }
491 :
492 : nsresult
493 1 : StorageDBThread::InitDatabase()
494 : {
495 : nsresult rv;
496 :
497 : // Here we are on the worker thread. This opens the worker connection.
498 1 : MOZ_ASSERT(!NS_IsMainThread());
499 :
500 1 : rv = OpenAndUpdateDatabase();
501 1 : NS_ENSURE_SUCCESS(rv, rv);
502 :
503 1 : rv = StorageDBUpdater::Update(mWorkerConnection);
504 1 : if (NS_FAILED(rv)) {
505 : // Update has failed, rather throw the database away and try
506 : // opening and setting it up again.
507 0 : rv = mWorkerConnection->Close();
508 0 : mWorkerConnection = nullptr;
509 0 : NS_ENSURE_SUCCESS(rv, rv);
510 :
511 0 : rv = mDatabaseFile->Remove(false);
512 0 : NS_ENSURE_SUCCESS(rv, rv);
513 :
514 0 : rv = OpenAndUpdateDatabase();
515 0 : NS_ENSURE_SUCCESS(rv, rv);
516 : }
517 :
518 : // Create a read-only clone
519 1 : (void)mWorkerConnection->Clone(true, getter_AddRefs(mReaderConnection));
520 1 : NS_ENSURE_TRUE(mReaderConnection, NS_ERROR_FAILURE);
521 :
522 : // Database open and all initiation operation are done. Switching this flag
523 : // to true allow main thread to read directly from the database. If we would
524 : // allow this sooner, we would have opened a window where main thread read
525 : // might operate on a totally broken and incosistent database.
526 1 : mDBReady = true;
527 :
528 : // List scopes having any stored data
529 2 : nsCOMPtr<mozIStorageStatement> stmt;
530 : // Note: result of this select must match StorageManager::CreateOrigin()
531 4 : rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
532 : "SELECT DISTINCT originAttributes || ':' || originKey FROM webappsstore2"),
533 4 : getter_AddRefs(stmt));
534 1 : NS_ENSURE_SUCCESS(rv, rv);
535 2 : mozStorageStatementScoper scope(stmt);
536 :
537 : bool exists;
538 1 : while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
539 0 : nsAutoCString foundOrigin;
540 0 : rv = stmt->GetUTF8String(0, foundOrigin);
541 0 : NS_ENSURE_SUCCESS(rv, rv);
542 :
543 0 : MonitorAutoLock monitor(mThreadObserver->GetMonitor());
544 0 : mOriginsHavingData.PutEntry(foundOrigin);
545 : }
546 :
547 1 : return NS_OK;
548 : }
549 :
550 : nsresult
551 1 : StorageDBThread::SetJournalMode(bool aIsWal)
552 : {
553 : nsresult rv;
554 :
555 : nsAutoCString stmtString(
556 2 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA journal_mode = ");
557 1 : if (aIsWal) {
558 1 : stmtString.AppendLiteral("wal");
559 : } else {
560 0 : stmtString.AppendLiteral("truncate");
561 : }
562 :
563 2 : nsCOMPtr<mozIStorageStatement> stmt;
564 1 : rv = mWorkerConnection->CreateStatement(stmtString, getter_AddRefs(stmt));
565 1 : NS_ENSURE_SUCCESS(rv, rv);
566 2 : mozStorageStatementScoper scope(stmt);
567 :
568 1 : bool hasResult = false;
569 1 : rv = stmt->ExecuteStep(&hasResult);
570 1 : NS_ENSURE_SUCCESS(rv, rv);
571 1 : if (!hasResult) {
572 0 : return NS_ERROR_FAILURE;
573 : }
574 :
575 2 : nsAutoCString journalMode;
576 1 : rv = stmt->GetUTF8String(0, journalMode);
577 1 : NS_ENSURE_SUCCESS(rv, rv);
578 2 : if ((aIsWal && !journalMode.EqualsLiteral("wal")) ||
579 1 : (!aIsWal && !journalMode.EqualsLiteral("truncate"))) {
580 0 : return NS_ERROR_FAILURE;
581 : }
582 :
583 1 : return NS_OK;
584 : }
585 :
586 : nsresult
587 1 : StorageDBThread::TryJournalMode()
588 : {
589 : nsresult rv;
590 :
591 1 : rv = SetJournalMode(true);
592 1 : if (NS_FAILED(rv)) {
593 0 : mWALModeEnabled = false;
594 :
595 0 : rv = SetJournalMode(false);
596 0 : NS_ENSURE_SUCCESS(rv, rv);
597 : } else {
598 1 : mWALModeEnabled = true;
599 :
600 1 : rv = ConfigureWALBehavior();
601 1 : NS_ENSURE_SUCCESS(rv, rv);
602 : }
603 :
604 1 : return NS_OK;
605 : }
606 :
607 : nsresult
608 1 : StorageDBThread::ConfigureWALBehavior()
609 : {
610 : // Get the DB's page size
611 2 : nsCOMPtr<mozIStorageStatement> stmt;
612 4 : nsresult rv = mWorkerConnection->CreateStatement(NS_LITERAL_CSTRING(
613 : MOZ_STORAGE_UNIQUIFY_QUERY_STR "PRAGMA page_size"
614 4 : ), getter_AddRefs(stmt));
615 1 : NS_ENSURE_SUCCESS(rv, rv);
616 :
617 1 : bool hasResult = false;
618 1 : rv = stmt->ExecuteStep(&hasResult);
619 1 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && hasResult, NS_ERROR_FAILURE);
620 :
621 1 : int32_t pageSize = 0;
622 1 : rv = stmt->GetInt32(0, &pageSize);
623 1 : NS_ENSURE_TRUE(NS_SUCCEEDED(rv) && pageSize > 0, NS_ERROR_UNEXPECTED);
624 :
625 : // Set the threshold for auto-checkpointing the WAL.
626 : // We don't want giant logs slowing down reads & shutdown.
627 1 : int32_t thresholdInPages = static_cast<int32_t>(MAX_WAL_SIZE_BYTES / pageSize);
628 2 : nsAutoCString thresholdPragma("PRAGMA wal_autocheckpoint = ");
629 1 : thresholdPragma.AppendInt(thresholdInPages);
630 1 : rv = mWorkerConnection->ExecuteSimpleSQL(thresholdPragma);
631 1 : NS_ENSURE_SUCCESS(rv, rv);
632 :
633 : // Set the maximum WAL log size to reduce footprint on mobile (large empty
634 : // WAL files will be truncated)
635 2 : nsAutoCString journalSizePragma("PRAGMA journal_size_limit = ");
636 : // bug 600307: mak recommends setting this to 3 times the auto-checkpoint
637 : // threshold
638 1 : journalSizePragma.AppendInt(MAX_WAL_SIZE_BYTES * 3);
639 1 : rv = mWorkerConnection->ExecuteSimpleSQL(journalSizePragma);
640 1 : NS_ENSURE_SUCCESS(rv, rv);
641 :
642 1 : return NS_OK;
643 : }
644 :
645 : nsresult
646 0 : StorageDBThread::ShutdownDatabase()
647 : {
648 : // Has to be called on the worker thread.
649 0 : MOZ_ASSERT(!NS_IsMainThread());
650 :
651 0 : nsresult rv = mStatus;
652 :
653 0 : mDBReady = false;
654 :
655 : // Finalize the cached statements.
656 0 : mReaderStatements.FinalizeStatements();
657 0 : mWorkerStatements.FinalizeStatements();
658 :
659 0 : if (mReaderConnection) {
660 : // No need to sync access to mReaderConnection since the main thread
661 : // is right now joining this thread, unable to execute any events.
662 0 : mReaderConnection->Close();
663 0 : mReaderConnection = nullptr;
664 : }
665 :
666 0 : if (mWorkerConnection) {
667 0 : rv = mWorkerConnection->Close();
668 0 : mWorkerConnection = nullptr;
669 : }
670 :
671 0 : return rv;
672 : }
673 :
674 : void
675 0 : StorageDBThread::ScheduleFlush()
676 : {
677 0 : if (mDirtyEpoch) {
678 0 : return; // Already scheduled
679 : }
680 :
681 : // Must be non-zero to indicate we are scheduled
682 0 : mDirtyEpoch = PR_IntervalNow() | 1;
683 :
684 : // Wake the monitor from indefinite sleep...
685 0 : (mThreadObserver->GetMonitor()).Notify();
686 : }
687 :
688 : void
689 0 : StorageDBThread::UnscheduleFlush()
690 : {
691 : // We are just about to do the flush, drop flags
692 0 : mFlushImmediately = false;
693 0 : mDirtyEpoch = 0;
694 0 : }
695 :
696 : PRIntervalTime
697 5 : StorageDBThread::TimeUntilFlush()
698 : {
699 5 : if (mFlushImmediately) {
700 0 : return 0; // Do it now regardless the timeout.
701 : }
702 :
703 : static_assert(PR_INTERVAL_NO_TIMEOUT != 0,
704 : "PR_INTERVAL_NO_TIMEOUT must be non-zero");
705 :
706 5 : if (!mDirtyEpoch) {
707 5 : return PR_INTERVAL_NO_TIMEOUT; // No pending task...
708 : }
709 :
710 0 : static const PRIntervalTime kMaxAge = PR_MillisecondsToInterval(FLUSHING_INTERVAL_MS);
711 :
712 0 : PRIntervalTime now = PR_IntervalNow() | 1;
713 0 : PRIntervalTime age = now - mDirtyEpoch;
714 0 : if (age > kMaxAge) {
715 0 : return 0; // It is time.
716 : }
717 :
718 0 : return kMaxAge - age; // Time left, this is used to sleep the monitor
719 : }
720 :
721 : void
722 0 : StorageDBThread::NotifyFlushCompletion()
723 : {
724 : #ifdef DOM_STORAGE_TESTS
725 0 : if (!NS_IsMainThread()) {
726 : RefPtr<nsRunnableMethod<StorageDBThread, void, false>> event =
727 0 : NewNonOwningRunnableMethod("dom::StorageDBThread::NotifyFlushCompletion",
728 : this,
729 0 : &StorageDBThread::NotifyFlushCompletion);
730 0 : NS_DispatchToMainThread(event);
731 0 : return;
732 : }
733 :
734 0 : nsCOMPtr<nsIObserverService> obs = mozilla::services::GetObserverService();
735 0 : if (obs) {
736 0 : obs->NotifyObservers(nullptr, "domstorage-test-flushed", nullptr);
737 : }
738 : #endif
739 : }
740 :
741 : // Helper SQL function classes
742 :
743 : namespace {
744 :
745 : class OriginAttrsPatternMatchSQLFunction final : public mozIStorageFunction
746 : {
747 : NS_DECL_ISUPPORTS
748 : NS_DECL_MOZISTORAGEFUNCTION
749 :
750 0 : explicit OriginAttrsPatternMatchSQLFunction(OriginAttributesPattern const& aPattern)
751 0 : : mPattern(aPattern) {}
752 :
753 : private:
754 : OriginAttrsPatternMatchSQLFunction() = delete;
755 0 : ~OriginAttrsPatternMatchSQLFunction() {}
756 :
757 : OriginAttributesPattern mPattern;
758 : };
759 :
760 0 : NS_IMPL_ISUPPORTS(OriginAttrsPatternMatchSQLFunction, mozIStorageFunction)
761 :
762 : NS_IMETHODIMP
763 0 : OriginAttrsPatternMatchSQLFunction::OnFunctionCall(
764 : mozIStorageValueArray* aFunctionArguments, nsIVariant** aResult)
765 : {
766 : nsresult rv;
767 :
768 0 : nsAutoCString suffix;
769 0 : rv = aFunctionArguments->GetUTF8String(0, suffix);
770 0 : NS_ENSURE_SUCCESS(rv, rv);
771 :
772 0 : OriginAttributes oa;
773 0 : bool success = oa.PopulateFromSuffix(suffix);
774 0 : NS_ENSURE_TRUE(success, NS_ERROR_FAILURE);
775 0 : bool result = mPattern.Matches(oa);
776 :
777 0 : RefPtr<nsVariant> outVar(new nsVariant());
778 0 : rv = outVar->SetAsBool(result);
779 0 : NS_ENSURE_SUCCESS(rv, rv);
780 :
781 0 : outVar.forget(aResult);
782 0 : return NS_OK;
783 : }
784 :
785 : } // namespace
786 :
787 : // StorageDBThread::DBOperation
788 :
789 2 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
790 : LocalStorageCacheBridge* aCache,
791 : const nsAString& aKey,
792 2 : const nsAString& aValue)
793 : : mType(aType)
794 : , mCache(aCache)
795 : , mKey(aKey)
796 2 : , mValue(aValue)
797 : {
798 2 : MOZ_ASSERT(mType == opPreload ||
799 : mType == opPreloadUrgent ||
800 : mType == opAddItem ||
801 : mType == opUpdateItem ||
802 : mType == opRemoveItem ||
803 : mType == opClear ||
804 : mType == opClearAll);
805 2 : MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
806 2 : }
807 :
808 1 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
809 1 : StorageUsageBridge* aUsage)
810 : : mType(aType)
811 1 : , mUsage(aUsage)
812 : {
813 1 : MOZ_ASSERT(mType == opGetUsage);
814 1 : MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
815 1 : }
816 :
817 0 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
818 0 : const nsACString& aOriginNoSuffix)
819 : : mType(aType)
820 : , mCache(nullptr)
821 0 : , mOrigin(aOriginNoSuffix)
822 : {
823 0 : MOZ_ASSERT(mType == opClearMatchingOrigin);
824 0 : MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
825 0 : }
826 :
827 0 : StorageDBThread::DBOperation::DBOperation(const OperationType aType,
828 0 : const OriginAttributesPattern& aOriginNoSuffix)
829 : : mType(aType)
830 : , mCache(nullptr)
831 0 : , mOriginPattern(aOriginNoSuffix)
832 : {
833 0 : MOZ_ASSERT(mType == opClearMatchingOriginAttributes);
834 0 : MOZ_COUNT_CTOR(StorageDBThread::DBOperation);
835 0 : }
836 :
837 6 : StorageDBThread::DBOperation::~DBOperation()
838 : {
839 3 : MOZ_COUNT_DTOR(StorageDBThread::DBOperation);
840 3 : }
841 :
842 : const nsCString
843 4 : StorageDBThread::DBOperation::OriginNoSuffix() const
844 : {
845 4 : if (mCache) {
846 4 : return mCache->OriginNoSuffix();
847 : }
848 :
849 0 : return EmptyCString();
850 : }
851 :
852 : const nsCString
853 4 : StorageDBThread::DBOperation::OriginSuffix() const
854 : {
855 4 : if (mCache) {
856 4 : return mCache->OriginSuffix();
857 : }
858 :
859 0 : return EmptyCString();
860 : }
861 :
862 : const nsCString
863 0 : StorageDBThread::DBOperation::Origin() const
864 : {
865 0 : if (mCache) {
866 0 : return mCache->Origin();
867 : }
868 :
869 0 : return mOrigin;
870 : }
871 :
872 : const nsCString
873 0 : StorageDBThread::DBOperation::Target() const
874 : {
875 0 : switch (mType) {
876 : case opAddItem:
877 : case opUpdateItem:
878 : case opRemoveItem:
879 0 : return Origin() + NS_LITERAL_CSTRING("|") + NS_ConvertUTF16toUTF8(mKey);
880 :
881 : default:
882 0 : return Origin();
883 : }
884 : }
885 :
886 : void
887 3 : StorageDBThread::DBOperation::PerformAndFinalize(StorageDBThread* aThread)
888 : {
889 3 : Finalize(Perform(aThread));
890 3 : }
891 :
892 : nsresult
893 3 : StorageDBThread::DBOperation::Perform(StorageDBThread* aThread)
894 : {
895 : nsresult rv;
896 :
897 3 : switch (mType) {
898 : case opPreload:
899 : case opPreloadUrgent:
900 : {
901 : // Already loaded?
902 2 : if (mCache->Loaded()) {
903 2 : break;
904 : }
905 :
906 : StatementCache* statements;
907 2 : if (MOZ_UNLIKELY(NS_IsMainThread())) {
908 0 : statements = &aThread->mReaderStatements;
909 : } else {
910 2 : statements = &aThread->mWorkerStatements;
911 : }
912 :
913 : // OFFSET is an optimization when we have to do a sync load
914 : // and cache has already loaded some parts asynchronously.
915 : // It skips keys we have already loaded.
916 4 : nsCOMPtr<mozIStorageStatement> stmt = statements->GetCachedStatement(
917 : "SELECT key, value FROM webappsstore2 "
918 : "WHERE originAttributes = :originAttributes AND originKey = :originKey "
919 2 : "ORDER BY key LIMIT -1 OFFSET :offset");
920 2 : NS_ENSURE_STATE(stmt);
921 2 : mozStorageStatementScoper scope(stmt);
922 :
923 8 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
924 6 : mCache->OriginSuffix());
925 2 : NS_ENSURE_SUCCESS(rv, rv);
926 :
927 8 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
928 6 : mCache->OriginNoSuffix());
929 2 : NS_ENSURE_SUCCESS(rv, rv);
930 :
931 8 : rv = stmt->BindInt32ByName(NS_LITERAL_CSTRING("offset"),
932 6 : static_cast<int32_t>(mCache->LoadedCount()));
933 2 : NS_ENSURE_SUCCESS(rv, rv);
934 :
935 : bool exists;
936 2 : while (NS_SUCCEEDED(rv = stmt->ExecuteStep(&exists)) && exists) {
937 0 : nsAutoString key;
938 0 : rv = stmt->GetString(0, key);
939 0 : NS_ENSURE_SUCCESS(rv, rv);
940 :
941 0 : nsAutoString value;
942 0 : rv = stmt->GetString(1, value);
943 0 : NS_ENSURE_SUCCESS(rv, rv);
944 :
945 0 : if (!mCache->LoadItem(key, value)) {
946 0 : break;
947 : }
948 : }
949 :
950 2 : mCache->LoadDone(NS_OK);
951 2 : break;
952 : }
953 :
954 : case opGetUsage:
955 : {
956 2 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
957 : "SELECT SUM(LENGTH(key) + LENGTH(value)) FROM webappsstore2 "
958 : "WHERE (originAttributes || ':' || originKey) LIKE :usageOrigin"
959 1 : );
960 1 : NS_ENSURE_STATE(stmt);
961 :
962 1 : mozStorageStatementScoper scope(stmt);
963 :
964 4 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("usageOrigin"),
965 3 : mUsage->OriginScope());
966 1 : NS_ENSURE_SUCCESS(rv, rv);
967 :
968 : bool exists;
969 1 : rv = stmt->ExecuteStep(&exists);
970 1 : NS_ENSURE_SUCCESS(rv, rv);
971 :
972 1 : int64_t usage = 0;
973 1 : if (exists) {
974 1 : rv = stmt->GetInt64(0, &usage);
975 1 : NS_ENSURE_SUCCESS(rv, rv);
976 : }
977 :
978 1 : mUsage->LoadUsage(usage);
979 1 : break;
980 : }
981 :
982 : case opAddItem:
983 : case opUpdateItem:
984 : {
985 0 : MOZ_ASSERT(!NS_IsMainThread());
986 :
987 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
988 : "INSERT OR REPLACE INTO webappsstore2 (originAttributes, originKey, scope, key, value) "
989 : "VALUES (:originAttributes, :originKey, :scope, :key, :value) "
990 0 : );
991 0 : NS_ENSURE_STATE(stmt);
992 :
993 0 : mozStorageStatementScoper scope(stmt);
994 :
995 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
996 0 : mCache->OriginSuffix());
997 0 : NS_ENSURE_SUCCESS(rv, rv);
998 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
999 0 : mCache->OriginNoSuffix());
1000 0 : NS_ENSURE_SUCCESS(rv, rv);
1001 : // Filling the 'scope' column just for downgrade compatibility reasons
1002 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
1003 0 : Scheme0Scope(mCache));
1004 0 : NS_ENSURE_SUCCESS(rv, rv);
1005 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
1006 0 : mKey);
1007 0 : NS_ENSURE_SUCCESS(rv, rv);
1008 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("value"),
1009 0 : mValue);
1010 0 : NS_ENSURE_SUCCESS(rv, rv);
1011 :
1012 0 : rv = stmt->Execute();
1013 0 : NS_ENSURE_SUCCESS(rv, rv);
1014 :
1015 0 : MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1016 0 : aThread->mOriginsHavingData.PutEntry(Origin());
1017 0 : break;
1018 : }
1019 :
1020 : case opRemoveItem:
1021 : {
1022 0 : MOZ_ASSERT(!NS_IsMainThread());
1023 :
1024 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1025 : "DELETE FROM webappsstore2 "
1026 : "WHERE originAttributes = :originAttributes AND originKey = :originKey "
1027 : "AND key = :key "
1028 0 : );
1029 0 : NS_ENSURE_STATE(stmt);
1030 0 : mozStorageStatementScoper scope(stmt);
1031 :
1032 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1033 0 : mCache->OriginSuffix());
1034 0 : NS_ENSURE_SUCCESS(rv, rv);
1035 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1036 0 : mCache->OriginNoSuffix());
1037 0 : NS_ENSURE_SUCCESS(rv, rv);
1038 0 : rv = stmt->BindStringByName(NS_LITERAL_CSTRING("key"),
1039 0 : mKey);
1040 0 : NS_ENSURE_SUCCESS(rv, rv);
1041 :
1042 0 : rv = stmt->Execute();
1043 0 : NS_ENSURE_SUCCESS(rv, rv);
1044 :
1045 0 : break;
1046 : }
1047 :
1048 : case opClear:
1049 : {
1050 0 : MOZ_ASSERT(!NS_IsMainThread());
1051 :
1052 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1053 : "DELETE FROM webappsstore2 "
1054 : "WHERE originAttributes = :originAttributes AND originKey = :originKey"
1055 0 : );
1056 0 : NS_ENSURE_STATE(stmt);
1057 0 : mozStorageStatementScoper scope(stmt);
1058 :
1059 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originAttributes"),
1060 0 : mCache->OriginSuffix());
1061 0 : NS_ENSURE_SUCCESS(rv, rv);
1062 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("originKey"),
1063 0 : mCache->OriginNoSuffix());
1064 0 : NS_ENSURE_SUCCESS(rv, rv);
1065 :
1066 0 : rv = stmt->Execute();
1067 0 : NS_ENSURE_SUCCESS(rv, rv);
1068 :
1069 0 : MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1070 0 : aThread->mOriginsHavingData.RemoveEntry(Origin());
1071 0 : break;
1072 : }
1073 :
1074 : case opClearAll:
1075 : {
1076 0 : MOZ_ASSERT(!NS_IsMainThread());
1077 :
1078 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1079 : "DELETE FROM webappsstore2"
1080 0 : );
1081 0 : NS_ENSURE_STATE(stmt);
1082 0 : mozStorageStatementScoper scope(stmt);
1083 :
1084 0 : rv = stmt->Execute();
1085 0 : NS_ENSURE_SUCCESS(rv, rv);
1086 :
1087 0 : MonitorAutoLock monitor(aThread->mThreadObserver->GetMonitor());
1088 0 : aThread->mOriginsHavingData.Clear();
1089 0 : break;
1090 : }
1091 :
1092 : case opClearMatchingOrigin:
1093 : {
1094 0 : MOZ_ASSERT(!NS_IsMainThread());
1095 :
1096 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1097 : "DELETE FROM webappsstore2"
1098 : " WHERE originKey GLOB :scope"
1099 0 : );
1100 0 : NS_ENSURE_STATE(stmt);
1101 0 : mozStorageStatementScoper scope(stmt);
1102 :
1103 0 : rv = stmt->BindUTF8StringByName(NS_LITERAL_CSTRING("scope"),
1104 0 : mOrigin + NS_LITERAL_CSTRING("*"));
1105 0 : NS_ENSURE_SUCCESS(rv, rv);
1106 :
1107 0 : rv = stmt->Execute();
1108 0 : NS_ENSURE_SUCCESS(rv, rv);
1109 :
1110 : // No need to selectively clear mOriginsHavingData here. That hashtable
1111 : // only prevents preload for scopes with no data. Leaving a false record in
1112 : // it has a negligible effect on performance.
1113 0 : break;
1114 : }
1115 :
1116 : case opClearMatchingOriginAttributes:
1117 : {
1118 0 : MOZ_ASSERT(!NS_IsMainThread());
1119 :
1120 : // Register the ORIGIN_ATTRS_PATTERN_MATCH function, initialized with the
1121 : // pattern
1122 : nsCOMPtr<mozIStorageFunction> patternMatchFunction(
1123 0 : new OriginAttrsPatternMatchSQLFunction(mOriginPattern));
1124 :
1125 0 : rv = aThread->mWorkerConnection->CreateFunction(
1126 0 : NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"), 1, patternMatchFunction);
1127 0 : NS_ENSURE_SUCCESS(rv, rv);
1128 :
1129 0 : nsCOMPtr<mozIStorageStatement> stmt = aThread->mWorkerStatements.GetCachedStatement(
1130 : "DELETE FROM webappsstore2"
1131 : " WHERE ORIGIN_ATTRS_PATTERN_MATCH(originAttributes)"
1132 0 : );
1133 :
1134 0 : if (stmt) {
1135 0 : mozStorageStatementScoper scope(stmt);
1136 0 : rv = stmt->Execute();
1137 : } else {
1138 0 : rv = NS_ERROR_UNEXPECTED;
1139 : }
1140 :
1141 : // Always remove the function
1142 0 : aThread->mWorkerConnection->RemoveFunction(
1143 0 : NS_LITERAL_CSTRING("ORIGIN_ATTRS_PATTERN_MATCH"));
1144 :
1145 0 : NS_ENSURE_SUCCESS(rv, rv);
1146 :
1147 : // No need to selectively clear mOriginsHavingData here. That hashtable
1148 : // only prevents preload for scopes with no data. Leaving a false record in
1149 : // it has a negligible effect on performance.
1150 0 : break;
1151 : }
1152 :
1153 : default:
1154 0 : NS_ERROR("Unknown task type");
1155 0 : break;
1156 : }
1157 :
1158 3 : return NS_OK;
1159 : }
1160 :
1161 : void
1162 3 : StorageDBThread::DBOperation::Finalize(nsresult aRv)
1163 : {
1164 3 : switch (mType) {
1165 : case opPreloadUrgent:
1166 : case opPreload:
1167 2 : if (NS_FAILED(aRv)) {
1168 : // When we are here, something failed when loading from the database.
1169 : // Notify that the storage is loaded to prevent deadlock of the main
1170 : // thread, even though it is actually empty or incomplete.
1171 0 : NS_WARNING("Failed to preload localStorage");
1172 : }
1173 :
1174 2 : mCache->LoadDone(aRv);
1175 2 : break;
1176 :
1177 : case opGetUsage:
1178 1 : if (NS_FAILED(aRv)) {
1179 0 : mUsage->LoadUsage(0);
1180 : }
1181 :
1182 1 : break;
1183 :
1184 : default:
1185 0 : if (NS_FAILED(aRv)) {
1186 : NS_WARNING("localStorage update/clear operation failed,"
1187 0 : " data may not persist or clean up");
1188 : }
1189 :
1190 0 : break;
1191 : }
1192 3 : }
1193 :
1194 : // StorageDBThread::PendingOperations
1195 :
1196 1 : StorageDBThread::PendingOperations::PendingOperations()
1197 1 : : mFlushFailureCount(0)
1198 : {
1199 1 : }
1200 :
1201 : bool
1202 0 : StorageDBThread::PendingOperations::HasTasks() const
1203 : {
1204 0 : return !!mUpdates.Count() || !!mClears.Count();
1205 : }
1206 :
1207 : namespace {
1208 :
1209 0 : bool OriginPatternMatches(const nsACString& aOriginSuffix,
1210 : const OriginAttributesPattern& aPattern)
1211 : {
1212 0 : OriginAttributes oa;
1213 0 : DebugOnly<bool> rv = oa.PopulateFromSuffix(aOriginSuffix);
1214 0 : MOZ_ASSERT(rv);
1215 0 : return aPattern.Matches(oa);
1216 : }
1217 :
1218 : } // namespace
1219 :
1220 : bool
1221 0 : StorageDBThread::PendingOperations::CheckForCoalesceOpportunity(DBOperation* aNewOp,
1222 : DBOperation::OperationType aPendingType,
1223 : DBOperation::OperationType aNewType)
1224 : {
1225 0 : if (aNewOp->Type() != aNewType) {
1226 0 : return false;
1227 : }
1228 :
1229 : StorageDBThread::DBOperation* pendingTask;
1230 0 : if (!mUpdates.Get(aNewOp->Target(), &pendingTask)) {
1231 0 : return false;
1232 : }
1233 :
1234 0 : if (pendingTask->Type() != aPendingType) {
1235 0 : return false;
1236 : }
1237 :
1238 0 : return true;
1239 : }
1240 :
1241 : void
1242 0 : StorageDBThread::PendingOperations::Add(StorageDBThread::DBOperation* aOperation)
1243 : {
1244 : // Optimize: when a key to remove has never been written to disk
1245 : // just bypass this operation. A key is new when an operation scheduled
1246 : // to write it to the database is of type opAddItem.
1247 0 : if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
1248 : DBOperation::opRemoveItem)) {
1249 0 : mUpdates.Remove(aOperation->Target());
1250 0 : delete aOperation;
1251 0 : return;
1252 : }
1253 :
1254 : // Optimize: when changing a key that is new and has never been
1255 : // written to disk, keep type of the operation to store it at opAddItem.
1256 : // This allows optimization to just forget adding a new key when
1257 : // it is removed from the storage before flush.
1258 0 : if (CheckForCoalesceOpportunity(aOperation, DBOperation::opAddItem,
1259 : DBOperation::opUpdateItem)) {
1260 0 : aOperation->mType = DBOperation::opAddItem;
1261 : }
1262 :
1263 : // Optimize: to prevent lose of remove operation on a key when doing
1264 : // remove/set/remove on a previously existing key we have to change
1265 : // opAddItem to opUpdateItem on the new operation when there is opRemoveItem
1266 : // pending for the key.
1267 0 : if (CheckForCoalesceOpportunity(aOperation, DBOperation::opRemoveItem,
1268 : DBOperation::opAddItem)) {
1269 0 : aOperation->mType = DBOperation::opUpdateItem;
1270 : }
1271 :
1272 0 : switch (aOperation->Type())
1273 : {
1274 : // Operations on single keys
1275 :
1276 : case DBOperation::opAddItem:
1277 : case DBOperation::opUpdateItem:
1278 : case DBOperation::opRemoveItem:
1279 : // Override any existing operation for the target (=scope+key).
1280 0 : mUpdates.Put(aOperation->Target(), aOperation);
1281 0 : break;
1282 :
1283 : // Clear operations
1284 :
1285 : case DBOperation::opClear:
1286 : case DBOperation::opClearMatchingOrigin:
1287 : case DBOperation::opClearMatchingOriginAttributes:
1288 : // Drop all update (insert/remove) operations for equivavelent or matching
1289 : // scope. We do this as an optimization as well as a must based on the
1290 : // logic, if we would not delete the update tasks, changes would have been
1291 : // stored to the database after clear operations have been executed.
1292 0 : for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
1293 0 : nsAutoPtr<DBOperation>& pendingTask = iter.Data();
1294 :
1295 0 : if (aOperation->Type() == DBOperation::opClear &&
1296 0 : (pendingTask->OriginNoSuffix() != aOperation->OriginNoSuffix() ||
1297 0 : pendingTask->OriginSuffix() != aOperation->OriginSuffix())) {
1298 0 : continue;
1299 : }
1300 :
1301 0 : if (aOperation->Type() == DBOperation::opClearMatchingOrigin &&
1302 0 : !StringBeginsWith(pendingTask->OriginNoSuffix(), aOperation->Origin())) {
1303 0 : continue;
1304 : }
1305 :
1306 0 : if (aOperation->Type() == DBOperation::opClearMatchingOriginAttributes &&
1307 0 : !OriginPatternMatches(pendingTask->OriginSuffix(), aOperation->OriginPattern())) {
1308 0 : continue;
1309 : }
1310 :
1311 0 : iter.Remove();
1312 : }
1313 :
1314 0 : mClears.Put(aOperation->Target(), aOperation);
1315 0 : break;
1316 :
1317 : case DBOperation::opClearAll:
1318 : // Drop simply everything, this is a super-operation.
1319 0 : mUpdates.Clear();
1320 0 : mClears.Clear();
1321 0 : mClears.Put(aOperation->Target(), aOperation);
1322 0 : break;
1323 :
1324 : default:
1325 0 : MOZ_ASSERT(false);
1326 : break;
1327 : }
1328 : }
1329 :
1330 : bool
1331 0 : StorageDBThread::PendingOperations::Prepare()
1332 : {
1333 : // Called under the lock
1334 :
1335 : // First collect clear operations and then updates, we can
1336 : // do this since whenever a clear operation for a scope is
1337 : // scheduled, we drop all updates matching that scope. So,
1338 : // all scope-related update operations we have here now were
1339 : // scheduled after the clear operations.
1340 0 : for (auto iter = mClears.Iter(); !iter.Done(); iter.Next()) {
1341 0 : mExecList.AppendElement(iter.Data().forget());
1342 : }
1343 0 : mClears.Clear();
1344 :
1345 0 : for (auto iter = mUpdates.Iter(); !iter.Done(); iter.Next()) {
1346 0 : mExecList.AppendElement(iter.Data().forget());
1347 : }
1348 0 : mUpdates.Clear();
1349 :
1350 0 : return !!mExecList.Length();
1351 : }
1352 :
1353 : nsresult
1354 0 : StorageDBThread::PendingOperations::Execute(StorageDBThread* aThread)
1355 : {
1356 : // Called outside the lock
1357 :
1358 0 : mozStorageTransaction transaction(aThread->mWorkerConnection, false);
1359 :
1360 : nsresult rv;
1361 :
1362 0 : for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1363 0 : StorageDBThread::DBOperation* task = mExecList[i];
1364 0 : rv = task->Perform(aThread);
1365 0 : if (NS_FAILED(rv)) {
1366 0 : return rv;
1367 : }
1368 : }
1369 :
1370 0 : rv = transaction.Commit();
1371 0 : if (NS_FAILED(rv)) {
1372 0 : return rv;
1373 : }
1374 :
1375 0 : return NS_OK;
1376 : }
1377 :
1378 : bool
1379 0 : StorageDBThread::PendingOperations::Finalize(nsresult aRv)
1380 : {
1381 : // Called under the lock
1382 :
1383 : // The list is kept on a failure to retry it
1384 0 : if (NS_FAILED(aRv)) {
1385 : // XXX Followup: we may try to reopen the database and flush these
1386 : // pending tasks, however testing showed that even though I/O is actually
1387 : // broken some amount of operations is left in sqlite+system buffers and
1388 : // seems like successfully flushed to disk.
1389 : // Tested by removing a flash card and disconnecting from network while
1390 : // using a network drive on Windows system.
1391 0 : NS_WARNING("Flush operation on localStorage database failed");
1392 :
1393 0 : ++mFlushFailureCount;
1394 :
1395 0 : return mFlushFailureCount >= 5;
1396 : }
1397 :
1398 0 : mFlushFailureCount = 0;
1399 0 : mExecList.Clear();
1400 0 : return true;
1401 : }
1402 :
1403 : namespace {
1404 :
1405 : bool
1406 0 : FindPendingClearForOrigin(const nsACString& aOriginSuffix,
1407 : const nsACString& aOriginNoSuffix,
1408 : StorageDBThread::DBOperation* aPendingOperation)
1409 : {
1410 0 : if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearAll) {
1411 0 : return true;
1412 : }
1413 :
1414 0 : if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClear &&
1415 0 : aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
1416 0 : aOriginSuffix == aPendingOperation->OriginSuffix()) {
1417 0 : return true;
1418 : }
1419 :
1420 0 : if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOrigin &&
1421 0 : StringBeginsWith(aOriginNoSuffix, aPendingOperation->Origin())) {
1422 0 : return true;
1423 : }
1424 :
1425 0 : if (aPendingOperation->Type() == StorageDBThread::DBOperation::opClearMatchingOriginAttributes &&
1426 0 : OriginPatternMatches(aOriginSuffix, aPendingOperation->OriginPattern())) {
1427 0 : return true;
1428 : }
1429 :
1430 0 : return false;
1431 : }
1432 :
1433 : } // namespace
1434 :
1435 : bool
1436 2 : StorageDBThread::PendingOperations::IsOriginClearPending(const nsACString& aOriginSuffix,
1437 : const nsACString& aOriginNoSuffix) const
1438 : {
1439 : // Called under the lock
1440 :
1441 2 : for (auto iter = mClears.ConstIter(); !iter.Done(); iter.Next()) {
1442 0 : if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
1443 0 : return true;
1444 : }
1445 : }
1446 :
1447 2 : for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1448 0 : if (FindPendingClearForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
1449 0 : return true;
1450 : }
1451 : }
1452 :
1453 2 : return false;
1454 : }
1455 :
1456 : namespace {
1457 :
1458 : bool
1459 0 : FindPendingUpdateForOrigin(const nsACString& aOriginSuffix,
1460 : const nsACString& aOriginNoSuffix,
1461 : StorageDBThread::DBOperation* aPendingOperation)
1462 : {
1463 0 : if ((aPendingOperation->Type() == StorageDBThread::DBOperation::opAddItem ||
1464 0 : aPendingOperation->Type() == StorageDBThread::DBOperation::opUpdateItem ||
1465 0 : aPendingOperation->Type() == StorageDBThread::DBOperation::opRemoveItem) &&
1466 0 : aOriginNoSuffix == aPendingOperation->OriginNoSuffix() &&
1467 0 : aOriginSuffix == aPendingOperation->OriginSuffix()) {
1468 0 : return true;
1469 : }
1470 :
1471 0 : return false;
1472 : }
1473 :
1474 : } // namespace
1475 :
1476 : bool
1477 2 : StorageDBThread::PendingOperations::IsOriginUpdatePending(const nsACString& aOriginSuffix,
1478 : const nsACString& aOriginNoSuffix) const
1479 : {
1480 : // Called under the lock
1481 :
1482 2 : for (auto iter = mUpdates.ConstIter(); !iter.Done(); iter.Next()) {
1483 0 : if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, iter.UserData())) {
1484 0 : return true;
1485 : }
1486 : }
1487 :
1488 2 : for (uint32_t i = 0; i < mExecList.Length(); ++i) {
1489 0 : if (FindPendingUpdateForOrigin(aOriginSuffix, aOriginNoSuffix, mExecList[i])) {
1490 0 : return true;
1491 : }
1492 : }
1493 :
1494 2 : return false;
1495 : }
1496 :
1497 : } // namespace dom
1498 : } // namespace mozilla
|