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

Commit e95203e0 authored by Hans Boehm's avatar Hans Boehm
Browse files

Make onSaveInstanceState wait for database writes

Bug: 34051652

Add a mechanism to wait for (asynchronous) database writes to
complete.

Invoke it in Calculator.onSaveInstanceState(). This avoids a potential
situation in which the saved instance state bundle includes a reference
to an expression that never made it to the database. This may be more
likely than it appears, since we run all background tasks on a single
thread. A database save operation can get hung up behind a computation.

Note that in almost all cases, the wait operation will return
immediately, as evidenced by the fact that this very rarely
leads to crashes without the wait operation.

Change-Id: I7c2bdcd9bf19f6cb4bd653fb9244bca0f6b25435
parent c3a0b31c
Loading
Loading
Loading
Loading
+3 −0
Original line number Diff line number Diff line
@@ -496,6 +496,9 @@ public class Calculator extends Activity
        outState.putByteArray(KEY_EVAL_STATE, byteArrayStream.toByteArray());
        outState.putBoolean(KEY_INVERSE_MODE, mInverseToggle.isSelected());
        outState.putBoolean(KEY_SHOW_TOOLBAR, mDisplayView.isToolbarVisible());
        // We must wait for asynchronous writes to complete, since outState may contain
        // references to expressions being written.
        mEvaluator.waitForWrites();
    }

    // Set the state, updating delete label and display colors.
+7 −0
Original line number Diff line number Diff line
@@ -1889,6 +1889,13 @@ public class Evaluator implements CalculatorExpr.ExprResolver {
        return sb.toString();
    }

    /**
     * Wait for pending writes to the database to complete.
     */
    public void waitForWrites() {
        mExprDB.waitForWrites();
    }

    /**
     * Destroy the current evaluator, forcing getEvaluator to allocate a new one.
     * This is needed for testing, since Robolectric apparently doesn't let us preserve
+44 −0
Original line number Diff line number Diff line
@@ -394,6 +394,48 @@ public class ExpressionDB {
        eraser.executeOnExecutor(AsyncTask.SERIAL_EXECUTOR);
    }

    // We track the number of outstanding writes to prevent onSaveInstanceState from
    // completing with in-flight database writes.

    private int mIncompleteWrites = 0;
    private Object mWriteCountsLock = new Object();  // Protects the preceding field.

    private void writeCompleted() {
        synchronized(mWriteCountsLock) {
            if (--mIncompleteWrites == 0) {
                mWriteCountsLock.notifyAll();
            }
        }
    }

    private void writeStarted() {
        synchronized(mWriteCountsLock) {
            ++mIncompleteWrites;
        }
    }

    /**
     * Wait for in-flight writes to complete.
     * This is not safe to call from one of our background tasks, since the writing
     * tasks may be waiting for the same underlying thread that we're using, resulting
     * in deadlock.
     */
    public void waitForWrites() {
        synchronized(mWriteCountsLock) {
            boolean caught = false;
            while (mIncompleteWrites != 0) {
                try {
                    mWriteCountsLock.wait();
                } catch (InterruptedException e) {
                    caught = true;
                }
            }
            if (caught) {
                Thread.currentThread().interrupt();
            }
        }
    }

    /**
     * Insert the given row in the database without blocking the UI thread.
     * These tasks must be executed on a serial executor to avoid reordering writes.
@@ -403,6 +445,7 @@ public class ExpressionDB {
        protected Long doInBackground(ContentValues... cvs) {
            long index = cvs[0].getAsLong(ExpressionEntry._ID);
            long result = mExpressionDB.insert(ExpressionEntry.TABLE_NAME, null, cvs[0]);
            writeCompleted();
            // Return 0 on success, row id on failure.
            if (result == -1) {
                return index;
@@ -454,6 +497,7 @@ public class ExpressionDB {
                // to just include values between mMinAccessible and mMaxAccessible.
                return newIndex;
            }
            writeStarted();
            ContentValues cvs = data.toContentValues();
            cvs.put(ExpressionEntry._ID, newIndex);
            AsyncWriter awriter = new AsyncWriter();