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

Enums accessed from Swift from a non-main thread crash #3353

Closed
kpgalligan opened this issue Sep 18, 2019 · 6 comments
Closed

Enums accessed from Swift from a non-main thread crash #3353

kpgalligan opened this issue Sep 18, 2019 · 6 comments

Comments

@kpgalligan
Copy link
Contributor

kpgalligan commented Sep 18, 2019

If you access an enum initially from Swift (and presumably Objc), from a non-main thread, and you got there by way of gcd, you will likely get a crash when that thread shuts down.

If you access any part of that enum from the main thread prior, this does not happen. Also, accessing from kotlin and using a Worker, this does not appear to happen.

I've tested this on 1.3.50 and master commit d153f21d5fbf6e375a4f1dbaaf86d30cc51131c7, which is almost head of master. I could not trigger the same issue on 1.3.41 or 1.3.30.

In kotlin from commonMain

enum class Hey {
    A, B, C
}

In Swift

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        DispatchQueue.global(qos: .background).async { 
            let ee = Hey.b
            print("Hey \(ee)")
        }
        
        return true
    }

Console

/Users/[my local build path]/runtime/src/main/cpp/Memory.cpp:1703: runtime assert: Memory leaks found

Stack

#0	0x000000010df540dd in __abort ()
#1	0x000000010df5402c in abort ()
#2	0x000000010b8cec19 in konan::abort() ()
#3	0x000000010b8cea70 in RuntimeAssertFailed(char const*, char const*) ()
#4	0x000000010b8cffe5 in DeinitMemory ()
#5	0x000000010b8cfad0 in (anonymous namespace)::deinitRuntime(RuntimeState*) ()
#6	0x000000010b8cf9bc in konan::onThreadExitCallback(void*) ()
#7	0x000000010e1e2660 in _pthread_tsd_cleanup ()
#8	0x000000010e1e5655 in _pthread_exit ()
#9	0x000000010e1e243a in _pthread_wqthread_exit ()
#10	0x000000010e1e1644 in _pthread_wqthread ()
#11	0x000000010e1e13fd in start_wqthread ()

Presumably onThreadExitCallback is called as that thread is shut down, and there's something off about how the enum is accessed and counted.

Accessing the enum in the main thread appears to prevent the issue.

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        let nah = Hey.b
        
        DispatchQueue.global(qos: .background).async { // sends registration to background queue
            let ee = Hey.b
            print("Hey \(ee)")
        }
        
        return true
    }

Repro code: https://github.com/touchlab/KNEnumCrash

The crash will not happen every time, just fyi. Generally a little better than 50/50.

@olonho
Copy link
Contributor

olonho commented Sep 23, 2019

With #3368 there's API to hide memory leak related crashes, as needed.

@kpgalligan
Copy link
Contributor Author

I'm rebuilding master and will try to repro with the memory checker off. Is this also something that will be fixed, or do we just need to disable memory checking? This is a pretty obscure bud to figure out.

@kpgalligan
Copy link
Contributor Author

I understand this better now! The reason the memory check happens is because it's the last memory cleanup lastMemoryState. Accessing enums from swift does seem to create memory that isn't deallocated, but most apps in practice won't ever see that issue because they're accessing something from kotlin in the main thread, and therefore avoid the lastMemoryState check (unless it's a command line app for macos or similar).

Setting isMemoryLeakCheckerActive to false will avoid the issue, but a far simpler workaround in this case is to "touch" Kotlin in the main thread. Call a noop method, or something similar. That will prevent lastMemoryState from being true during thread shutdown.

In Kotlin:

fun touch(){}

In Swift:

func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        
        SampleKt.touch()
        DispatchQueue.global(qos: .background).async { // sends registration to background queue
            let ee = Hey.b
            print("Hey \(ee)")
        }
        
        return true
    }

It does seem like the objc interop is allocating something to generate the enums, which does seem like it shouldn't count towards "allocation" and memory leaks, but I don't really understand how that process works for interop.

@benasher44
Copy link
Contributor

@olonho will this API make it into 1.3.60?

@SvyatoslavScherbina
Copy link
Collaborator

Fixed in 1.4.30 with #4482.

@SvyatoslavScherbina
Copy link
Collaborator

Update: the crash is fixed, but it is still recommended to "touch" Kotlin in the main thread.
This won't be required in new memory manager.

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