buresu / ndi-python

NewTek NDI Python wrapper
MIT License
138 stars 31 forks source link

Ndi-python leaves the GIL locked during all library calls #38

Open JC3 opened 1 month ago

JC3 commented 1 month ago

Problem

It appears that ndi-python leaves the GIL locked during all library calls, which halts all other Python threads while NDIlib calls are being executed.

Solution

The GIL should be released during long-running, blocking library calls.

See https://docs.python.org/3/c-api/init.html#releasing-the-gil-from-extension-code for information about threading compatibility in extension libraries.

It looks like somebody has already implemented this in https://github.com/buresu/ndi-python/pull/22, it may be worth reviewing and potentially merging that PR then releasing an update.

Example

For example, the following program creates a thread that repeatedly calls find_wait_for_sources while continuously printing messages from the main thread (Python 3.10):

import threading
import time
import NDIlib as ndi

def p (message: str) -> None:
    print(f"[{time.time():.3f}] {message}")

def threadproc () -> None:
    finder = ndi.find_create_v2()
    while True:
        p("find_wait_for_sources enter")
        ndi.find_wait_for_sources(finder, 3000)
        p("find_wait_for_sources leave")

ndi.initialize()
thread = threading.Thread(target=threadproc)
thread.start()
while True:
    p("ding")
    time.sleep(0.1)

The expected output of this program would be "ding" every 1/10th of a second, but the actual output (notice the timestamps) is:

[1727534088.330] find_wait_for_sources enter
[1727534091.330] ding
[1727534091.330] find_wait_for_sources leave
[1727534091.330] find_wait_for_sources enter
[1727534094.331] ding
[1727534094.331] find_wait_for_sources leave
[1727534094.331] find_wait_for_sources enter
[1727534097.336] ding
[1727534097.336] find_wait_for_sources leave
[1727534097.336] find_wait_for_sources enter
[1727534100.337] ding
[1727534100.337] find_wait_for_sources leave
[1727534100.337] find_wait_for_sources enter
[1727534103.341] ding
[1727534103.341] find_wait_for_sources leave
[1727534103.341] find_wait_for_sources enter
[1727534106.341] ding
[1727534106.341] find_wait_for_sources leave

This shows that the main thread is not executing while the library is inside the find_wait_for_sources call, which is indicative of the GIL not being released during the call.

Consequence

It is impossible to use ndi-python in a threaded context. For moderately complex applications this is a showstopper, especially when performance is critical.

The only workaround is to use ndi-python in its own process and build all the IPC architecture necessary to shuffle frames around between processes, which is very cumbersome.