Search code examples
androidtransactionsormliterace-condition

Ormlite TransactionManager synchronization on android works for me but the documentation says otherwise


I am looking to implement a dirty bit on my Ormlite models. During a sync process with the server I need to make sure that I do not overwrite a row that has been edited by the user. For this purpose I need a way to guarantee that no user edit is allowed between the time of the sync thread reading the dirty bit and updating the values.

I am using a singleton DatabaseHelper class:

public class DatabaseHelper extends OrmLiteSqliteOpenHelper {

    private static DatabaseHelper databaseHelper = null;
    private Dao<FormField, String> formFieldDao = null;

    public static DatabaseHelper getHelper() {
        if (databaseHelper == null) {
            databaseHelper =
                    OpenHelperManager.getHelper(MyGoldCare.getAppContext(), DatabaseHelper.class);
        }
        return databaseHelper;
    }
  public Dao<FormField, String> getFormFieldDao() throws java.sql.SQLException {
    if (formFieldDao == null) {
        formFieldDao = getDao(FormField.class);
    }
    return formFieldDao;
}

My first extinct was to do the read of the dirty bit and update of the values within a transaction. I wrote a simple test to see if this would work without race conditions:

public class TestTransactionWriter implements Runnable {

    FormField formField;
    long sleepTime;

    public TestTransactionWriter(FormField formField, long sleepTime){
        this.formField = formField;
        this.sleepTime = sleepTime;
    }
    @Override
    public void run() {
        android.os.Process.setThreadPriority(android.os.Process.THREAD_PRIORITY_BACKGROUND);
        try {
            TransactionManager.callInTransaction(DatabaseHelper.getHelper().getConnectionSource(), new Callable<Void>(){
             public Void call() throws SQLException,InterruptedException{
            for (int i = 0; i < 10; i++) {
                DatabaseHelper.getHelper().getFormFieldDao().refresh(formField);
                Thread.sleep(sleepTime);
                Integer v = Integer.parseInt(formField.getValue());
                v++;
                formField.setValue(v.toString());
                DatabaseHelper.getHelper().getFormFieldDao().update(formField);
            }
            return null;
            }
             });
            }catch (SQLException se){
               Log.e("TEST",se.toString());
            }
    }
}

Then I started 2 threads with the above runnable:

            formField.setValue("0");
            DatabaseHelper.getHelper().getFormFieldDao().update(formField);
            TestTransactionWriter testTransactionWriter = new TestTransactionWriter(formField,1500);
            Thread thread = new Thread(testTransactionWriter) ;
            thread.start();

            TestTransactionWriter testTransactionWriter1 = new TestTransactionWriter(formField,2000);
            Thread thread1 = new Thread(testTransactionWriter) ;
            thread1.start();

        }catch (SQLException se){
            Log.e(LOG_TAG,se.toString());
        }
    }

I got the result I was expecting. The final value was 20 every time I tested. When I remove the call in transaction I get race conditions. Another thing I noticed is that while these threads are running I am unable to read any values(other tables as well) from the database and the UI freezes for a moment.

To confirm my test I decided to read through the source of Ormlite and i found the following warning:

 * WARNING: it is up to you to properly synchronize around this method if multiple threads are using a
 * connection-source which works gives out a single-connection. The reason why this is necessary is that multiple
 * operations are performed on the connection and race-conditions will exist with multiple threads working on the
 * same connection.
 * </p>
 * 
 * @param callable
 *            Callable to execute inside of the transaction.
 * @return The object returned by the callable.
 * @throws SQLException
 *             If the callable threw an exception then the transaction is rolled back and a SQLException wraps the
 *             callable exception and is thrown by this method.
 */
public <T> T callInTransaction(final Callable<T> callable) throws SQLException {
    return callInTransaction(connectionSource, callable);

How is it that in my case the test did not produce race conditions even though I did no synchronization of my own? Was it some SQLlite database lock kicking in? Was there synchronization at some other level given the face I use a singleton database helper?


Solution

  • How is it that in my case the test did not produce race conditions even though I did no synchronization of my own? Was it some SQLlite database lock kicking in?

    No but I suspect that some Sqlite locking kicked in. That WARNING is for all ORMLite backends including JDBC which allow multiple thread connections to the same database.

    For this purpose I need a way to guarantee that no user edit is allowed between the time of the sync thread reading the dirty bit and updating the values.

    I'd take a look at the version = true support in ORMLite which does validation of this at the data level. It is made for distributed systems where multiple connections are being made to a database from multiple clients but it might help you as well. See the version field documentation.