diff --git a/app/src/main/java/eu/faircode/email/DB.java b/app/src/main/java/eu/faircode/email/DB.java index 2089537a1a..d860f47fa9 100644 --- a/app/src/main/java/eu/faircode/email/DB.java +++ b/app/src/main/java/eu/faircode/email/DB.java @@ -30,7 +30,9 @@ import org.json.JSONException; import org.json.JSONObject; import java.io.File; +import java.io.FileOutputStream; import java.lang.reflect.Field; +import java.nio.channels.FileLock; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -123,6 +125,7 @@ public abstract class DB extends RoomDatabase { static final String DB_NAME = "fairemail"; static final int DEFAULT_QUERY_THREADS = 4; // AndroidX default thread count: 4 static final int DEFAULT_CACHE_SIZE = 20; // percentage of memory class + private static final long DB_LOCK_TIMEOUT = 60 * 1000L; private static final int DB_JOURNAL_SIZE_LIMIT = 1048576; // requery/sqlite-android default private static final int DB_CHECKPOINT = 1000; // requery/sqlite-android default @@ -425,6 +428,40 @@ public abstract class DB extends RoomDatabase { Log.d("ROOM invalidated=" + TextUtils.join(",", tables)); } }); + + // Ref: https://android-review.googlesource.com/c/platform/frameworks/support/+/1797472 + Log.i("DB critical section start"); + File dbDir = context.getDatabasePath(DB_NAME).getParentFile(); + dbDir.mkdirs(); + File lockFile = new File(dbDir, DB_NAME + ".lock"); + try (FileOutputStream fos = new FileOutputStream(lockFile)) { + ObjectHolder lock = new ObjectHolder<>(null); + try { + Thread thread = new Thread(new Runnable() { + @Override + public void run() { + try { + lock.value = fos.getChannel().lock(); + } catch (Throwable ex) { + Log.e(ex); + } + } + }); + thread.start(); + thread.join(DB_LOCK_TIMEOUT); + if (thread.isAlive()) + throw new IllegalArgumentException("DB critical section failed"); + + // Force migration + sInstance.getOpenHelper().getWritableDatabase(); + } finally { + if (lock.value != null) + lock.value.release(); + } + } catch (Throwable ex) { + Log.e(ex); + } + Log.i("DB critical section end"); } return sInstance;