Search code examples
c#xamarinrealmrealm-net

Inconsistent results with parallel reads & writes in Realm


Maybe I'm missing something out but I'm getting some weird results while reading and writing in parallel from Realm, same object.

I first encountered this in a bigger project but just managed now to reproduce it in a test project.

Scenario: one RealmObject object with two DateTimeOffset fields is created and then updated every minute. Another thread reads every 10 seconds and outputs the values.

I'll first show the output since it's most relevant. The time in the paranthesis is the time at which the output is logged. The rest is the identifier (READER/WRITER) and then the JSON representation of the RealmObject.

What happens is that after a successful write, the reader reads the old values for a while, then reads the new value and then reads the old values again. If I restart the app then everything is OK, of course, for a while.

//Reader starts reading. The output is correct

[0:] (10:05:44.656) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:05:54.656) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:04.657) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]

//Writer comes in and updates the values.

[0:] (10:06:07.523) WRITER: {"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}

//Reader reads incorrect (OLD) values for a while

[0:] (10:06:14.661) READER: > [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:24.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:06:34.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]

//Reader suddnely reads correct values

[0:] (10:06:44.678) READER: [{"LastSyncTime":"2016-07-17T22:06:07.521+00:00","LastChangeDate":"2016-07-17T22:11:07.523+00:00","IsManaged":true}]

//Reader falls back to previous values (????)

[0:] (10:06:54.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}] [0:] (10:07:04.678) READER: [{"LastSyncTime":"2016-07-17T22:04:45.384+00:00","LastChangeDate":"2016-07-17T22:09:45.384+00:00","IsManaged":true}]

The code:

public class TimestampDataObject : RealmObject
{
    public DateTimeOffset LastSyncTime { get; set; }
    public DateTimeOffset? LastChangeDate { get; set; }
}

The writing part:

Observable.Interval(TimeSpan.FromMinutes(1)).Subscribe(async (v) =>
{
    await Realm.GetInstance().WriteAsync(r =>
    {
        var item = r.All<TimestampDataObject>().AsEnumerable().FirstOrDefault();
        if (item == null)
        {
            item = r.CreateObject<TimestampDataObject>();
        }

        item.LastSyncTime = DateTimeOffset.UtcNow;
        item.LastChangeDate = DateTimeOffset.UtcNow.AddMinutes(5);

        Debug.WriteLine($"({DateTime.UtcNow.ToString("hh:mm:ss.fff")}) WRITER: {JsonConvert.SerializeObject(item)}");
    });
});

The reader:

Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
{
    var latestTimestampInfo = Realm.GetInstance().All<TimestampDataObject>();
    Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
});

Not sure what happens. Perhaps some of the Realm guys can clarify this.

UPDATE

Did some more tests and apparently it all works fine when the reader and writer are on the same Thread. Added in the logs the managed thread id right after the timestamps. So, below you can see how a reader running on thread 11 - the same as the writer's - works fine. But a reader running on a different thread outputs the old values:

//Before the update (correct)

[0:] (10:56:53.679,11) READER: [{"LastSyncTime":"2016-07-17T22:55:55.573+00:00","LastChangeDate":"2016-07-17T23:00:55.576+00:00","IsManaged":true}]

//The update is done on thread 11

[0:] (10:56:55.552,11) WRITER: {"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}

//Reader on thread 11 outputs correct values

[0:] (10:57:03.702,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]

//Again, the reader is on thread 11. Correct output.

[0:] (10:57:13.702,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]

//A reader on thread 12 outputs incorrect results

[0:] (10:57:23.703,12) READER: [{"LastSyncTime":"2016-07-17T22:23:19.674+00:00","LastChangeDate":"2016-07-17T22:28:19.676+00:00","IsManaged":true}]

//Again, the reader is on thread 11. Correct output.

[0:] (10:57:33.703,11) READER: [{"LastSyncTime":"2016-07-17T22:56:55.552+00:00","LastChangeDate":"2016-07-17T23:01:55.553+00:00","IsManaged":true}]


Solution

  • Nevermind. It seems I need to call Realm.Refresh() for Realm instances created and used on threads without an event loop. Otherwise they stay in sync with the write commit at the point when they were created.

    Realm.Refresh() causes the realm to advance to the latest write commit and use that for subsequent reads.

    Relevant documentation:

    When you initially open a Realm on a thread, its state will be based off the most recent successful write commit, and it will remain on that version until refreshed. Realms are automatically refreshed at the start of every runloop iteration. If a thread has no runloop (which is generally the case in a background thread), then Realm.Refresh() must be called manually in order to advance the transaction to the most recent state.

    The reader part looks like this now:

    Observable.Interval(TimeSpan.FromSeconds(10)).Subscribe(v =>
    {
        var realm = Realm.GetInstance();
        realm.Refresh();
        var latestTimestampInfo = realm.All<TimestampDataObject>();
        Debug.WriteLine($"({ DateTime.UtcNow.ToString("hh:mm:ss.fff")},{Thread.CurrentThread.ManagedThreadId}) READER: {JsonConvert.SerializeObject(latestTimestampInfo)}");
    });