Remove the Timer parameters from timer callbacks
[WebKit-https.git] / Source / WebCore / storage / StorageAreaSync.cpp
1 /*
2  * Copyright (C) 2008, 2009, 2010 Apple Inc. All Rights Reserved.
3  *
4  * Redistribution and use in source and binary forms, with or without
5  * modification, are permitted provided that the following conditions
6  * are met:
7  * 1. Redistributions of source code must retain the above copyright
8  *    notice, this list of conditions and the following disclaimer.
9  * 2. Redistributions in binary form must reproduce the above copyright
10  *    notice, this list of conditions and the following disclaimer in the
11  *    documentation and/or other materials provided with the distribution.
12  *
13  * THIS SOFTWARE IS PROVIDED BY APPLE INC. ``AS IS'' AND ANY
14  * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
15  * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
16  * PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL APPLE INC. OR
17  * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
18  * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
19  * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
20  * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY
21  * OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
22  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
23  * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
24  */
25
26 #include "config.h"
27 #include "StorageAreaSync.h"
28
29 #include "EventNames.h"
30 #include "FileSystem.h"
31 #include "HTMLElement.h"
32 #include "SQLiteDatabaseTracker.h"
33 #include "SQLiteFileSystem.h"
34 #include "SQLiteStatement.h"
35 #include "SQLiteTransaction.h"
36 #include "SecurityOrigin.h"
37 #include "StorageAreaImpl.h"
38 #include "StorageSyncManager.h"
39 #include "StorageTracker.h"
40 #include "SuddenTermination.h"
41 #include <wtf/MainThread.h>
42 #include <wtf/text/CString.h>
43
44 namespace WebCore {
45
46 // If the StorageArea undergoes rapid changes, don't sync each change to disk.
47 // Instead, queue up a batch of items to sync and actually do the sync at the following interval.
48 static const double StorageSyncInterval = 1.0;
49
50 // A sane limit on how many items we'll schedule to sync all at once.  This makes it
51 // much harder to starve the rest of LocalStorage and the OS's IO subsystem in general.
52 static const int MaxiumItemsToSync = 100;
53
54 inline StorageAreaSync::StorageAreaSync(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
55     : m_syncTimer(*this, &StorageAreaSync::syncTimerFired)
56     , m_itemsCleared(false)
57     , m_finalSyncScheduled(false)
58     , m_storageArea(storageArea)
59     , m_syncManager(storageSyncManager)
60     , m_databaseIdentifier(databaseIdentifier.isolatedCopy())
61     , m_clearItemsWhileSyncing(false)
62     , m_syncScheduled(false)
63     , m_syncInProgress(false)
64     , m_databaseOpenFailed(false)
65     , m_syncCloseDatabase(false)
66     , m_importComplete(false)
67 {
68     ASSERT(isMainThread());
69     ASSERT(m_storageArea);
70     ASSERT(m_syncManager);
71
72     // FIXME: If it can't import, then the default WebKit behavior should be that of private browsing,
73     // not silently ignoring it. https://bugs.webkit.org/show_bug.cgi?id=25894
74     RefPtr<StorageAreaSync> protector(this);
75     m_syncManager->dispatch([protector] {
76         protector->performImport();
77     });
78 }
79
80 PassRefPtr<StorageAreaSync> StorageAreaSync::create(PassRefPtr<StorageSyncManager> storageSyncManager, PassRefPtr<StorageAreaImpl> storageArea, const String& databaseIdentifier)
81 {
82     RefPtr<StorageAreaSync> area = adoptRef(new StorageAreaSync(storageSyncManager, storageArea, databaseIdentifier));
83
84     return area.release();
85 }
86
87 StorageAreaSync::~StorageAreaSync()
88 {
89     ASSERT(isMainThread());
90     ASSERT(!m_syncTimer.isActive());
91     ASSERT(m_finalSyncScheduled);
92 }
93
94 void StorageAreaSync::scheduleFinalSync()
95 {
96     ASSERT(isMainThread());
97     // FIXME: We do this to avoid races, but it'd be better to make things safe without blocking.
98     blockUntilImportComplete();
99     m_storageArea = 0;  // This is done in blockUntilImportComplete() but this is here as a form of documentation that we must be absolutely sure the ref count cycle is broken.
100
101     if (m_syncTimer.isActive())
102         m_syncTimer.stop();
103     else {
104         // The following is balanced by the call to enableSuddenTermination in the
105         // syncTimerFired function.
106         disableSuddenTermination();
107     }
108     // FIXME: This is synchronous.  We should do it on the background process, but
109     // we should do it safely.
110     m_finalSyncScheduled = true;
111     syncTimerFired();
112
113     RefPtr<StorageAreaSync> protector(this);
114     m_syncManager->dispatch([protector] {
115         protector->deleteEmptyDatabase();
116     });
117 }
118
119 void StorageAreaSync::scheduleItemForSync(const String& key, const String& value)
120 {
121     ASSERT(isMainThread());
122     ASSERT(!m_finalSyncScheduled);
123
124     m_changedItems.set(key, value);
125     if (!m_syncTimer.isActive()) {
126         m_syncTimer.startOneShot(StorageSyncInterval);
127
128         // The following is balanced by the call to enableSuddenTermination in the
129         // syncTimerFired function.
130         disableSuddenTermination();
131     }
132 }
133
134 void StorageAreaSync::scheduleClear()
135 {
136     ASSERT(isMainThread());
137     ASSERT(!m_finalSyncScheduled);
138
139     m_changedItems.clear();
140     m_itemsCleared = true;
141     if (!m_syncTimer.isActive()) {
142         m_syncTimer.startOneShot(StorageSyncInterval);
143
144         // The following is balanced by the call to enableSuddenTermination in the
145         // syncTimerFired function.
146         disableSuddenTermination();
147     }
148 }
149
150 void StorageAreaSync::scheduleCloseDatabase()
151 {
152     ASSERT(isMainThread());
153     ASSERT(!m_finalSyncScheduled);
154
155     if (!m_database.isOpen())
156         return;
157
158     m_syncCloseDatabase = true;
159     
160     if (!m_syncTimer.isActive()) {
161         m_syncTimer.startOneShot(StorageSyncInterval);
162         
163         // The following is balanced by the call to enableSuddenTermination in the
164         // syncTimerFired function.
165         disableSuddenTermination();
166     }
167 }
168
169 void StorageAreaSync::syncTimerFired()
170 {
171     ASSERT(isMainThread());
172
173     bool partialSync = false;
174     {
175         MutexLocker locker(m_syncLock);
176
177         // Do not schedule another sync if we're still trying to complete the
178         // previous one.  But, if we're shutting down, schedule it anyway.
179         if (m_syncInProgress && !m_finalSyncScheduled) {
180             ASSERT(!m_syncTimer.isActive());
181             m_syncTimer.startOneShot(StorageSyncInterval);
182             return;
183         }
184
185         if (m_itemsCleared) {
186             m_itemsPendingSync.clear();
187             m_clearItemsWhileSyncing = true;
188             m_itemsCleared = false;
189         }
190
191         HashMap<String, String>::iterator changed_it = m_changedItems.begin();
192         HashMap<String, String>::iterator changed_end = m_changedItems.end();
193         for (int count = 0; changed_it != changed_end; ++count, ++changed_it) {
194             if (count >= MaxiumItemsToSync && !m_finalSyncScheduled) {
195                 partialSync = true;
196                 break;
197             }
198             m_itemsPendingSync.set(changed_it->key.isolatedCopy(), changed_it->value.isolatedCopy());
199         }
200
201         if (partialSync) {
202             // We can't do the fast path of simply clearing all items, so we'll need to manually
203             // remove them one by one.  Done under lock since m_itemsPendingSync is modified by
204             // the background thread.
205             HashMap<String, String>::iterator pending_it = m_itemsPendingSync.begin();
206             HashMap<String, String>::iterator pending_end = m_itemsPendingSync.end();
207             for (; pending_it != pending_end; ++pending_it)
208                 m_changedItems.remove(pending_it->key);
209         }
210
211         if (!m_syncScheduled) {
212             m_syncScheduled = true;
213
214             // The following is balanced by the call to enableSuddenTermination in the
215             // performSync function.
216             disableSuddenTermination();
217
218             RefPtr<StorageAreaSync> protector(this);
219             m_syncManager->dispatch([protector] {
220                 protector->performSync();
221             });
222         }
223     }
224
225     if (partialSync) {
226         // If we didn't finish syncing, then we need to finish the job later.
227         ASSERT(!m_syncTimer.isActive());
228         m_syncTimer.startOneShot(StorageSyncInterval);
229     } else {
230         // The following is balanced by the calls to disableSuddenTermination in the
231         // scheduleItemForSync, scheduleClear, and scheduleFinalSync functions.
232         enableSuddenTermination();
233
234         m_changedItems.clear();
235     }
236 }
237
238 void StorageAreaSync::openDatabase(OpenDatabaseParamType openingStrategy)
239 {
240     ASSERT(!isMainThread());
241     ASSERT(!m_database.isOpen());
242     ASSERT(!m_databaseOpenFailed);
243
244     SQLiteTransactionInProgressAutoCounter transactionCounter;
245
246     String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
247
248     if (!fileExists(databaseFilename) && openingStrategy == SkipIfNonExistent)
249         return;
250
251     if (databaseFilename.isEmpty()) {
252         LOG_ERROR("Filename for local storage database is empty - cannot open for persistent storage");
253         markImported();
254         m_databaseOpenFailed = true;
255         return;
256     }
257
258     // A StorageTracker thread may have been scheduled to delete the db we're
259     // reopening, so cancel possible deletion.
260     StorageTracker::tracker().cancelDeletingOrigin(m_databaseIdentifier);
261
262     if (!m_database.open(databaseFilename)) {
263         LOG_ERROR("Failed to open database file %s for local storage", databaseFilename.utf8().data());
264         markImported();
265         m_databaseOpenFailed = true;
266         return;
267     }
268
269     migrateItemTableIfNeeded();
270
271     if (!m_database.executeCommand("CREATE TABLE IF NOT EXISTS ItemTable (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)")) {
272         LOG_ERROR("Failed to create table ItemTable for local storage");
273         markImported();
274         m_databaseOpenFailed = true;
275         return;
276     }
277
278     StorageTracker::tracker().setOriginDetails(m_databaseIdentifier, databaseFilename);
279 }
280
281 void StorageAreaSync::migrateItemTableIfNeeded()
282 {
283     if (!m_database.tableExists("ItemTable"))
284         return;
285
286     {
287         SQLiteStatement query(m_database, "SELECT value FROM ItemTable LIMIT 1");
288         // this query isn't ever executed.
289         if (query.isColumnDeclaredAsBlob(0))
290             return;
291     }
292
293     // alter table for backward compliance, change the value type from TEXT to BLOB.
294     static const char* commands[] = {
295         "DROP TABLE IF EXISTS ItemTable2",
296         "CREATE TABLE ItemTable2 (key TEXT UNIQUE ON CONFLICT REPLACE, value BLOB NOT NULL ON CONFLICT FAIL)",
297         "INSERT INTO ItemTable2 SELECT * from ItemTable",
298         "DROP TABLE ItemTable",
299         "ALTER TABLE ItemTable2 RENAME TO ItemTable",
300         0,
301     };
302
303     SQLiteTransaction transaction(m_database, false);
304     transaction.begin();
305     for (size_t i = 0; commands[i]; ++i) {
306         if (!m_database.executeCommand(commands[i])) {
307             LOG_ERROR("Failed to migrate table ItemTable for local storage when executing: %s", commands[i]);
308             transaction.rollback();
309
310             // finally it will try to keep a backup of ItemTable for the future restoration.
311             // NOTICE: this will essentially DELETE the current database, but that's better
312             // than continually hitting this case and never being able to use the local storage.
313             // if this is ever hit, it's definitely a bug.
314             ASSERT_NOT_REACHED();
315             if (!m_database.executeCommand("ALTER TABLE ItemTable RENAME TO Backup_ItemTable"))
316                 LOG_ERROR("Failed to save ItemTable after migration job failed.");
317
318             return;
319         }
320     }
321     transaction.commit();
322 }
323
324 void StorageAreaSync::performImport()
325 {
326     ASSERT(!isMainThread());
327     ASSERT(!m_database.isOpen());
328
329     openDatabase(SkipIfNonExistent);
330     if (!m_database.isOpen()) {
331         markImported();
332         return;
333     }
334
335     SQLiteStatement query(m_database, "SELECT key, value FROM ItemTable");
336     if (query.prepare() != SQLResultOk) {
337         LOG_ERROR("Unable to select items from ItemTable for local storage");
338         markImported();
339         return;
340     }
341
342     HashMap<String, String> itemMap;
343
344     int result = query.step();
345     while (result == SQLResultRow) {
346         itemMap.set(query.getColumnText(0), query.getColumnBlobAsString(1));
347         result = query.step();
348     }
349
350     if (result != SQLResultDone) {
351         LOG_ERROR("Error reading items from ItemTable for local storage");
352         markImported();
353         return;
354     }
355
356     m_storageArea->importItems(itemMap);
357
358     markImported();
359 }
360
361 void StorageAreaSync::markImported()
362 {
363     MutexLocker locker(m_importLock);
364     m_importComplete = true;
365     m_importCondition.signal();
366 }
367
368 // FIXME: In the future, we should allow use of StorageAreas while it's importing (when safe to do so).
369 // Blocking everything until the import is complete is by far the simplest and safest thing to do, but
370 // there is certainly room for safe optimization: Key/length will never be able to make use of such an
371 // optimization (since the order of iteration can change as items are being added). Get can return any
372 // item currently in the map. Get/remove can work whether or not it's in the map, but we'll need a list
373 // of items the import should not overwrite. Clear can also work, but it'll need to kill the import
374 // job first.
375 void StorageAreaSync::blockUntilImportComplete()
376 {
377     ASSERT(isMainThread());
378
379     // Fast path.  We set m_storageArea to 0 only after m_importComplete being true.
380     if (!m_storageArea)
381         return;
382
383     MutexLocker locker(m_importLock);
384     while (!m_importComplete)
385         m_importCondition.wait(m_importLock);
386     m_storageArea = 0;
387 }
388
389 void StorageAreaSync::sync(bool clearItems, const HashMap<String, String>& items)
390 {
391     ASSERT(!isMainThread());
392
393     if (items.isEmpty() && !clearItems && !m_syncCloseDatabase)
394         return;
395     if (m_databaseOpenFailed)
396         return;
397
398     if (!m_database.isOpen() && m_syncCloseDatabase) {
399         m_syncCloseDatabase = false;
400         return;
401     }
402
403     if (!m_database.isOpen())
404         openDatabase(CreateIfNonExistent);
405     if (!m_database.isOpen())
406         return;
407
408     // Closing this db because it is about to be deleted by StorageTracker.
409     // The delete will be cancelled if StorageAreaSync needs to reopen the db
410     // to write new items created after the request to delete the db.
411     if (m_syncCloseDatabase) {
412         m_syncCloseDatabase = false;
413         m_database.close();
414         return;
415     }
416     
417     SQLiteTransactionInProgressAutoCounter transactionCounter;
418
419     // If the clear flag is set, then we clear all items out before we write any new ones in.
420     if (clearItems) {
421         SQLiteStatement clear(m_database, "DELETE FROM ItemTable");
422         if (clear.prepare() != SQLResultOk) {
423             LOG_ERROR("Failed to prepare clear statement - cannot write to local storage database");
424             return;
425         }
426
427         int result = clear.step();
428         if (result != SQLResultDone) {
429             LOG_ERROR("Failed to clear all items in the local storage database - %i", result);
430             return;
431         }
432     }
433
434     SQLiteStatement insert(m_database, "INSERT INTO ItemTable VALUES (?, ?)");
435     if (insert.prepare() != SQLResultOk) {
436         LOG_ERROR("Failed to prepare insert statement - cannot write to local storage database");
437         return;
438     }
439
440     SQLiteStatement remove(m_database, "DELETE FROM ItemTable WHERE key=?");
441     if (remove.prepare() != SQLResultOk) {
442         LOG_ERROR("Failed to prepare delete statement - cannot write to local storage database");
443         return;
444     }
445
446     HashMap<String, String>::const_iterator end = items.end();
447
448     SQLiteTransaction transaction(m_database);
449     transaction.begin();
450     for (HashMap<String, String>::const_iterator it = items.begin(); it != end; ++it) {
451         // Based on the null-ness of the second argument, decide whether this is an insert or a delete.
452         SQLiteStatement& query = it->value.isNull() ? remove : insert;
453
454         query.bindText(1, it->key);
455
456         // If the second argument is non-null, we're doing an insert, so bind it as the value.
457         if (!it->value.isNull())
458             query.bindBlob(2, it->value);
459
460         int result = query.step();
461         if (result != SQLResultDone) {
462             LOG_ERROR("Failed to update item in the local storage database - %i", result);
463             break;
464         }
465
466         query.reset();
467     }
468     transaction.commit();
469 }
470
471 void StorageAreaSync::performSync()
472 {
473     ASSERT(!isMainThread());
474
475     bool clearItems;
476     HashMap<String, String> items;
477     {
478         MutexLocker locker(m_syncLock);
479
480         ASSERT(m_syncScheduled);
481
482         clearItems = m_clearItemsWhileSyncing;
483         m_itemsPendingSync.swap(items);
484
485         m_clearItemsWhileSyncing = false;
486         m_syncScheduled = false;
487         m_syncInProgress = true;
488     }
489
490     sync(clearItems, items);
491
492     {
493         MutexLocker locker(m_syncLock);
494         m_syncInProgress = false;
495     }
496
497     // The following is balanced by the call to disableSuddenTermination in the
498     // syncTimerFired function.
499     enableSuddenTermination();
500 }
501
502 void StorageAreaSync::deleteEmptyDatabase()
503 {
504     ASSERT(!isMainThread());
505     if (!m_database.isOpen())
506         return;
507
508     SQLiteStatement query(m_database, "SELECT COUNT(*) FROM ItemTable");
509     if (query.prepare() != SQLResultOk) {
510         LOG_ERROR("Unable to count number of rows in ItemTable for local storage");
511         return;
512     }
513
514     int result = query.step();
515     if (result != SQLResultRow) {
516         LOG_ERROR("No results when counting number of rows in ItemTable for local storage");
517         return;
518     }
519
520     int count = query.getColumnInt(0);
521     if (!count) {
522         query.finalize();
523         m_database.close();
524         if (StorageTracker::tracker().isActive()) {
525             StringImpl* databaseIdentifierCopy = &m_databaseIdentifier.impl()->isolatedCopy().leakRef();
526             callOnMainThread([databaseIdentifierCopy] {
527                 StorageTracker::tracker().deleteOriginWithIdentifier(databaseIdentifierCopy);
528                 databaseIdentifierCopy->deref();
529             });
530         } else {
531             String databaseFilename = m_syncManager->fullDatabaseFilename(m_databaseIdentifier);
532             if (!SQLiteFileSystem::deleteDatabaseFile(databaseFilename))
533                 LOG_ERROR("Failed to delete database file %s\n", databaseFilename.utf8().data());
534         }
535     }
536 }
537
538 void StorageAreaSync::scheduleSync()
539 {
540     syncTimerFired();
541 }
542
543 } // namespace WebCore