Source code

Revision control

Other Tools

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