KT-51297: Allow suspend from any thread from ObjC
This patch adds a binary compiler argument to allow not crashing if a suspend function is called from a non-Main dispatcher from ObjC or Swift. This is relevant for people using the new memory model, in which this restriction may be relaxed.
This commit is contained in:
committed by
SvyatoslavScherbina
parent
2dac366dbc
commit
93cb4d71d8
+2
@@ -26,6 +26,8 @@ object BinaryOptions : BinaryOptionRegistry() {
|
||||
|
||||
val unitSuspendFunctionObjCExport by option<UnitSuspendFunctionObjCExport>()
|
||||
|
||||
val objcExportSuspendFunctionLaunchThreadRestriction by option<ObjCExportSuspendFunctionLaunchThreadRestriction>()
|
||||
|
||||
val gcSchedulerType by option<GCSchedulerType>()
|
||||
|
||||
val linkRuntime by option<RuntimeLinkageStrategyBinaryOption>()
|
||||
|
||||
+1
@@ -112,6 +112,7 @@ class KonanConfig(val project: Project, val configuration: CompilerConfiguration
|
||||
else -> WorkerExceptionHandling.LEGACY
|
||||
}
|
||||
val runtimeLogs: String? get() = configuration.get(KonanConfigKeys.RUNTIME_LOGS)
|
||||
val suspendFunctionsFromAnyThreadFromObjC: Boolean by lazy { configuration.get(BinaryOptions.objcExportSuspendFunctionLaunchThreadRestriction) == ObjCExportSuspendFunctionLaunchThreadRestriction.NONE }
|
||||
val freezing: Freezing by lazy {
|
||||
val freezingMode = configuration.get(BinaryOptions.freezing)
|
||||
when {
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
/*
|
||||
* Copyright 2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors.
|
||||
* Use of this source code is governed by the Apache 2.0 license that can be found in the license/LICENSE.txt file.
|
||||
*/
|
||||
|
||||
package org.jetbrains.kotlin.backend.konan
|
||||
|
||||
enum class ObjCExportSuspendFunctionLaunchThreadRestriction {
|
||||
/**
|
||||
* In this mode, suspend functions called from ObjC/Swift may only be called from the main thread
|
||||
*/
|
||||
MAIN,
|
||||
|
||||
/**
|
||||
* In this mode, suspend functions called from ObjC/Swift may be called from any thread
|
||||
*/
|
||||
NONE,
|
||||
;
|
||||
}
|
||||
+1
@@ -2775,6 +2775,7 @@ internal class CodeGeneratorVisitor(val context: Context, val lifetimes: Map<IrE
|
||||
overrideRuntimeGlobal("Kotlin_workerExceptionHandling", Int32(context.config.workerExceptionHandling.value))
|
||||
overrideRuntimeGlobal("Kotlin_freezingEnabled", Int32(if (context.config.freezing.enableFreezeAtRuntime) 1 else 0))
|
||||
overrideRuntimeGlobal("Kotlin_freezingChecksEnabled", Int32(if (context.config.freezing.enableFreezeChecks) 1 else 0))
|
||||
overrideRuntimeGlobal("Kotlin_suspendFunctionsFromAnyThreadFromObjC", Int32(if (context.config.suspendFunctionsFromAnyThreadFromObjC) 1 else 0))
|
||||
overrideRuntimeGlobal("Kotlin_gcSchedulerType", Int32(context.config.gcSchedulerType.value))
|
||||
val getSourceInfoFunctionName = when (context.config.sourceInfoType) {
|
||||
SourceInfoType.NOOP -> null
|
||||
|
||||
@@ -5564,8 +5564,10 @@ if (isAppleTarget(project)) {
|
||||
artifact = frameworkArtifactName
|
||||
library = libraryName
|
||||
isStatic = true
|
||||
opts = ["-Xbinary=objcExportSuspendFunctionLaunchThreadRestriction=none"]
|
||||
}
|
||||
swiftSources = ['objcexport']
|
||||
swiftExtraOpts = [ '-D', 'ALLOW_SUSPEND_ANY_THREAD' ]
|
||||
if (isNoopGC) {
|
||||
swiftExtraOpts += ["-D", "NOOP_GC"]
|
||||
}
|
||||
|
||||
@@ -48,6 +48,41 @@ private func testUnitCallSimple() throws {
|
||||
try assertNil(error)
|
||||
}
|
||||
|
||||
private func testUnitCallNonMainDispatcher() throws {
|
||||
#if LEGACY_SUSPEND_UNIT_FUNCTION_EXPORT
|
||||
var result: KotlinUnit? = nil
|
||||
#endif
|
||||
var error: Error? = nil
|
||||
var completionCalled = 0
|
||||
|
||||
let group = DispatchGroup()
|
||||
|
||||
#if ALLOW_SUSPEND_ANY_THREAD
|
||||
#if LEGACY_SUSPEND_UNIT_FUNCTION_EXPORT
|
||||
DispatchQueue.global().async(group: group) {
|
||||
CoroutinesKt.unitSuspendFun { _result, _error in
|
||||
completionCalled += 1
|
||||
result = _result
|
||||
error = _error
|
||||
}
|
||||
}
|
||||
#else
|
||||
DispatchQueue.global().async(group: group) {
|
||||
CoroutinesKt.unitSuspendFun { _error in
|
||||
completionCalled += 1
|
||||
error = _error
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
|
||||
#if ALLOW_SUSPEND_ANY_THREAD
|
||||
group.wait()
|
||||
try assertEquals(actual: completionCalled, expected: 1)
|
||||
#endif
|
||||
}
|
||||
|
||||
|
||||
private func testCallSuspendFun(doSuspend: Bool, doThrow: Bool) throws {
|
||||
class C {}
|
||||
let expectedResult = C()
|
||||
@@ -630,6 +665,7 @@ class CoroutinesTests : SimpleTestProvider {
|
||||
|
||||
test("TestCallSimple", testCallSimple)
|
||||
test("TestCallUnitSimple", testUnitCallSimple)
|
||||
test("TestCallFromNonMainDispatcher", testUnitCallNonMainDispatcher)
|
||||
test("TestCall", testCall)
|
||||
test("TestCallChain", testCallChain)
|
||||
test("TestOverride", testOverride)
|
||||
@@ -640,4 +676,4 @@ class CoroutinesTests : SimpleTestProvider {
|
||||
test("TestKSuspendFunctionType", testSuspendFunctionType)
|
||||
test("TestSuspendFunctionSwiftImpl", testSuspendFunctionSwiftImpl)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,6 +16,7 @@ RUNTIME_WEAK int32_t Kotlin_gcSchedulerType = 2;
|
||||
RUNTIME_WEAK int32_t Kotlin_workerExceptionHandling = 0;
|
||||
RUNTIME_WEAK int32_t Kotlin_freezingEnabled = 1;
|
||||
RUNTIME_WEAK int32_t Kotlin_freezingChecksEnabled = 1;
|
||||
RUNTIME_WEAK int32_t Kotlin_suspendFunctionsFromAnyThreadFromObjC = 0;
|
||||
RUNTIME_WEAK const Kotlin_getSourceInfo_FunctionType Kotlin_getSourceInfo_Function = nullptr;
|
||||
#ifdef KONAN_ANDROID
|
||||
RUNTIME_WEAK int32_t Kotlin_printToAndroidLogcat = 1;
|
||||
@@ -37,6 +38,10 @@ ALWAYS_INLINE bool compiler::freezingChecksEnabled() noexcept {
|
||||
return Kotlin_freezingChecksEnabled != 0;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE bool compiler::suspendFunctionsFromAnyThreadFromObjCEnabled() noexcept {
|
||||
return Kotlin_suspendFunctionsFromAnyThreadFromObjC != 0;
|
||||
}
|
||||
|
||||
ALWAYS_INLINE compiler::GCSchedulerType compiler::getGCSchedulerType() noexcept {
|
||||
return static_cast<compiler::GCSchedulerType>(Kotlin_gcSchedulerType);
|
||||
}
|
||||
|
||||
@@ -80,6 +80,7 @@ ALWAYS_INLINE inline std::string_view runtimeLogs() noexcept {
|
||||
|
||||
bool freezingEnabled() noexcept;
|
||||
bool freezingChecksEnabled() noexcept;
|
||||
bool suspendFunctionsFromAnyThreadFromObjCEnabled() noexcept;
|
||||
|
||||
|
||||
ALWAYS_INLINE inline int getSourceInfo(void* addr, SourceInfo *result, int result_size) {
|
||||
|
||||
@@ -37,7 +37,7 @@ extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgumentImpl,
|
||||
KRef completionHolder, const TypeInfo** exceptionTypes);
|
||||
|
||||
extern "C" OBJ_GETTER(Kotlin_ObjCExport_createContinuationArgument, id completion, const TypeInfo** exceptionTypes) {
|
||||
if (pthread_main_np() != 1) {
|
||||
if (!kotlin::compiler::suspendFunctionsFromAnyThreadFromObjCEnabled() && pthread_main_np() != 1) {
|
||||
[NSException raise:NSGenericException
|
||||
format:@"Calling Kotlin suspend functions from Swift/Objective-C is currently supported only on main thread"];
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user