Skip to content
This repository has been archived by the owner on Aug 10, 2021. It is now read-only.

Memory.cpp:356: runtime assert: Unable to execute Kotlin code on uninitialized thread #2207

Closed
yuriry opened this issue Oct 12, 2018 · 26 comments

Comments

@yuriry
Copy link

yuriry commented Oct 12, 2018

My code is running into this line when it calls a method from iOS (calling from Android works fine). The method getFoo() looks like this:

internal expect val ApplicationDispatcher: CoroutineDispatcher

class RESTClient(val host: String) {
    private val client = HttpClient()

    fun getFoo(path: String?, extraHeaders: Map<String, String>? = null, callback: (Foo?, Exception?) -> Unit) {
        GlobalScope.launch(ApplicationDispatcher) {
            try {
                val jsonText: String = client.get {
                    url {
                        protocol = URLProtocol.HTTPS
                        host = this@RESTClient.host
                        encodedPath = ...
                        extraHeaders?.forEach { header(it.key, it.value)  }
                    }
                }
                val foo = Foo.parse(jsonText)
                callback(foo, null)
            } catch (e: Exception) {
                callback(null, e)
            }
        }
    }

My current version information is as follows:

kotlin_version=1.3.0-rc-57
kotlin_native_version=0.9.2
serialization_version=0.8.1-rc13
ktor_version=0.9.5-rc13-conf2
android_gradle_version=3.2.0

Any help on how to properly initialize treads when calling Kotlin methods on iOS would be greatly appreciated.

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

More information: iOS version of ApplicationDispatcher looks like this:

internal actual val ApplicationDispatcher: CoroutineDispatcher = NsQueueDispatcher(dispatch_get_main_queue())

internal class NsQueueDispatcher(private val dispatchQueue: dispatch_queue_t) : CoroutineDispatcher() {
    override fun dispatch(context: CoroutineContext, block: Runnable) {
        dispatch_async(dispatchQueue) {
            block.run()
        }
    }
}

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

If I replace NsQueueDispatcher with Dispatchers.Default on iOS side, my method gets called, but it still fails later on.

0x0000000105bca146 kfun:kotlin.Exception.<init>(kotlin.String?)kotlin.Exception + 70
0x0000000105bca066 kfun:kotlin.RuntimeException.<init>(kotlin.String?)kotlin.RuntimeException + 70
0x0000000105bd66b6 kfun:kotlin.IllegalStateException.<init>(kotlin.String?)kotlin.IllegalStateException + 70
0x0000000105cedbbf kfun:kotlinx.coroutines.takeEventLoop#internal + 239
0x0000000105ceda76 kfun:kotlinx.coroutines.DefaultExecutor.dispatch(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.Runnable) + 86
0x0000000105ce2e8c kfun:kotlinx.coroutines.resumeCancellable$kotlinx-coroutines-core-native@kotlin.coroutines.Continuation<#GENERIC>.(#GENERIC)Generic + 380
0x0000000105cebe56 kfun:kotlinx.coroutines.intrinsics.startCoroutineCancellable@kotlin.coroutines.SuspendFunction1<#GENERIC,#GENERIC>.(#GENERIC;kotlin.coroutines.Continuation<#GENERIC>)Generic + 118
0x0000000105cebd6b kfun:kotlinx.coroutines.CoroutineStart.invoke(kotlin.coroutines.SuspendFunction1<#GENERIC,#GENERIC>;#GENERIC;kotlin.coroutines.Continuation<#GENERIC>)Generic + 155
0x0000000105cebc8e kfun:kotlinx.coroutines.AbstractCoroutine.start(kotlinx.coroutines.CoroutineStart;#GENERIC;kotlin.coroutines.SuspendFunction1<#GENERIC,#GENERIC>)Generic + 110
0x0000000105cec260 kfun:kotlinx.coroutines.launch@kotlinx.coroutines.CoroutineScope.(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.Function1<kotlin.Throwable?,kotlin.Unit>?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,kotlin.Unit>)kotlinx.coroutines.Job + 288
0x0000000105cf3440 kfun:kotlinx.coroutines.launch$default@kotlinx.coroutines.CoroutineScope.(kotlin.coroutines.CoroutineContext;kotlinx.coroutines.CoroutineStart;kotlin.Function1<kotlin.Throwable?,kotlin.Unit>?;kotlin.coroutines.SuspendFunction1<kotlinx.coroutines.CoroutineScope,kotlin.Unit>;kotlin.Int)kotlinx.coroutines.Job + 384
0x0000000105b9d282 kfun:org.example.serialization.RESTClient.getFoo(kotlin.String?;kotlin.collections.Map<kotlin.String,kotlin.String>?;kotlin.Function2<org.example.serialization.Foo?,kotlin.Exception?,kotlin.Unit>) + 258

@Alex009
Copy link

Alex009 commented Oct 12, 2018

@yuriry for dispatch_async my worked coroutinedispatcher is:

class MainQueueDispatcher : ContinuationDispatcher() {
    private val mQueue = dispatch_get_main_queue()

    override fun <T> dispatchResume(value: T, continuation: Continuation<T>): Boolean {
        dispatch_async_f(mQueue,
                DetachedObjectGraph<Pair<T, Continuation<T>>>(TransferMode.UNSAFE) { Pair(value, continuation) }.asCPointer(),
                staticCFunction { it ->
                    initRuntimeIfNeeded()
                    val pair = DetachedObjectGraph<Pair<T, Continuation<T>>>(it).attach()
                    pair.second.resume(pair.first)
                })
        return true
    }

    override fun dispatchResumeWithException(exception: Throwable, continuation: Continuation<*>): Boolean {
        dispatch_async_f(mQueue,
                DetachedObjectGraph<Pair<Throwable, Continuation<*>>>(TransferMode.UNSAFE) { Pair(exception, continuation) }.asCPointer(),
                staticCFunction { it ->
                    initRuntimeIfNeeded()
                    val pair = DetachedObjectGraph<Pair<Throwable, Continuation<*>>>(it).attach()
                    pair.second.resumeWithException(pair.first)
                })
        return true
    }
}

i think it's can be unstable in some cases, but in my case it's work

@olonho
Copy link
Contributor

olonho commented Oct 12, 2018

Can you try build with 973cc7b?

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

@olonho I built the distribution when I had Xcode 9.x, but now with Xcode 10.0 there are many errors like below when I run ./gradlew bundle:

.../kotlin-native/runtime/src/main/cpp/Exceptions.cpp:16:10: fatal error: 'stdio.h' file not found
#include <stdio.h>
         ^~~~~~~~~
1 error generated.
In file included from .../kotlin-native/runtime/src/main/cpp/ObjCExport.mm:17:
.../kotlin-native/runtime/src/main/cpp/Types.h:20:10: fatal error: 'stdlib.h' file not found
#include <stdlib.h>
         ^~~~~~~~~~

I removed ~/.konan, re-run ./gradlew dependencies:update, but running ./gradlew bundle fails.
Edit: The failure is on master branch after syncing to 973cc7b

@olonho
Copy link
Contributor

olonho commented Oct 12, 2018

Likely you didn't run command-line tools installation, just run Xcode and it will prompt you to do so.

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

Installing Command Line tools now. Strange that when I had Xcode 9.x, the Kotlin Native distribution didn't compile with Command Line tools installed. I had to remove them and re-boot the laptop in order to compile.

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

Installed command line tools, installed updates, rebooted the laptop, removed ~/.konan, ran ./gradlew clean, ran ./gradlew dependencies:update. Still have similar issues when running ./gradlew bundle

> Task :common:android_arm32Hash FAILED
.../kotlin-native/common/src/hash/cpp/Base64.cpp:16:10: fatal error: 'string.h' file not found
#include <string.h>
         ^~~~~~~~~~
1 error generated.
.../kotlin-native/common/src/hash/cpp/Sha1.cpp:35:10: fatal error: 'stdio.h' file not found
#include <stdio.h>
         ^~~~~~~~~
1 error generated.
.../kotlin-native/common/src/hash/cpp/Names.cpp:16:10: fatal error: 'cassert' file not found
#include <cassert>
         ^~~~~~~~~
1 error generated.
.../kotlin-native/common/src/hash/cpp/City.cpp:18:10: fatal error: 'string.h' file not found
#include <string.h>
         ^~~~~~~~~~
1 error generated.

> Task :Interop:Runtime:compileCallbacksSharedLibraryCallbacksC FAILED
.../kotlin-native/Interop/Runtime/src/callbacks/c/callbacks.c:4:10: fatal error: 'ffi.h' file not found
#include <ffi.h>
         ^~~~~~~
1 error generated.

Any other suggestions?

@olonho
Copy link
Contributor

olonho commented Oct 12, 2018

It seems dependencies aren't downloaded yet. What's missing is android_arm32 deps.

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

@olonho In common subproject the following tasks fail with similar errors:

  • android_arm32Hash
  • android_arm64Hash
  • wasm32Hash
  • zephyr_stm32f4_discoHash

The following tasks succeed:

  • ios_arm32Hash
  • ios_arm64Hash
  • ios_x64Hash
  • macos_x64Hash

Where do dependencies for the failing tasks come from? Are they supposed to be downloaded by gradle or do I need to install them locally by hand?

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

The task :Interop:Runtime:compileCallbacksSharedLibraryCallbacksC always fails

> Task :Interop:Runtime:compileCallbacksSharedLibraryCallbacksC FAILED
.../kotlin-native/Interop/Runtime/src/callbacks/c/callbacks.c:4:10: fatal error: 'ffi.h' file not found
#include <ffi.h>
         ^~~~~~~
1 error generated.

ffi.h is located in

/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/win/x64/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/win/ia32/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/freebsd/x64/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/freebsd/ia32/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/mac/x64/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/mac/ia32/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/linux/x64/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/linux/ia32/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/linux/arm/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/solaris/x64/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/deps/libffi/config/solaris/ia32/ffi.h
/Applications/Xcode.app/Contents/Developer/usr/share/xcs/xcsd/node_modules/nodobjc/node_modules/ffi/src/ffi.h
/Applications/Xcode.app/Contents/Developer/Platforms/MacOSX.platform/Developer/SDKs/MacOSX.sdk/usr/include/ffi/ffi.h

And after installing Command Line Tools, it is also in

/Library/Developer/CommandLineTools/SDKs/MacOSX.sdk/usr/include/ffi/ffi.h

Could multiple ffi.h be the issue?

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

I removed Command Line Tools, removed Xcode, installed Xcode (v 10.0) again, removed ~/.m2, ~/.gradle and ~/.konan, rebooted the laptop, ran ./gradlew dependencies:update without problems, but ./gradlew bundle still fails

> Task :Interop:Runtime:compileCallbacksSharedLibraryCallbacksC FAILED
.../kotlin-native/Interop/Runtime/src/callbacks/c/callbacks.c:4:10: fatal error: 'ffi.h' file not found
#include <ffi.h>
         ^~~~~~~
1 error generated.

@yuriry
Copy link
Author

yuriry commented Oct 12, 2018

I noticed v1.0 was released which includes 973cc7b, so I synced to v1.0 tag and it seems to building fine (at least it is way past the previous point of failure)

@yuriry
Copy link
Author

yuriry commented Oct 15, 2018

@olonho After checking out v1.0 tag, the build of Kotlin Native compiler succeeded. But then when I tried to build iOS version of my app, I got undefined symbols errors similar to this one.

Then I re-compiled and published locally serialization, atomicFu, kotlinx-io, couroutines, and ktor, making sure only locally built dependencies are used to build each of the libraries. After that I was able to build iOS version of my app.

Now, when I run my app, I get the error shown below. I'm not sure if this is because I built and published the libraries from incorrect branches, or if this a legitimate bug. Any thoughts?

at 0   MyLib                            0x0000000108b61ad6 kfun:kotlin.Exception.<init>(kotlin.String?)kotlin.Exception + 70
at 1   MyLib                            0x0000000108b61a46 kfun:kotlin.RuntimeException.<init>(kotlin.String?)kotlin.RuntimeException + 70
at 2   MyLib                            0x0000000108c01d76 kfun:kotlin.native.IncorrectDereferenceException.<init>(kotlin.String)kotlin.native.IncorrectDereferenceException + 70
at 3   MyLib                            0x0000000108c024bc ThrowIllegalObjectSharingException + 300
at 4   MyLib                            0x0000000108c1ac3d _ZNK16KRefSharedHolder14verifyRefOwnerEv + 77
at 5   MyLib                            0x0000000108c1ad0a -[KotlinObjectHolder ref] + 26
at 6   MyLib                            0x0000000108d005ba platform_darwin_kniBridge206 + 90
at 7   MyLib                            0x0000000108d00ca9 __platform_darwin_kniBridge205_block_invoke + 25
at 8   libdispatch.dylib                   0x000000010fc575d1 _dispatch_call_block_and_release + 12
at 9   libdispatch.dylib                   0x000000010fc5863e _dispatch_client_callout + 8
at 10  libdispatch.dylib                   0x000000010fc659d6 _dispatch_main_queue_callback_4CF + 1541
at 11  CoreFoundation                      0x000000010add87f9 __CFRUNLOOP_IS_SERVICING_THE_MAIN_DISPATCH_QUEUE__ + 9
at 12  CoreFoundation                      0x000000010add2e86 __CFRunLoopRun + 2342
at 13  CoreFoundation                      0x000000010add2221 CFRunLoopRunSpecific + 625
at 14  GraphicsServices                    0x0000000114ee61dd GSEventRunModal + 62
at 15  UIKitCore                           0x0000000110bde115 UIApplicationMain + 140
at 16  libswiftUIKit.dylib                 0x000000010e97d92a $S5UIKit17UIApplicationMainys5Int32VAD_SpySpys4Int8VGGSgSSSgAJtF + 154
at 17  MyApp                          0x0000000104ba216f main + 255
at 18  libdyld.dylib                       0x000000010fcce551 start + 1

P.S. And there is no problem with Android app (as usual).

@SvyatoslavScherbina
Copy link
Collaborator

This stacktrace seems to be related to the problem you encountered before updating.
It means that your program tries to use non-shared objects from other threads.
In your case this is likely to be caused by creating RESTClient and calling getFoo from non-main thread.

@yuriry
Copy link
Author

yuriry commented Oct 15, 2018

@SvyatoslavScherbina Thank you for the reply. This is interesting. May be my efforts to rebuild Kotlin Native compiler, the plugin and all the libraries were not necessary?

I didn't realize the RESTClient should be created on main thread, and getFoo should also be called on main thread. This behavior is different from what is happening on Android. In both iOS and Android I create RESTClient and call getFoo on background threads. Android works, iOS does not. If I'm not mistaken, this is related to this comment and the linked issue.

I changed iOS code to create RESTClient and call getFoo on main thread:

DispatchQueue.main.async {
    let client = RESTClient(host:host)
    client.getFoo(path:path, extraHeaders:headers) { (foo:Foo?, error:KotlinException?) -> KotlinUnit in
        processFoo(foo)  // <-- breakpoint 1
        return KotlinUnit()
    }
}

After the change I get deserialized foo instance and can access all its fields in the debugger. Everything is fine at breakpoint 1. The next problem is that processFoo() needs to be called on a background thread. If I call it as shown above, the call happens on the main thread and assserts inside processFoo() halt the application.

My next step was to call processFoo() on the background thread:

DispatchQueue.main.async {
    let client = RESTClient(host:host)
    client.getFoo(path:path, extraHeaders:headers) { (foo:Foo?, error:KotlinException?) -> KotlinUnit in
        DispatchQueue.global().async { // <-- breakpoint 2
            processFoo(foo)            // <-- breakpoint 3
        }
        return KotlinUnit()
    }
}

At breakpoint 2 foo is accessible in the debugger, but when I try to access foo at breakpoint 3, I get the following error in the lldb console:

(lldb) p foo
error: Execution was interrupted, reason: internal c++ exception breakpoint(-4)..
The process has been returned to the state before expression evaluation.

If I continue the debugger, I get the same stack with ThrowIllegalObjectSharingException as in the earlier comment.

Any advice on how to correctly call processFoo() on a background thread would be greatly appreciated.

@yuriry
Copy link
Author

yuriry commented Oct 15, 2018

@SvyatoslavScherbina Is it possible that by the time the code on a background thread runs, the instance of Foo is already deallocated?

@yuriry
Copy link
Author

yuriry commented Oct 15, 2018

@SvyatoslavScherbina As an experiment, if I commented out asserts inside of processFoo(), and the method processes Foo instance without problems. But this includes access to CoreData, and we don't want to access Core Data in production on the main thread. Is it possible to pass an instance of Foo from the main thread back to some background thread?

(Allowing creating RESTClient and calling getFoo() on background thread would be even better (similarly how it works on Android/JVM))

@SvyatoslavScherbina
Copy link
Collaborator

May be my efforts to rebuild Kotlin Native compiler, the plugin and all the libraries were not necessary?

These efforts were absolutely useful, since we have improved incorrect object sharing diagnostics in the version you updated to. That's why you get an exception now instead of runtime assertion.

Any advice on how to correctly call processFoo() on a background thread would be greatly appreciated.

It depends on how your data is organized. Kotlin/Native imposes strict restrictions on sharing objects among different threads.
Does anything prevent you from parsing Foo on background thread just before calling processFoo? Does this parsing require anything except jsonText? If the answer is "no" in both cases, then I suppose I have simple solution for you.

@yuriry
Copy link
Author

yuriry commented Oct 16, 2018

Foo is parsed by RESTClient using kotlinx.serialization library, and this is the implementation of Foo.parse. processFoo() takes a parsed Foo instance, maps it to an instance of different Core Data class and saves it. May be this is not an ideal cut point, but our application is pretty large and we'd like to migrate it incrementally to Kotlin Native. Our current goal is to create a REST API layer that we can share between iOS and Android.

RESTClient is making HTTP calls and returns us parsed data, such as Foo. The existing part of the application picks up parsed data and continues processing (mapping to Core Data and saving), and this further processing should happen on a background thread. I hope this description makes sense, but I can elaborate more if required.

@SvyatoslavScherbina
Copy link
Collaborator

Do I understand correctly that Foo is not mutated (i.e. fields values aren't changed) after parsing?

@yuriry
Copy link
Author

yuriry commented Oct 16, 2018

Most data classes have only val fields. A few classes have var fields, but they are not modified after parsing. The classes with var fields are also used to construct requests, that's why the fields are var. But I think we can convert all of them to val fields with slight modification of the code that creates request objects.

@SvyatoslavScherbina
Copy link
Collaborator

But I think we can convert all of them to val fields

It is not necessary. Instead you can "freeze" Foo instance after parsing by calling .freeze() method on it. After this it becomes immutable (if you try to mutate it, then an exception will be thrown), but also can be properly used from any thread.

See documentation for more details.

@yuriry
Copy link
Author

yuriry commented Oct 16, 2018

@SvyatoslavScherbina I hope this is right, at least it works - Foo is accessible on a background thread. Thank you so much for your help!

Common

internal expect fun <T> freeze(t: T): T

Android

internal actual fun <T> freeze(t: T): T = t

iOS

import kotlin.native.concurrent.*

internal actual fun <T> freeze(t: T): T = t.freeze()

@SvyatoslavScherbina
Copy link
Collaborator

Thank you for your efforts that made it possible for us to find this solution!
Feel free to ask any questions if you encounter further issues with threads in Kotlin/Native.

@yuriry
Copy link
Author

yuriry commented Oct 17, 2018

I've made these notes mostly for my own future reference. Linking them here in case someone needs to build dependencies locally. The steps are probably not optimal, I'll improve them in the future if time permits. Thanks again for your help!

@olonho olonho closed this as completed Nov 22, 2018
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants