Skip to content

Commit 071beff

Browse files
committed
support weakref for methods
1 parent 63c785c commit 071beff

File tree

2 files changed

+76
-1
lines changed

2 files changed

+76
-1
lines changed

getstream/video/rtc/participants.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
from typing import Optional, Callable, List
2+
import inspect
23
import weakref
34

45
from pyee.asyncio import AsyncIOEventEmitter
@@ -90,7 +91,13 @@ def cleanup_callback(ref):
9091
# Create a weak reference to the handler
9192
# The Subscription object will hold a strong reference to the handler
9293
# to prevent it from being garbage collected (important for inline lambdas)
93-
handler_ref = weakref.ref(handler, cleanup_callback)
94+
# Use WeakMethod for bound methods, ref for other callables
95+
if inspect.ismethod(handler) or (
96+
hasattr(handler, "__self__") and hasattr(handler, "__func__")
97+
):
98+
handler_ref = weakref.WeakMethod(handler, cleanup_callback)
99+
else:
100+
handler_ref = weakref.ref(handler, cleanup_callback)
94101
self._map_handlers.append(handler_ref)
95102

96103
# Call handler immediately with current list

tests/rtc/test_participants.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -403,3 +403,71 @@ def test_get_stream_id_from_track_id(self):
403403

404404
stream_id = state.get_stream_id_from_track_id("track3")
405405
assert stream_id is None
406+
407+
def test_map_with_bound_method(self):
408+
"""Test that map works with bound methods."""
409+
state = ParticipantsState()
410+
411+
class Handler:
412+
def __init__(self):
413+
self.results = []
414+
415+
def on_participants(self, participants):
416+
self.results.append(len(participants))
417+
418+
handler_obj = Handler()
419+
420+
# Subscribe with a bound method
421+
_subscription = state.map(handler_obj.on_participants)
422+
423+
# Should be called immediately
424+
assert handler_obj.results == [0]
425+
426+
# Add a participant
427+
p1 = models_pb2.Participant()
428+
p1.user_id = "user1"
429+
p1.track_lookup_prefix = "prefix1"
430+
state._add_participant(p1)
431+
432+
# Handler should be called
433+
assert handler_obj.results == [0, 1]
434+
435+
# Add another participant
436+
p2 = models_pb2.Participant()
437+
p2.user_id = "user2"
438+
p2.track_lookup_prefix = "prefix2"
439+
state._add_participant(p2)
440+
441+
# Handler should be called again
442+
assert handler_obj.results == [0, 1, 2]
443+
444+
def test_weak_reference_cleanup_with_bound_method(self):
445+
"""Test that bound method handlers are cleaned up when object is garbage collected."""
446+
state = ParticipantsState()
447+
448+
class Handler:
449+
def __init__(self):
450+
self.results = []
451+
452+
def on_participants(self, participants):
453+
self.results.append(len(participants))
454+
455+
handler_obj = Handler()
456+
_subscription = state.map(handler_obj.on_participants)
457+
458+
assert handler_obj.results == [0]
459+
assert len(state._map_handlers) == 1
460+
461+
# Delete the handler object and subscription, force garbage collection
462+
del handler_obj
463+
del _subscription
464+
gc.collect()
465+
466+
# Handler should be cleaned up
467+
p1 = models_pb2.Participant()
468+
p1.user_id = "user1"
469+
p1.track_lookup_prefix = "prefix1"
470+
state._add_participant(p1)
471+
472+
# No handlers should remain
473+
assert len(state._map_handlers) == 0

0 commit comments

Comments
 (0)