@@ -189,6 +189,7 @@ def __init__(
189189 self ._connection_state = ConnectionState .CONN_DISCONNECTED
190190 self ._first_sid_future = asyncio .Future [str ]()
191191 self ._local_participant : LocalParticipant | None = None
192+ self ._is_connecting : bool = False
192193
193194 # active incoming stream readers, keyed by FFI reader handle id
194195 self ._text_stream_readers : Dict [int , TextStreamReader ] = {}
@@ -209,10 +210,16 @@ async def sid(self) -> str:
209210
210211 Returns:
211212 str: The session ID of the room.
213+
214+ Raises:
215+ RuntimeError: If the room is disconnected and no SID was received.
212216 """
213217 if self ._info .sid :
214218 return self ._info .sid
215219
220+ if self ._connection_state == ConnectionState .CONN_DISCONNECTED and not self ._is_connecting :
221+ raise RuntimeError ("cannot get sid of a disconnected room" )
222+
216223 # This future is shared state for the Room. Caller-side timeouts, e.g.
217224 # asyncio.wait_for(room.sid, ...), should not cancel it for future waiters.
218225 return await asyncio .shield (self ._first_sid_future )
@@ -221,8 +228,16 @@ def _resolve_first_sid(self, sid: str) -> None:
221228 # The room SID can arrive through the initial connect result or later
222229 # room update events. Resolve the first-SID waiter from any source that
223230 # carries it so waiters do not depend on event ordering.
224- if sid and not self ._first_sid_future .done ():
225- self ._first_sid_future .set_result (sid )
231+ if sid :
232+ self ._info .sid = sid
233+ if not self ._first_sid_future .done ():
234+ self ._first_sid_future .set_result (sid )
235+
236+ def _fail_first_sid (self , exc : BaseException ) -> None :
237+ if not self ._first_sid_future .done ():
238+ self ._first_sid_future .set_exception (exc )
239+ # Retrieve the exception to suppress unretrieved future warnings if unawaited
240+ self ._first_sid_future .exception ()
226241
227242 @property
228243 def local_participant (self ) -> LocalParticipant :
@@ -469,6 +484,9 @@ def on_participant_connected(participant):
469484 """
470485 self ._server_url = url
471486 self ._token = token
487+ self ._is_connecting = True
488+ if self ._first_sid_future .done () and not self ._info .sid :
489+ self ._first_sid_future = asyncio .Future [str ]()
472490 req = proto_ffi .FfiRequest ()
473491 req .connect .url = url
474492 req .connect .token = token
@@ -560,43 +578,55 @@ def on_participant_connected(participant):
560578 ),
561579 )
562580
563- queue = FfiClient .instance .queue .subscribe ()
564581 try :
565- resp = FfiClient .instance .request (req )
566- cb : proto_ffi .FfiEvent = await queue .wait_for (
567- lambda e : e .connect .async_id == resp .connect .async_id
568- )
569- finally :
570- FfiClient .instance .queue .unsubscribe (queue )
571-
572- if cb .connect .error :
573- FfiClient .instance .queue .unsubscribe (self ._ffi_queue )
574- raise ConnectError (cb .connect .error )
575-
576- self ._ffi_handle = FfiHandle (cb .connect .result .room .handle .id )
582+ queue = FfiClient .instance .queue .subscribe ()
583+ try :
584+ resp = FfiClient .instance .request (req )
585+ cb : proto_ffi .FfiEvent = await queue .wait_for (
586+ lambda e : e .connect .async_id == resp .connect .async_id
587+ )
588+ finally :
589+ FfiClient .instance .queue .unsubscribe (queue )
577590
578- self . _e2ee_manager = E2EEManager (
579- self . _ffi_handle . handle , options . encryption or options . e2ee
580- )
591+ if cb . connect . error :
592+ FfiClient . instance . queue . unsubscribe ( self . _ffi_queue )
593+ raise ConnectError ( cb . connect . error )
581594
582- self ._info = cb .connect .result .room .info
583- self ._resolve_first_sid (self ._info .sid )
584- self ._connection_state = ConnectionState .CONN_CONNECTED
595+ self ._ffi_handle = FfiHandle (cb .connect .result .room .handle .id )
585596
586- self ._local_participant = LocalParticipant (
587- self ._room_queue , cb . connect . result . local_participant
588- )
597+ self ._e2ee_manager = E2EEManager (
598+ self ._ffi_handle . handle , options . encryption or options . e2ee
599+ )
589600
590- for pt in cb .connect .result .participants :
591- rp = self ._create_remote_participant (pt .participant )
601+ self ._info = cb .connect .result .room .info
602+ self ._resolve_first_sid (self ._info .sid )
603+ self ._connection_state = ConnectionState .CONN_CONNECTED
592604
593- # add the initial remote participant tracks
594- for owned_publication_info in pt .publications :
595- publication = RemoteTrackPublication (owned_publication_info )
596- rp ._track_publications [publication .sid ] = publication
605+ self ._local_participant = LocalParticipant (
606+ self ._room_queue , cb .connect .result .local_participant
607+ )
597608
598- # start listening to room events
599- self ._task = self ._loop .create_task (self ._listen_task ())
609+ for pt in cb .connect .result .participants :
610+ rp = self ._create_remote_participant (pt .participant )
611+
612+ # add the initial remote participant tracks
613+ for owned_publication_info in pt .publications :
614+ publication = RemoteTrackPublication (owned_publication_info )
615+ rp ._track_publications [publication .sid ] = publication
616+
617+ # start listening to room events
618+ self ._task = self ._loop .create_task (self ._listen_task ())
619+ except BaseException as e :
620+ if self ._ffi_handle is None and hasattr (self , "_ffi_queue" ):
621+ FfiClient .instance .queue .unsubscribe (self ._ffi_queue )
622+ self ._fail_first_sid (
623+ RuntimeError ("room connection was cancelled" )
624+ if isinstance (e , asyncio .CancelledError )
625+ else e
626+ )
627+ raise
628+ finally :
629+ self ._is_connecting = False
600630
601631 # unsubscribe from the FFI queue once the listen task ends.
602632 # disconnect() unsubscribes too, but it early-returns when the
@@ -713,6 +743,7 @@ async def disconnect(
713743 if self ._connection_state != ConnectionState .CONN_DISCONNECTED :
714744 self .local_participant ._info .disconnect_reason = reason
715745 self ._connection_state = ConnectionState .CONN_DISCONNECTED
746+ self ._fail_first_sid (RuntimeError ("room disconnected before receiving sid" ))
716747 self .emit ("connection_state_changed" , self ._connection_state )
717748 self .emit ("disconnected" , reason )
718749
@@ -744,6 +775,7 @@ async def _listen_task(self) -> None:
744775 await self ._drain_rpc_invocation_tasks ()
745776 self ._error_stream_readers ()
746777 self ._dispose_open_stream_writers ()
778+ self ._fail_first_sid (RuntimeError ("room disconnected before receiving sid" ))
747779
748780 def _on_rpc_method_invocation (self , rpc_invocation : RpcMethodInvocationEvent ) -> None :
749781 if self ._local_participant is None :
@@ -1126,6 +1158,7 @@ def _on_room_event(self, event: proto_room.RoomEvent) -> None:
11261158 self ._connection_state = connection_state
11271159 self .emit ("connection_state_changed" , connection_state )
11281160 elif which == "disconnected" :
1161+ self ._fail_first_sid (RuntimeError ("room disconnected before receiving sid" ))
11291162 self .emit ("disconnected" , event .disconnected .reason )
11301163 elif which == "reconnecting" :
11311164 self .emit ("reconnecting" )
@@ -1243,8 +1276,12 @@ def _retrieve_remote_participant(self, identity: str) -> Optional[RemoteParticip
12431276
12441277 def _retrieve_participant (self , identity : str ) -> Optional [Participant ]:
12451278 """Retrieve a local or remote participant by identity"""
1246- if identity and identity == self .local_participant .identity :
1247- return self .local_participant
1279+ if (
1280+ identity
1281+ and self ._local_participant is not None
1282+ and identity == self ._local_participant .identity
1283+ ):
1284+ return self ._local_participant
12481285
12491286 return self ._retrieve_remote_participant (identity )
12501287
@@ -1260,7 +1297,13 @@ def _create_remote_participant(
12601297
12611298 def __repr__ (self ) -> str :
12621299 sid = "unknown"
1263- if self ._first_sid_future .done ():
1300+ if self ._info .sid :
1301+ sid = self ._info .sid
1302+ elif (
1303+ self ._first_sid_future .done ()
1304+ and not self ._first_sid_future .cancelled ()
1305+ and self ._first_sid_future .exception () is None
1306+ ):
12641307 sid = self ._first_sid_future .result ()
12651308
12661309 return f"rtc.Room(sid={ sid } , name={ self .name } , metadata={ self .metadata } , connection_state={ ConnectionState .Name (self ._connection_state )} )"
0 commit comments