diff --git a/livekit-rtc/livekit/rtc/_ffi_client.py b/livekit-rtc/livekit/rtc/_ffi_client.py index 3ae44aac..aab703f6 100644 --- a/livekit-rtc/livekit/rtc/_ffi_client.py +++ b/livekit-rtc/livekit/rtc/_ffi_client.py @@ -288,7 +288,7 @@ def request(self, req: proto_ffi.FfiRequest) -> proto_ffi.FfiResponse: ) proto_data = req.SerializeToString() proto_len = len(proto_data) - data = (ctypes.c_ubyte * proto_len)(*proto_data) + data = ctypes.cast(ctypes.c_char_p(proto_data), ctypes.POINTER(ctypes.c_ubyte)) resp_ptr = ctypes.POINTER(ctypes.c_ubyte)() resp_len = ctypes.c_size_t() diff --git a/livekit-rtc/tests/test_ffi_client.py b/livekit-rtc/tests/test_ffi_client.py new file mode 100644 index 00000000..53873884 --- /dev/null +++ b/livekit-rtc/tests/test_ffi_client.py @@ -0,0 +1,44 @@ +# Copyright 2026 LiveKit, Inc. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""FfiClient.request tests against the native library.""" + +import subprocess +import sys + +# Runs in a fresh interpreter: ctypes reuses a `c_ubyte * n` type that another +# live object still holds, which would hide a new one made per request. +_COUNT_ARRAY_TYPES_IN_GARBAGE = """ +import ctypes +import gc + +from livekit import rtc + +gc.collect() +gc.set_debug(gc.DEBUG_SAVEALL) +for _ in range(50): + rtc.AudioProcessingModule() +gc.collect() +print(sum(isinstance(o, type(ctypes.Array)) for o in gc.garbage)) +""" + + +def test_request_leaves_no_ctypes_array_type_in_garbage() -> None: + result = subprocess.run( + [sys.executable, "-c", _COUNT_ARRAY_TYPES_IN_GARBAGE], + capture_output=True, + text=True, + check=True, + ) + assert result.stdout.strip() == "0"