Problems with memory ownership

I started an iOS example based on the 0.5 calculator one to experiment with some questions I asked on the blog but weren’t fully clear. I’m refering to @Nikolay.Igotti mentioning that globals are currently thread local, which makes them pretty useless for sharing logic state, so I tried to use heap memory and failed.

I uploaded an example to https://gitlab.com/gradha/kotlin-memory-ownership which implements a very simple kotlin framework to test both thread local globals and heap memory. The app delegate calls a function to test globals which outputs:

KG1 {GTD: defaultKotlinGlobal contains 0}
KG2 {GTD: generated modified once contains 0}
KG3 {GTD: generated modified twice contains 1}
KG Post Dispatch kotlin global block
KG Background report {GTD: generated fromBackground contains 0}
KG should be fromBackground
KG Back on UI {GTD: generated modified twice contains 1}
KG If different from background, we have thread local

One can see that the background thread has its own thread global, corroborating Nikolay’s answer. The class constructor, however, also runs a test with heap allocated state:

KS initial {GTD: createdExternalGlobal contains 0}
KS changed {GTD: generated Hehe1 contains 1}
KS Post Dispatch swift global block
KS background {GTD: generated fromBackground contains 0}
KS back UI {GTD: generated fromBackground contains 0}

The memory is allocated and returned by the framework library in createKotlinState, and the logs suggest that this memory can be shared and modified by different states. The memory returned is stored as the private ks property of the Logic swift singleton, so that it can be used later too.

However, once the app is launched and the two UI buttons used, accessing (or modifying, it’s not clear from the crash) crashes the app… randomly. Sometimes I can press both buttons in the simulator for a long time and nothing happens, sometimes the app crashes on the first attempt to increment the state count. The iOS backtrace shows:

Increasing counter
Adding item to ks:59417ee0
Adding item to ks.testData:59b00e50
Increasing counter
Adding item to ks:59417ee0
Adding item to ks.testData:59b00e50
Increasing counter
Adding item to ks:59417ee0
Adding item to ks.testData:59b00e50
Checking counter
Increasing counter
Adding item to ks:59417ee0
Adding item to ks.testData:59b00e50
* <-- Debugger is stopped here with crash info
(lldb) bt
* thread #5, queue = 'com.apple.root.default-qos', stop reason = EXC_BAD_ACCESS (code=1, address=0x8)
    frame #0: 0x000000010410cfc1 KotlinMemoryOwnership`(anonymous namespace)::Release(ContainerHeader*) + 129
    frame #1: 0x00007fe159803490
    frame #2: 0x0000000104117b8e KotlinMemoryOwnership`LeaveFrame + 78
    frame #3: 0x00000001040fc2e6 KotlinMemoryOwnership`kfun:kotlin.collections.arrayOfUninitializedElements(kotlin.Int)Reference + 102
    frame #4: 0x000000010410483c KotlinMemoryOwnership`kfun:kotlin.collections.copyOfUninitializedElements@kotlin.Array<#GENERIC>.(kotlin.Int;kotlin.Int)Reference + 140
    frame #5: 0x0000000104104756 KotlinMemoryOwnership`kfun:kotlin.collections.copyOfUninitializedElements@kotlin.Array<#GENERIC>.(kotlin.Int)Reference + 86
    frame #6: 0x0000000104104614 KotlinMemoryOwnership`kfun:kotlin.collections.ArrayList.ensureCapacity(kotlin.Int) + 228
    frame #7: 0x00000001041044ef KotlinMemoryOwnership`kfun:kotlin.collections.ArrayList.ensureExtraCapacity#internal + 79
    frame #8: 0x00000001041043db KotlinMemoryOwnership`kfun:kotlin.collections.ArrayList.insertAtInternal#internal + 75
    frame #9: 0x0000000104104ab9 KotlinMemoryOwnership`kfun:kotlin.collections.ArrayList.addAtInternal#internal + 233
    frame #10: 0x0000000104103047 KotlinMemoryOwnership`kfun:kotlin.collections.ArrayList.add(#GENERIC)ValueType + 103
    frame #11: 0x00000001040ecc6f KotlinMemoryOwnership`kfun:es.elhaso.GlobalTestData.add(kotlin.String)ValueType + 79
    frame #12: 0x00000001040ec6e4 KotlinMemoryOwnership`___lldb_unnamed_symbol2$$KotlinMemoryOwnership + 84
    frame #13: 0x0000000104051b15 Memory Ownership`Logic.addItem(self=0x00007fe15941d500) at Logic.swift:48
    frame #14: 0x000000010404e630 Memory Ownership`closure #1 in ViewController.didTouchIncreaseCounter(_:) at ViewController.swift:15
  * frame #15: 0x000000010404e679 Memory Ownership`thunk for @callee_owned () -> () at ViewController.swift:0
    frame #16: 0x0000000106db2ba6 libdispatch.dylib`_dispatch_call_block_and_release + 12
    frame #17: 0x0000000106dd07f4 libdispatch.dylib`_dispatch_client_callout + 8
    frame #18: 0x0000000106dbb15d libdispatch.dylib`_dispatch_root_queue_drain + 1097
    frame #19: 0x0000000106dbc5d9 libdispatch.dylib`_dispatch_worker_thread3 + 111
    frame #20: 0x00000001071321ca libsystem_pthread.dylib`_pthread_wqthread + 1387
    frame #21: 0x0000000107131c4d libsystem_pthread.dylib`start_wqthread + 13

I didn’t find any information about memory ownership, so I’m just guessing what is happening here. My best guess is that the createKotlinState() method return value stored on the swift side is not counted by the kotlin side garbage collector, and sometimes this memory is freed earlier, sometimes later. This kind of makes sense, since the button to trigger a kotlin state read never crashes (probably accessing zombie/freed memory on the simulator is fine), but trying to modify it does.

But then I thought that before returning the heap memory to swift I could assign it to a thread local global, thus preventing it from being collected. Yet the crashes still happen, suggesting this is not the source of the problem. Reading again the blog post announcement for 0.4 I took notice of the Pinning class but I haven’t been able to figure out what to do with it.

My purpose of this test is to evaluate writing domain/business/storage logic in Kotlin and sharing it among iOS/Android. But if kotlin globals can’t be used due to being thread local, and heap memory access crashes, what can be done to create and preserve the business logic in memory?