Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 80d8343a authored by Lee Shombert's avatar Lee Shombert
Browse files

Make SQLiteOpenHelper thread safe

Concurrent SQLiteOpenHelper instances is a common anti-pattern that
leads to SQLiteDatabase exceptions.  The specific problem occurs when
a database upgrade is triggered automatically inside the open helper.

This creates a lock for every database file managed by a
SQLiteOpenHelper.  The lock guards the code that opens a database and
optionally upgrades it.  This does not protect against code that might
access the database outside of a SQLiteOpenHelper.

Flag: android.database.sqlite.concurrent_open_helper
Bug: 335904370
Test: atest
 * CtsDatabaseTestCases
 * FrameworksCoreTests:android.database
Change-Id: I4d79375c44ca3170495fce6cd9d682cf21e62e2f
parent e302462c
Loading
Loading
Loading
Loading
+85 −55
Original line number Diff line number Diff line
@@ -25,10 +25,15 @@ import android.database.DatabaseErrorHandler;
import android.database.SQLException;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.os.FileUtils;
import android.util.ArrayMap;
import android.util.Log;

import com.android.internal.annotations.GuardedBy;

import java.io.File;
import java.io.IOException;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;

/**
 * A helper class to manage database creation and version management.
@@ -54,6 +59,13 @@ import java.util.Objects;
public abstract class SQLiteOpenHelper implements AutoCloseable {
    private static final String TAG = SQLiteOpenHelper.class.getSimpleName();

    // Every database file has a lock, saved in this map.  The lock is held while the database is
    // opened.
    private static final ConcurrentHashMap<String, Object> sDbLock = new ConcurrentHashMap<>();

    // The lock that this open helper instance must hold when the database is opened.
    private final Object mLock;

    private final Context mContext;
    @UnsupportedAppUsage
    private final String mName;
@@ -168,6 +180,21 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
        mNewVersion = version;
        mMinimumSupportedVersion = Math.max(0, minimumSupportedVersion);
        setOpenParamsBuilder(openParamsBuilder);

        Object lock = null;
        if (mName == null || !Flags.concurrentOpenHelper()) {
            lock = new Object();
        } else {
            try {
                final String path = mContext.getDatabasePath(mName).getCanonicalPath();
                lock = sDbLock.computeIfAbsent(path, (String k) -> new Object());
            } catch (IOException e) {
                Log.d(TAG, "failed to construct db path for " + mName);
                // Ensure the lock is not null.
                lock = new Object();
            }
        }
        mLock = lock;
    }

    /**
@@ -358,6 +385,7 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {

        SQLiteDatabase db = mDatabase;
        try {
            synchronized (mLock) {
                mIsInitializing = true;

                if (db != null) {
@@ -378,7 +406,8 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
                            throw ex;
                        }
                        Log.e(TAG, "Couldn't open database for writing (will try read-only):", ex);
                    params = params.toBuilder().addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                        params = params.toBuilder()
                                 .addOpenFlags(SQLiteDatabase.OPEN_READONLY).build();
                        db = SQLiteDatabase.openDatabase(filePath, params);
                    }
                }
@@ -388,8 +417,8 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
                final int version = db.getVersion();
                if (version != mNewVersion) {
                    if (db.isReadOnly()) {
                    throw new SQLiteException("Can't upgrade read-only database from version " +
                            db.getVersion() + " to " + mNewVersion + ": " + mName);
                        throw new SQLiteException("Can't upgrade read-only database from version "
                                + db.getVersion() + " to " + mNewVersion + ": " + mName);
                    }

                    if (version > 0 && version < mMinimumSupportedVersion) {
@@ -426,6 +455,7 @@ public abstract class SQLiteOpenHelper implements AutoCloseable {
                onOpen(db);
                mDatabase = db;
                return db;
            }
        } finally {
            mIsInitializing = false;
            if (db != null && db != mDatabase) {
+9 −0
Original line number Diff line number Diff line
@@ -17,3 +17,12 @@ flag {
     description: "SQLite APIs held back for Android 15"
     bug: "279043253"
}

flag {
     name: "concurrent_open_helper"
     is_exported: true
     namespace: "system_performance"
     is_fixed_read_only: false
     description: "Make SQLiteOpenHelper thread-safe"
     bug: "335904370"
}