Realm-dotnet: Using Realm with a SynchronizationContext implementation that is not single-thread-affine

Created on 16 Dec 2016  路  18Comments  路  Source: realm/realm-dotnet

Crash at Realms.SynchronizationContextEventLoopSignal/EventLoop

Goal

I have a complex async/await/Reactive flow which uses multiple Realm instances using the same RealmConfiguration and also has a custom SynchronizationContext involved that is crashing almost 100% of the time. The Realm 0.80.0 version of the App runs fine.

I repo'd this "random" issue using xUnit for Devices / Xunit.Sdk.AsyncTestSyncContext and Realm 0.81.0 as it produces a similar crash and stack trace. You will a see repeating xUnit [Theory] in the unit test to ensure that it does crash. It almost always crashes on the first execution, but sometimes it might work 2,3 times before crashing... as the crash is not 100% deterministic.

Expected Results

No crash due to custom SynchronizationContext

Actual Results

libc++abi.dylib: terminating with uncaught exception of type realm::IncorrectThreadException: Realm accessed from incorrect thread.
Realm0810SynchronizationContext[24731:19379897] critical: Stacktrace:

critical:   at <unknown> <0xffffffff>
critical:   at (wrapper managed-to-native) object.wrapper_native_0x10f019150 (intptr) <IL 0x00009, 0x000dd>
critical:   at Realms.SynchronizationContextEventLoopSignal/EventLoop/<Post>c__AnonStorey0.<>m__0 (object) <IL 0x0001e, 0x000a5>
critical:   at Xunit.Sdk.AsyncTestSyncContext/<>c__DisplayClass7_0.<Post>b__1 (object) <IL 0x0000c, 0x00082>
critical:   at Xunit.Sdk.MaxConcurrencySyncContext.RunOnSyncContext (System.Threading.SendOrPostCallback,object) <IL 0x0000d, 0x000c4>
critical:   at Xunit.Sdk.MaxConcurrencySyncContext/<>c__DisplayClass11_0.<WorkerThreadProc>b__0 (object) <IL 0x0001c, 0x000f3>
critical:   at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x0008d] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:957
critical:   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x00000] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:904
critical:   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object) [0x00031] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:893
critical:   at (wrapper runtime-invoke) <Module>.runtime_invoke_void_object_object_object (object,intptr,intptr,intptr) <IL 0x00065, 0x00352>
critical:   at <unknown> <0xffffffff>
critical:   at (wrapper managed-to-native) System.Reflection.MonoMethod.InternalInvoke (System.Reflection.MonoMethod,object,object[],System.Exception&) <IL 0x00016, 0x00131>
critical:   at System.Reflection.MonoMethod.Invoke (object,System.Reflection.BindingFlags,System.Reflection.Binder,object[],System.Globalization.CultureInfo) [0x00038] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/corlib/System.Reflection/MonoMethod.cs:305
critical:   at System.Reflection.MethodBase.Invoke (object,object[]) [0x00000] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/reflection/methodbase.cs:229
critical:   at Microsoft.Scripting.Interpreter.MethodInfoCallInstruction.InvokeWorker (object[]) [0x00010] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/dlr/Runtime/Microsoft.Dynamic/Interpreter/Instructions/CallInstruction.cs:261
critical:   at Microsoft.Scripting.Interpreter.MethodInfoCallInstruction.Invoke (object[]) [0x00000] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/dlr/Runtime/Microsoft.Dynamic/Interpreter/Instructions/CallInstruction.cs:239
critical:   at Microsoft.Scripting.Interpreter.MethodInfoCallInstruction.Run (Microsoft.Scripting.Interpreter.InterpretedFrame) [0x0003b] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/dlr/Runtime/Microsoft.Dynamic/Interpreter/Instructions/CallInstruction.cs:289
critical:   at Microsoft.Scripting.Interpreter.Interpreter.Run (Microsoft.Scripting.Interpreter.InterpretedFrame) [0x0001b] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/dlr/Runtime/Microsoft.Dynamic/Interpreter/Interpreter.cs:126
critical:   at Microsoft.Scripting.Interpreter.LightLambda.RunVoid2<T0_REF, T1_REF> (T0_REF,T1_REF) [0x0002a] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/dlr/Runtime/Microsoft.Dynamic/Interpreter/LightLambda.Generated.cs:117
critical:   at Xunit.Sdk.ExecutionContextHelper.Run (object,System.Action`1<object>) <IL 0x00011, 0x000f3>
critical:   at Xunit.Sdk.MaxConcurrencySyncContext.WorkerThreadProc () <IL 0x0007c, 0x00450>
critical:   at Xunit.Sdk.XunitWorkerThread/<>c.<QueueUserWorkItem>b__5_0 (object) <IL 0x0000d, 0x000d1>
critical:   at System.Threading.Tasks.Task.InnerInvoke () [0x0002b] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/Task.cs:2885
critical:   at System.Threading.Tasks.Task.Execute () [0x00016] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/Task.cs:2502
critical:   at System.Threading.Tasks.Task.ExecutionContextCallback (object) [0x00007] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/Task.cs:2865
critical:   at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x0008d] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:957
critical:   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x00000] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:904
critical:   at System.Threading.Tasks.Task.ExecuteWithThreadLocal (System.Threading.Tasks.Task&) [0x0005f] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/Task.cs:2827
critical:   at System.Threading.Tasks.Task.ExecuteEntry (bool) [0x0006f] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/Task.cs:2760
critical:   at System.Threading.Tasks.ThreadPoolTaskScheduler.LongRunningThreadWork (object) [0x00007] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/Tasks/ThreadPoolTaskScheduler.cs:47
critical:   at System.Threading.ThreadHelper.ThreadStart_Context (object) [0x0002c] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/thread.cs:72
critical:   at System.Threading.ExecutionContext.RunInternal (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x0008d] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:957
critical:   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object,bool) [0x00000] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:904
critical:   at System.Threading.ExecutionContext.Run (System.Threading.ExecutionContext,System.Threading.ContextCallback,object) [0x00031] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/executioncontext.cs:893
critical:   at System.Threading.ThreadHelper.ThreadStart (object) [0x00012] in /Users/builder/data/lanes/3985/b4a43820/source/xamarin-macios/_ios-build/Library/Frameworks/Xamarin.iOS.framework/Versions/git/src/mono/mcs/class/referencesource/mscorlib/system/threading/thread.cs:87
critical:   at (wrapper runtime-invoke) <Module>.runtime_invoke_void__this___object (object,intptr,intptr,intptr) <IL 0x00053, 0x00310>
critical: 
Native stacktrace:
2016-12-15 18:23:16.487 Realm0810SynchronizationContext[24731:19379660]     [PASS] Realm0810SynchronizationContext.RealmRead.ObjectForPrimaryKey_Sequential
Thread finished:  #9
Thread finished:  #10
 Tests run: 1 Passed: 1 Failed: 0 Skipped: 0
critical:   0   libmonosgen-2.0.dylib               0x000000011a5d9c3d mono_handle_native_sigsegv + 253
critical:   1   libsystem_platform.dylib            0x000000011e44352a _sigtramp + 26
critical:   2   libsystem_c.dylib                   0x000000011e1d50b3 suboptarg + 5403
critical:   3   libsystem_c.dylib                   0x000000011e1a0fd7 abort + 129
critical:   4   libc++abi.dylib                     0x000000011ac6d95a __cxa_bad_cast + 0
critical:   5   libc++abi.dylib                     0x000000011ac92ce7 _ZL25default_terminate_handlerv + 243
critical:   6   libobjc.A.dylib                     0x000000011acaa4b4 _ZL15_objc_terminatev + 124
critical:   7   libc++abi.dylib                     0x000000011ac8fe69 _ZSt11__terminatePFvvE + 8
critical:   8   libc++abi.dylib                     0x000000011ac8f8e0 _ZN10__cxxabiv1L22exception_cleanup_funcE19_Unwind_Reason_CodeP17_Unwind_Exception + 0
critical:   9   Realm0810SynchronizationContext     0x000000010f034e62 _ZNK5realm5Realm13verify_threadEv + 80
critical:   10  Realm0810SynchronizationContext     0x000000010f03562e _ZN5realm5Realm6notifyEv + 68
critical:   11  Realm0810SynchronizationContext     0x000000010f018f65 _ZNK5realm5_impl17WeakRealmNotifier8CallbackclEv + 45
critical:   12  ???                                 0x00000001346393ce 0x0 + 5173908430
critical:   13  ???                                 0x00000001345d2435 0x0 + 5173486645
critical:   14  ???                                 0x000000013302362c 0x0 + 5150750252
critical:   15  libmonosgen-2.0.dylib               0x000000011a52e747 mono_jit_runtime_invoke + 2247
critical:   16  libmonosgen-2.0.dylib               0x000000011a6f7638 do_runtime_invoke + 88
critical:   17  libmonosgen-2.0.dylib               0x000000011a6fb070 mono_runtime_try_invoke_array + 1536
critical:   18  libmonosgen-2.0.dylib               0x000000011a6684ee ves_icall_InternalInvoke + 734
critical:   19  ???                                 0x00000001331701e2 0x0 + 5152113122
critical:   20  ???                                 0x000000013316fad8 0x0 + 5152111320
critical:   21  ???                                 0x00000001345b6168 0x0 + 5173371240
critical: 
=================================================================
Got a SIGABRT while executing native code. This usually indicates
a fatal error in the mono runtime or one of the native libraries 
used by your application.
=================================================================

Code Sample

Xamarin.iOS app using xUnit for Devices:

Realm081Test.zip SHA Checksum is : 78ec0beeeb0beb21e6eae718c9c92f6b8ebc6e90

Within the SynchronizationContextCrash.cs | GenerateDB method, if you comment out the Realm.WriteAsync block the crash does not ever happen.

Downgrading the Realm package to 0.80.0 will result in Success: 100 tests

async Task<Realm> GenerateDB(string path)
{
    var config = new RealmConfiguration(path)
    {
        ObjectClasses = new Type[] { typeof(KeyValue) }
    };
    var cache = Realm.GetInstance(config);

    // Commit out the WriteSync, replace with "await Task.FromResult(0);" and the "random" crash goes away
    await cache.WriteAsync(r =>
    {
        // Fetch and write new objects
    });
    //await Task.FromResult(0);

    return cache;
}

Version of Realm and tooling

Realm version(s): 0.81.0
Which operating system version and device: Only iOS has be tested so far (sim & device)

T-Bug-Crash

Most helpful comment

Hey @sushihangover,

This is an unfortunate side-effect of our work to support Windows. For 0.81.0 we migrated away from platform-specific code for the iOS RunLoop and Android Looper to an abstraction based on System.Threading.SynchronizationContext in order to support more platforms.

Most of the SynchronizationContext implementations out there - the ones Xamarin built on top of the iOS RunLoop and Android Looper and the ones that come with Windows Forms, WPF, and UWP, are thread-affine in that they always post work on the same thread. xUnit, however, comes with a SynchronizationContext implementation that uses a dedicated worker thread.

The simple workaround is to temporarily unset the current SynchronizationContext when opening a realm on a worker thread so that the realm doesn't capture it:

Realm GetInstanceWithoutCapturingContext(RealmConfiguration config)
{
    var context = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    Realm realm = null;
    try
    {
        realm = Realm.GetInstance(config);
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(context);
    }

    return realm;
}

When opening a realm on a thread without a SynchronizationContext, the reactive properties of Realm - change events and auto refresh, will be disabled, same as when opening a realm on a thread without a runloop or looper in previous versions.

Alternatively, you can use Nito.AsyncEx.AsyncContext in your tests which will install a SynchronizationContext implementation that is thread-affine. This is the library I'm using for running our tests on Windows.

There might be further improvements we can come up with to be compatible with more SynchronizationContext implementations, which will hopefully eliminate the need for these workarounds.

All 18 comments

Android-based crash of the same test.

(Glad I debugged this on iOS as there is no managed stack to provide a clue of the origins...)

F/libc    (25237): Fatal signal 6 (SIGABRT), code -6 in tid 25303 (Thread-2859)
I/DEBUG   (  294): *** *** *** *** *** *** *** *** *** *** *** *** *** *** *** ***
I/DEBUG   (  294): Build fingerprint: 'ZTE/Z716BL/baffin:5.1.1/LMY47V/20160113.115848:user/release-keys'
I/DEBUG   (  294): Revision: '0'
I/DEBUG   (  294): ABI: 'arm'
I/DEBUG   (  294): pid: 25237, tid: 25301, name: Thread-2857  >>> com.sushhangover.realm0810_synccontext_droid <<<
I/DEBUG   (  294): signal 6 (SIGABRT), code -6 (SI_TKILL), fault addr --------
I/DEBUG   (  294):     r0 00000000  r1 000062d5  r2 00000006  r3 00000000
I/DEBUG   (  294):     r4 9cd84dd8  r5 00000006  r6 00000000  r7 0000010c
I/DEBUG   (  294):     r8 b97a8bc0  r9 9d08caf8  sl fffffb64  fp 9cd84670
I/DEBUG   (  294):     ip 000062d5  sp 9cd84460  lr b6e0b9dd  pc b6e3227c  cpsr 600f0010
I/DEBUG   (  294):
I/DEBUG   (  294): backtrace:
I/DEBUG   (  294):     #00 pc 0003a27c  /system/lib/libc.so (tgkill+12)
I/DEBUG   (  294):     #01 pc 000139d9  /system/lib/libc.so (pthread_kill+52)
I/DEBUG   (  294):     #02 pc 000145ef  /system/lib/libc.so (raise+10)
I/DEBUG   (  294):     #03 pc 00010e7d  /system/lib/libc.so (__libc_android_abort+36)
I/DEBUG   (  294):     #04 pc 0000f534  /system/lib/libc.so (abort+4)
I/DEBUG   (  294):     #05 pc 001c5173  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (__gnu_cxx::__verbose_terminate_handler()+226)
I/DEBUG   (  294):     #06 pc 00193495  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (__cxxabiv1::__terminate(void (*)())+4)
I/DEBUG   (  294):     #07 pc 00193509  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (std::terminate()+8)
I/DEBUG   (  294):     #08 pc 0019362d  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (__cxa_throw+120)
I/DEBUG   (  294):     #09 pc 0003bde7  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (realm::Realm::verify_thread() const+90)
I/DEBUG   (  294):     #10 pc 0003c391  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (realm::Realm::notify()+40)
I/DEBUG   (  294):     #11 pc 0003f469  /data/app/com.sushhangover.realm0810_synccontext_droid-1/lib/arm/librealm-wrappers.so (realm::_impl::WeakRealmNotifier::Callback::operator()() const+108)
I/DEBUG   (  294):     #12 pc 00001698  <unknown>
I/DEBUG   (  294):
I/DEBUG   (  294): Tombstone written to: /data/tombstones/tombstone_07

Hey @sushihangover,

This is an unfortunate side-effect of our work to support Windows. For 0.81.0 we migrated away from platform-specific code for the iOS RunLoop and Android Looper to an abstraction based on System.Threading.SynchronizationContext in order to support more platforms.

Most of the SynchronizationContext implementations out there - the ones Xamarin built on top of the iOS RunLoop and Android Looper and the ones that come with Windows Forms, WPF, and UWP, are thread-affine in that they always post work on the same thread. xUnit, however, comes with a SynchronizationContext implementation that uses a dedicated worker thread.

The simple workaround is to temporarily unset the current SynchronizationContext when opening a realm on a worker thread so that the realm doesn't capture it:

Realm GetInstanceWithoutCapturingContext(RealmConfiguration config)
{
    var context = SynchronizationContext.Current;
    SynchronizationContext.SetSynchronizationContext(null);

    Realm realm = null;
    try
    {
        realm = Realm.GetInstance(config);
    }
    finally
    {
        SynchronizationContext.SetSynchronizationContext(context);
    }

    return realm;
}

When opening a realm on a thread without a SynchronizationContext, the reactive properties of Realm - change events and auto refresh, will be disabled, same as when opening a realm on a thread without a runloop or looper in previous versions.

Alternatively, you can use Nito.AsyncEx.AsyncContext in your tests which will install a SynchronizationContext implementation that is thread-affine. This is the library I'm using for running our tests on Windows.

There might be further improvements we can come up with to be compatible with more SynchronizationContext implementations, which will hopefully eliminate the need for these workarounds.

@fealebenpae Thanks for the followup/information, removing the syncro'context that totally makes sense for unit testing and works fine, also tried it in a Realm-based background fetch service this morning on an iOS app that has the same issue and it works fine there, manually calculating/publishing change events is ugly but works, but this particular app is a different story.

The app uses a custom IScheduler/SynchronizationContext that manages a thread pool and balances operations over that pool that is inter-mixed within reactive observables and losing the Realm benefits within those streams would be a no-go. After some discussions and testing, we'll keep it on 0.80.0 for now, we can swap-in the alternative store and add the new app features requests to it (we maintain the App's original C++-based ORM/SQLite store and have Xamarin/C# CppSharp-based bindings for it).

Thanks again.

losing the Realm benefits

Unless the thread you open the realm instance on has an active and spinning CFRunLoop or ALooper that realm instance is not going to post change events even in 0.80.0. I believe the equivalent of this in 0.81.0 is to open the realm when SynchronizationContext.Current == null.

manually calculating/publishing change events is ugly

You can call Realm.Refresh and it should force the realm to move to the latest version of the realm file and emit change events for any transactions between the current and new one. Instances of the same realm across threads and processes talk to each other over a pipe - every time an instance advances the write transaction it notifies the other instances which results in them being automatically refreshed. That works only if they can post work items to the thread that spawned them, though, so in the absence of an evenloop/SynchronizationContext in place, calling Realm.Refresh achieves the same effect.

The app uses a custom IScheduler/SynchronizationContext that manages a thread pool

I would be really grateful if you could go into a little bit more detail about the nature of this SynchronizationContext implementation and how it splits the work involving realm instances across a number of threads. My experience so far has been with eventloop-based implementations, but I'm hoping I will be able to extract a usage pattern that will still allow us to support a few more eventloop incarnations out there without being overly-eager to capture contexts that don't behave the way we expect them to.

manually calculating/publishing change events is ugly

You can call Realm.Refresh and it should force the realm to move to the latest version of the realm file and emit change events for any transactions between the current and new one.

I will re-visit this as I lost the change events for some reason... (great info on the trans/pipe/refresh usage, you should tag that info in an "Advanced Realm threading for Dummies" ;-) section of the docs #1041)

losing the Realm benefits

Unless the thread you open the realm instance on has an active and spinning CFRunLoop or ALooper that realm instance...

Yes, I have CFRunloops&Loopers (non-UI) so I can post long running user requests, capture network sources, etc...

The app uses a custom IScheduler/SynchronizationContext that manages a thread pool

I would be really grateful if you could go into a little bit more detail about the nature of this SynchronizationContext implementation

NP, I'll put together something and post another comment

closing this assuming we're not going to get another sample. Have added more notes to TroubleShooting section of docs and added WriteAsync (in review now)

I've opened an issue to track our progress towards lifting the same-thread requirement for Realms/RealmObjects: https://github.com/realm/realm-dotnet/issues/1257

@nirinchev is there a solution for this?

Yes, the next major release will work on a per-synchronization context basis for Realms that are opened on a thread with a synchronization context.

Can you point me to a example that helps me to understand this: Realms that are opened on a thread with a synchronization context?

You can read up on synchronization contexts here: https://hamidmosalla.com/2018/06/24/what-is-synchronizationcontext/

For most apps, the main thread is going to have a synchronization context and background threads don't (e.g. Task.Run(...)-ed methods run on the threadpool without a sync context). RX also typically installs a sync context.

I know that part, i was asking about a Realm example where i can write to the realm on a background thread and reflect that on the UI so i don't get "Realm accessed from incorrect thread."

This request is a bit too vague so I'm not sure what exactly you're looking for. Perhaps ThreadSafeReferences are something that can help you for your use case? You can create a reference to an object from the main thread and use it on the background thread. Writes made on background threads will automatically be reflected on the UI thread when the Realm on the UI thread gets refreshed (happens automatically on a runloop iteration).

I get exactly the same exception while trying to use ThreadSafeReferences:

```c#
var myItems = _realm.All();
var itemsReference = ThreadSafeReference.Create(myItems);

await Task.Run(async () =>
{
var otherRealm = Realm.GetInstance();
var otherItems = otherRealm.ResolveReference(itemsReference);

var newItems = await client.GetDiff(otherItems.ToList());

foreach(var item in newItems)
{
    // Exception here
    otherRealm.Write(() =>
    {
        otherRealm.Add(item);
    });

}
});
```

I thought that with Realm, i was able to get an instance on the background thread, write, and get my queries updated on the UI thread. It seems i was wrong and that is very disappointing.

Edit: fixed

The problem here is that in your background thread, you're using await. Since there's no synchronization context there, the continuation of the await is dispatched on the threadpool (i.e. a random thread), so your background Realm is no longer valid when you try to write to it.

You can write on a background thread and get your queries updated on the main thread, but you have to make sure you're not accessing a Realm from the wrong thread. One way you can go about fixing this is to open the Realm again after the client.GetDiff line:

var other Realm = ...
var otherItems = ...

var newItems = await client.GetDiff(...);

otherRealm = Realm.GetInstance();
foreach (var item in newItems)
{
    otherRealm.Write(...);
}

Alternatively, you can use the Nito.AsyncEx library to install a synchronizationcontext on your background thread:

await Task.Run(AsyncContext.Run(async () =>
{
    var otherRealm = ...
    var otherItems = ...

    var newItems = await client.GetDiff(...);

    // Note that because we're running that in an AsyncContext lambda, the
    // client.GetDiff continuation is dispatched on the same thread, so otherRealm
    // is still valid

    foreach (var item in newItems)
    {
        otherRealm.Write(...);
    }
}));

The Realm package has a dependency on Nito.AsyncEx, so you should be able to just start using it, without taking an explicit dependency on it.

No matter what i do, with Nito or without it, reading or writing to the Realm in another thread still makes a UITableView scrolling to lag.

Edit: fixed.

I'm sorry, but this is getting more and more detached from the original issue. You have opened an issue regarding the UITableView lag - once you've provided a repro case, we can continue the discussion there.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

FoggyFinder picture FoggyFinder  路  8Comments

nirinchev picture nirinchev  路  8Comments

RealmBot picture RealmBot  路  4Comments

salgiza picture salgiza  路  10Comments

AndyDentFree picture AndyDentFree  路  7Comments