Skip to content

Commit 882466b

Browse files
authored
lazy ffi dlopen (#567)
1 parent 20d73ec commit 882466b

3 files changed

Lines changed: 59 additions & 45 deletions

File tree

livekit-rtc/livekit/rtc/_ffi_client.py

Lines changed: 46 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -34,22 +34,27 @@
3434
atexit.register(_resource_files.close)
3535

3636

37+
def _lib_name():
38+
if platform.system() == "Linux":
39+
return "liblivekit_ffi.so"
40+
elif platform.system() == "Darwin":
41+
return "liblivekit_ffi.dylib"
42+
elif platform.system() == "Windows":
43+
return "livekit_ffi.dll"
44+
return None
45+
46+
3747
def get_ffi_lib():
3848
# allow to override the lib path using an env var
3949
libpath = os.environ.get("LIVEKIT_LIB_PATH", "").strip()
4050
if libpath:
4151
return ctypes.CDLL(libpath)
4252

43-
if platform.system() == "Linux":
44-
libname = "liblivekit_ffi.so"
45-
elif platform.system() == "Darwin":
46-
libname = "liblivekit_ffi.dylib"
47-
elif platform.system() == "Windows":
48-
libname = "livekit_ffi.dll"
49-
else:
53+
libname = _lib_name()
54+
if libname is None:
5055
raise Exception(
51-
f"no ffi library found for platform {platform.system()}. \
52-
Set LIVEKIT_LIB_PATH to specify a the lib path"
56+
f"no ffi library found for platform {platform.system()}. "
57+
"Set LIVEKIT_LIB_PATH to specify the lib path"
5358
)
5459

5560
res = importlib.resources.files("livekit.rtc.resources") / libname
@@ -58,32 +63,8 @@ def get_ffi_lib():
5863
return ctypes.CDLL(str(path))
5964

6065

61-
ffi_lib = get_ffi_lib()
6266
ffi_cb_fnc = ctypes.CFUNCTYPE(None, ctypes.POINTER(ctypes.c_uint8), ctypes.c_size_t)
6367

64-
# C function types
65-
ffi_lib.livekit_ffi_initialize.argtypes = [
66-
ffi_cb_fnc,
67-
ctypes.c_bool,
68-
ctypes.c_char_p,
69-
ctypes.c_char_p,
70-
]
71-
72-
ffi_lib.livekit_ffi_request.argtypes = [
73-
ctypes.POINTER(ctypes.c_ubyte),
74-
ctypes.c_size_t,
75-
ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)),
76-
ctypes.POINTER(ctypes.c_size_t),
77-
]
78-
ffi_lib.livekit_ffi_request.restype = ctypes.c_uint64
79-
80-
ffi_lib.livekit_ffi_drop_handle.argtypes = [ctypes.c_uint64]
81-
ffi_lib.livekit_ffi_drop_handle.restype = ctypes.c_bool
82-
83-
84-
ffi_lib.livekit_ffi_dispose.argtypes = []
85-
ffi_lib.livekit_ffi_dispose.restype = None
86-
8768
INVALID_HANDLE = 0
8869

8970

@@ -102,7 +83,7 @@ def disposed(self) -> bool:
10283
def dispose(self) -> None:
10384
if self.handle != INVALID_HANDLE and not self._disposed:
10485
self._disposed = True
105-
assert ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
86+
assert FfiClient.instance._ffi_lib.livekit_ffi_drop_handle(ctypes.c_uint64(self.handle))
10687

10788
def __repr__(self) -> str:
10889
return f"FfiHandle({self.handle})"
@@ -214,10 +195,39 @@ def __init__(self) -> None:
214195
self._lock = threading.RLock()
215196
self._queue = FfiQueue[proto_ffi.FfiEvent]()
216197

217-
ffi_lib.livekit_ffi_initialize(
198+
try:
199+
self._ffi_lib = get_ffi_lib()
200+
except Exception as e:
201+
libname = _lib_name() or "livekit_ffi"
202+
raise ImportError(
203+
"failed to load %s: %s\n"
204+
"Install the livekit package with: pip install livekit\n"
205+
"Or set LIVEKIT_LIB_PATH to the path of the native library." % (libname, e)
206+
) from None
207+
self._ffi_lib.livekit_ffi_initialize.argtypes = [
208+
ffi_cb_fnc,
209+
ctypes.c_bool,
210+
ctypes.c_char_p,
211+
ctypes.c_char_p,
212+
]
213+
self._ffi_lib.livekit_ffi_request.argtypes = [
214+
ctypes.POINTER(ctypes.c_ubyte),
215+
ctypes.c_size_t,
216+
ctypes.POINTER(ctypes.POINTER(ctypes.c_ubyte)),
217+
ctypes.POINTER(ctypes.c_size_t),
218+
]
219+
self._ffi_lib.livekit_ffi_request.restype = ctypes.c_uint64
220+
self._ffi_lib.livekit_ffi_drop_handle.argtypes = [ctypes.c_uint64]
221+
self._ffi_lib.livekit_ffi_drop_handle.restype = ctypes.c_bool
222+
self._ffi_lib.livekit_ffi_dispose.argtypes = []
223+
self._ffi_lib.livekit_ffi_dispose.restype = None
224+
225+
self._ffi_lib.livekit_ffi_initialize(
218226
ffi_event_callback, True, b"python", __version__.encode("ascii")
219227
)
220228

229+
ffi_lib = self._ffi_lib
230+
221231
@atexit.register
222232
def _dispose_lk_ffi():
223233
ffi_lib.livekit_ffi_dispose()
@@ -233,7 +243,7 @@ def request(self, req: proto_ffi.FfiRequest) -> proto_ffi.FfiResponse:
233243

234244
resp_ptr = ctypes.POINTER(ctypes.c_ubyte)()
235245
resp_len = ctypes.c_size_t()
236-
handle = ffi_lib.livekit_ffi_request(
246+
handle = self._ffi_lib.livekit_ffi_request(
237247
data, proto_len, ctypes.byref(resp_ptr), ctypes.byref(resp_len)
238248
)
239249
assert handle != INVALID_HANDLE

livekit-rtc/livekit/rtc/participant.py

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -318,9 +318,7 @@ async def perform_rpc(
318318
queue = FfiClient.instance.queue.subscribe()
319319
try:
320320
resp = FfiClient.instance.request(req)
321-
cb = await queue.wait_for(
322-
lambda e: (e.perform_rpc.async_id == resp.perform_rpc.async_id)
323-
)
321+
cb = await queue.wait_for(lambda e: e.perform_rpc.async_id == resp.perform_rpc.async_id)
324322
finally:
325323
FfiClient.instance.queue.unsubscribe(queue)
326324

tests/rtc/test_e2e.py

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -314,9 +314,11 @@ def on_room2_track_unpublished(
314314
await room1.connect(url, token1)
315315

316316
await assert_eventually(
317-
lambda: len(events["connection_state_changed"]) > 0
318-
and events["connection_state_changed"][-1]
319-
== f"room1-{rtc.ConnectionState.CONN_CONNECTED}",
317+
lambda: (
318+
len(events["connection_state_changed"]) > 0
319+
and events["connection_state_changed"][-1]
320+
== f"room1-{rtc.ConnectionState.CONN_CONNECTED}"
321+
),
320322
message="room1 connection_state_changed event not fired or did not reach CONN_CONNECTED state",
321323
)
322324

@@ -370,9 +372,13 @@ def on_room2_track_unpublished(
370372
await room1.disconnect()
371373

372374
await assert_eventually(
373-
lambda: lambda: len(events["connection_state_changed"]) > 0
374-
and events["connection_state_changed"][-1]
375-
== f"room1-{rtc.ConnectionState.CONN_DISCONNECTED}",
375+
lambda: (
376+
lambda: (
377+
len(events["connection_state_changed"]) > 0
378+
and events["connection_state_changed"][-1]
379+
== f"room1-{rtc.ConnectionState.CONN_DISCONNECTED}"
380+
)
381+
),
376382
message="room1 disconnected event not fired",
377383
)
378384

0 commit comments

Comments
 (0)