Swift and Python: get a RunLoop that can run in threading

Viewed 68

I am trying to write a Swift standalone library to monitor a NSUserDefaults value and callback a function in Python. To do so, I need an event loop in the Swift code that allows the Observer code to run and, when needed, call the callback function.

Swift provides RunLoop for this purpose, which works fine also when interfaced with ctypes from the main Python thread. However, the moment I wrap my calls in threading to avoid blocking the main Python code (the whole point of callbacks), RunLoop also returns immediately and becomes useless.

The following contains a minimal example to reproduce this behavior. No functional code is included, just the loop and its invocations from Python.

  • runLoop.swift
import Foundation

@_silgen_name("run")
public func run() -> Void {
    // Some convenience code to allow interrupting with Ctrl-C from Python
    let signalCallback: sig_t = { signal in
        exit(signal)
    }
    signal(SIGINT, signalCallback)

    RunLoop.current.run()
}

Build this by running swiftc -emit-library -o ./loop runLoop.swift

  • pyrun.py
import ctypes
import threading

loop = ctypes.cdll.LoadLibrary('./loop')
loop.run.argtypes = None
loop.run.restype = ctypes.c_void_p

#loop.run() #this works

t = threading.Thread(target=loop.run)
t.start()
t.join()

Run this via python3 pyrun.py.

Running the code as it is results in immediate return. On the other hand, when uncommenting the loop.run(), the Swift loop correctly runs indefinitely.

Funny thing, if multiprocessing is used instead of threading, the Swift loop continues to work, so this must be something very specific to how threading is creating the threads. Just FYI, the same happens with Objective-C and [[NSRunLoop currentRunLoop] run];.

1 Answers

This is not related to Python, but is behaviour of runloops themselves. I get the same behaviour when using an ObjC program that uses pthread_start to create a thread that runs the runloop.

The -[NSRunLoop run] method will return immediately when there are no timers or sources attached to the runloop (see Apple's documentation)

There's probably an input source attached to the main thread, although I don't know how to check for this.

Related