So far I've always run Nimrod code in the main thread, also called the GUI thread, in my iOS app. But now I have to run a potentially long task and the typical way to run them is to start some UI please wait indicator, start the long task in a background thread, then publish the results back to the main thread.
This, however, crashes Nimrod. If I run the code blocking on the main thread, everything works. If I run the Nimrod code from a background thread, the GC crashes at some point:
* thread #3: tid = 0x26d5b, 0x000c486a seohtracker-devel`markstackandregisters_63636(gch=0x00100b88) + 346 at root_system.m:3961, queue = 'com.apple.root.default-priority, stop reason = EXC_BAD_ACCESS (code=2, address=0x2520000)
frame #0: 0x000c486a seohtracker-devel`markstackandregisters_63636(gch=0x00100b88) + 346 at root_system.m:3961
frame #1: 0x000c5e64 seohtracker-devel`collectctbody_66007(gch=0x00100b88) + 188 at root_system.m:4456
frame #2: 0x000c6332 seohtracker-devel`collectct_44008(gch=0x00100b88) + 438 at root_system.m:4551
frame #3: 0x000c63a0 seohtracker-devel`rawnewobj_49802(typ=0x00101824, size=16, gch=0x00100b88) + 100 at root_system.m:4730
frame #4: 0x000c6c1e seohtracker-devel`newObj(typ=0x00101824, size=16) + 106 at root_system.m:4755
frame #5: 0x000c735a seohtracker-devel`rawNewString(space=0) + 66 at root_system.m:5001
frame #6: 0x000c8536 seohtracker-devel`copyString(src=0x000f8738) + 62 at root_system.m:5691
frame #7: 0x000bb858 seohtracker-devel`format_92645(info=0x00292b68, f=0x000f79ec) + 7016 at pure_times.m:2578
frame #8: 0x000b1a94 seohtracker-devel`buildcsvfile_116545(conn=0x155e89f0, outfilename=0x029ac028) + 2908 at nim_seohyun_db.m:2253
frame #9: 0x000b51be seohtracker-devel`export_database_to_csv(csvfilename=0x156cb100) + 294 at nim_seohyun.m:1887
frame #10: 0x000bd816 seohtracker-devel`__37-[EHSync_export_vc prepare_csv_dump:]_block_invoke(.block_descriptor=0x156caa00) + 210 at EHSync_export_vc.m:114
frame #11: 0x3b6bd0c2 libdispatch.dylib`_dispatch_call_block_and_release + 10
frame #12: 0x3b6c27d8 libdispatch.dylib`_dispatch_root_queue_drain + 224
frame #13: 0x3b6c29c4 libdispatch.dylib`_dispatch_worker_thread2 + 56
frame #14: 0x3b7ecdfe libsystem_pthread.dylib`_pthread_wqthread + 298
frame #15: 0x3b7eccc4 libsystem_pthread.dylib`start_wqthread + 8
No other Nimrod code is being run at the same time, and I was hoping that this was OK, but it is not. Is there any way I can run this chunk of Nimrod code from the background thread without crashing the GC and without running it on the main thread?That's really bad, threads are worse than GCD, plus it means ObjC vs Nimrod code has to be funneled on the main thread, and this TChannel communication, I don't see that working directly with a background objc thread, which is the main issue.
Maybe for the objc generation Nimrod could use objc's pseudo garbage collection? Being a reference counting model it would be the same as standard objc memory handling. There are crazy people implementing objc code in plain C. This should work with Nimrod objects allocated on the heap. But in objc you can't have classes on the stack, so I guess that would be a problem for Nimrod stack values, right?
No other Nimrod code is being run at the same time, and I was hoping that this was OK
So, what was the original goal here?
You hoped that you'll be able to reuse the same heap on a different thread? We could add some unsafe APIs for migrating the thread local storage data storing the pointers to the current heap. Or was the problem that you are not in control of the thread creation and can't use the standard Nimrod threads that initialize their heaps properly?
The sentence you are quoting expresses my limited experience with threading and garbage collection. So far for me in other languages the only trouble was making sure code was reentrant or non-reentrant code would have locked/serial access. I wasn't aware that garbage collection introduces new requirements.
For the specific case I was trying to run, adding this unsafe API would likely be enough. Once you are aware of this problem, generic wrapping of the exported Nimrod APIs is also possible. For all exportc functions a small wrapper can be added to check if it is running on the main thread, and if not, queue execution of itself on it. This prevents crashes. Then, Nimrod code can create a Nimrod thread, run in the background and trigger a callback when finished to the objc side. The objc side can then ping pong UI results to the main thread.
Funnelling execution on the main thread is a little bit more involved but seems possible, and as long as the wrapper code running on the main thread doesn't do much, end users won't notice the little UI hiccups. The only problem was knowing this has to be done.
With regards to migrating thread local storage, I don't think it's a good idea for objc's gcd, because as you say the programmer is not in control of thread creation, which is precisely the point.