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