Bug 1006947 - Part 1: enable WAL on the Java side of SQLiteBridge. r=gcp, a=sylvestre
authorRichard Newman <rnewman@mozilla.com>
Fri, 30 May 2014 12:29:04 -0700
changeset 200490 94eb5c1fec367ab04f83c9d60af4b0e19bcaa0c6
parent 200489 ae6cc31bba604036ed77eccbc75952870925b3b7
child 200491 6f239dea3a368ba9dac7326878e665478a30e262
push id486
push userasasaki@mozilla.com
push dateMon, 14 Jul 2014 18:39:42 +0000
treeherdermozilla-release@d33428174ff1 [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewersgcp, sylvestre
bugs1006947
milestone31.0a2
Bug 1006947 - Part 1: enable WAL on the Java side of SQLiteBridge. r=gcp, a=sylvestre
mobile/android/base/sqlite/SQLiteBridge.java
--- a/mobile/android/base/sqlite/SQLiteBridge.java
+++ b/mobile/android/base/sqlite/SQLiteBridge.java
@@ -5,44 +5,52 @@
 package org.mozilla.gecko.sqlite;
 
 import android.content.ContentValues;
 import android.database.Cursor;
 import android.database.sqlite.SQLiteDatabase;
 import android.database.sqlite.SQLiteException;
 import android.text.TextUtils;
 import android.util.Log;
+
 import org.mozilla.gecko.mozglue.RobocopTarget;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Map.Entry;
 
 /*
  * This class allows using the mozsqlite3 library included with Firefox
  * to read SQLite databases, instead of the Android SQLiteDataBase API,
  * which might use whatever outdated DB is present on the Android system.
  */
 public class SQLiteBridge {
     private static final String LOGTAG = "SQLiteBridge";
 
     // Path to the database. If this database was not opened with openDatabase, we reopen it every query.
     private String mDb;
-    // pointer to the database if it was opened with openDatabase
-    protected long mDbPointer = 0;
+
+    // Pointer to the database if it was opened with openDatabase. 0 implies closed.
+    protected volatile long mDbPointer = 0L;
 
     // Values remembered after a query.
     private long[] mQueryResults;
 
     private boolean mTransactionSuccess = false;
     private boolean mInTransaction = false;
 
     private static final int RESULT_INSERT_ROW_ID = 0;
     private static final int RESULT_ROWS_CHANGED = 1;
 
+    // Shamelessly cribbed from db/sqlite3/src/moz.build.
+    private static final int DEFAULT_PAGE_SIZE_BYTES = 32768;
+
+    // The same size we use elsewhere.
+    private static final int MAX_WAL_SIZE_BYTES = 524288;
+
     // JNI code in $(topdir)/mozglue/android/..
     private static native MatrixBlobCursor sqliteCall(String aDb, String aQuery,
                                                       String[] aParams,
                                                       long[] aUpdateResult)
         throws SQLiteBridgeException;
     private static native MatrixBlobCursor sqliteCallWithDb(long aDb, String aQuery,
                                                             String[] aParams,
                                                             long[] aUpdateResult)
@@ -249,31 +257,34 @@ public class SQLiteBridge {
             throw new RuntimeException("flags not supported.");
         }
 
         SQLiteBridge bridge = null;
         try {
             bridge = new SQLiteBridge(path);
             bridge.mDbPointer = SQLiteBridge.openDatabase(path);
         } catch (SQLiteBridgeException ex) {
-            // catch and rethrow as a SQLiteException to match SQLiteDatabase
+            // Catch and rethrow as a SQLiteException to match SQLiteDatabase.
             throw new SQLiteException(ex.getMessage());
         }
+
+        prepareWAL(bridge);
+
         return bridge;
     }
 
     public void close() {
         if (isOpen()) {
           closeDatabase(mDbPointer);
         }
-        mDbPointer = 0;
+        mDbPointer = 0L;
     }
 
     public boolean isOpen() {
-        return mDbPointer > 0;
+        return mDbPointer != 0;
     }
 
     public void beginTransaction() throws SQLiteBridgeException {
         if (inTransaction()) {
             throw new SQLiteBridgeException("Nested transactions are not supported");
         }
         execSQL("BEGIN EXCLUSIVE");
         mTransactionSuccess = false;
@@ -319,9 +330,55 @@ public class SQLiteBridge {
 
     @Override
     public void finalize() {
         if (isOpen()) {
             Log.e(LOGTAG, "Bridge finalized without closing the database");
             close();
         }
     }
+
+    private static void prepareWAL(final SQLiteBridge bridge) {
+        // Prepare for WAL mode. If we can, we switch to journal_mode=WAL, then
+        // set the checkpoint size appropriately. If we can't, then we fall back
+        // to truncating and synchronous writes.
+        final Cursor cursor = bridge.internalQuery("PRAGMA journal_mode=WAL", null);
+        try {
+            if (cursor.moveToFirst()) {
+                String journalMode = cursor.getString(0);
+                Log.d(LOGTAG, "Journal mode: " + journalMode);
+                if ("wal".equals(journalMode)) {
+                    // Success! Let's make sure we autocheckpoint at a reasonable interval.
+                    final int pageSizeBytes = bridge.getPageSizeBytes();
+                    final int checkpointPageCount = MAX_WAL_SIZE_BYTES / pageSizeBytes;
+                    bridge.internalQuery("PRAGMA wal_autocheckpoint=" + checkpointPageCount, null).close();
+                } else {
+                    if (!"truncate".equals(journalMode)) {
+                        Log.w(LOGTAG, "Unable to activate WAL journal mode. Using truncate instead.");
+                        bridge.internalQuery("PRAGMA journal_mode=TRUNCATE", null).close();
+                    }
+                    Log.w(LOGTAG, "Not using WAL mode: using synchronous=FULL instead.");
+                    bridge.internalQuery("PRAGMA synchronous=FULL", null).close();
+                }
+            }
+        } finally {
+            cursor.close();
+        }
+    }
+
+    private int getPageSizeBytes() {
+        if (!isOpen()) {
+            throw new IllegalStateException("Database not open.");
+        }
+
+        final Cursor cursor = internalQuery("PRAGMA page_size", null);
+        try {
+            if (!cursor.moveToFirst()) {
+                Log.w(LOGTAG, "Unable to retrieve page size.");
+                return DEFAULT_PAGE_SIZE_BYTES;
+            }
+
+            return cursor.getInt(0);
+        } finally {
+            cursor.close();
+        }
+    }
 }