Closed yuriry closed 5 years ago
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()
}
}
}
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
@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
Can you try build with 973cc7bbdb0333b4d1e241c59499e88c086fb02c?
@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
Likely you didn't run command-line tools installation, just run Xcode and it will prompt you to do so.
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.
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?
It seems dependencies aren't downloaded yet. What's missing is android_arm32 deps.
@olonho In common
subproject the following tasks fail with similar errors:
The following tasks succeed:
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?
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?
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.
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)
@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).
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.
@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.
@SvyatoslavScherbina Is it possible that by the time the code on a background thread runs, the instance of Foo
is already deallocated?
@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))
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.
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.
Do I understand correctly that Foo
is not mutated (i.e. fields values aren't changed) after parsing?
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.
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.
@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()
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.
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!
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:My current version information is as follows:
Any help on how to properly initialize treads when calling Kotlin methods on iOS would be greatly appreciated.