10
10
from __future__ import annotations
11
11
12
12
import abc
13
- from dataclasses import dataclass
14
13
import errno
15
14
import json
16
15
import logging
17
- import select
16
+ import selectors
18
17
import socket
19
18
import ssl
20
19
import threading
21
20
import time
22
21
from collections import defaultdict
22
+ from dataclasses import dataclass
23
23
from struct import pack , unpack
24
24
25
25
import zeroconf
26
26
27
- from .controllers import CallbackType , BaseController
27
+ from .const import MESSAGE_TYPE , REQUEST_ID , SESSION_ID
28
+ from .controllers import BaseController , CallbackType
28
29
from .controllers .media import MediaController
29
30
from .controllers .receiver import CastStatus , CastStatusListener , ReceiverController
30
- from .const import MESSAGE_TYPE , REQUEST_ID , SESSION_ID
31
31
from .dial import get_host_from_service
32
32
from .error import (
33
33
ChromecastConnectionError ,
34
34
ControllerNotRegistered ,
35
- UnsupportedNamespace ,
36
35
NotConnected ,
37
36
PyChromecastStopped ,
37
+ UnsupportedNamespace ,
38
38
)
39
39
40
40
# pylint: disable-next=no-name-in-module
64
64
CONNECTION_STATUS_FAILED_RESOLVE = "FAILED_RESOLVE"
65
65
# The socket connection was lost and needs to be retried
66
66
CONNECTION_STATUS_LOST = "LOST"
67
- # Check for select poll method
68
- SELECT_HAS_POLL = hasattr (select , "poll" )
69
67
70
68
HB_PING_TIME = 10
71
69
HB_PONG_TIME = 10
@@ -213,6 +211,11 @@ def __init__(
213
211
self .connecting = True
214
212
self .first_connection = True
215
213
self .socket : socket .socket | ssl .SSLSocket | None = None
214
+ self .selector = selectors .DefaultSelector ()
215
+ self .wakeup_selector_key = self .selector .register (
216
+ self .socketpair [0 ], selectors .EVENT_READ
217
+ )
218
+ self .remote_selector_key : selectors .SelectorKey | None = None
216
219
217
220
# dict mapping namespace on Controller objects
218
221
self ._handlers : dict [str , set [BaseController ]] = defaultdict (set )
@@ -236,8 +239,10 @@ def initialize_connection( # pylint:disable=too-many-statements, too-many-branc
236
239
tries = self .tries
237
240
238
241
if self .socket is not None :
242
+ self .selector .unregister (self .socket )
239
243
self .socket .close ()
240
244
self .socket = None
245
+ self .remote_selector_key = None
241
246
242
247
# Make sure nobody is blocking.
243
248
for callback_function in self ._request_callbacks .values ():
@@ -286,10 +291,15 @@ def mdns_backoff(
286
291
try :
287
292
if self .socket is not None :
288
293
# If we retry connecting, we need to clean up the socket again
289
- self .socket .close () # type: ignore[unreachable]
294
+ self .selector .unregister (self .socket ) # type: ignore[unreachable]
295
+ self .socket .close ()
290
296
self .socket = None
297
+ self .remote_selector_key = None
291
298
292
299
self .socket = new_socket ()
300
+ self .remote_selector_key = self .selector .register (
301
+ self .socket , selectors .EVENT_READ
302
+ )
293
303
self .socket .settimeout (self .timeout )
294
304
self ._report_connection_status (
295
305
ConnectionStatus (
@@ -557,20 +567,8 @@ def _run_once(self) -> int:
557
567
assert self .socket is not None
558
568
559
569
# poll the socket, as well as the socketpair to allow us to be interrupted
560
- rlist = [self .socket , self .socketpair [0 ]]
561
570
try :
562
- if SELECT_HAS_POLL is True :
563
- # Map file descriptors to socket objects because select.select does not support fd > 1024
564
- # https://stackoverflow.com/questions/14250751/how-to-increase-filedescriptors-range-in-python-select
565
- fd_to_socket = {rlist_item .fileno (): rlist_item for rlist_item in rlist }
566
-
567
- poll_obj = select .poll ()
568
- for poll_fd in rlist :
569
- poll_obj .register (poll_fd , select .POLLIN )
570
- poll_result = poll_obj .poll ()
571
- can_read = [fd_to_socket [fd ] for fd , _status in poll_result ]
572
- else :
573
- can_read , _ , _ = select .select (rlist , [], [], None )
571
+ ready = self .selector .select ()
574
572
except (ValueError , OSError ) as exc :
575
573
self .logger .error (
576
574
"[%s(%s):%s] Error in select call: %s" ,
@@ -582,9 +580,10 @@ def _run_once(self) -> int:
582
580
self ._force_recon = True
583
581
return 0
584
582
583
+ can_read = {key for key , _ in ready }
585
584
# read message from chromecast
586
585
message = None
587
- if self .socket in can_read and not self ._force_recon :
586
+ if self .remote_selector_key in can_read and not self ._force_recon :
588
587
try :
589
588
message = self ._read_message ()
590
589
except InterruptLoop as exc :
@@ -620,7 +619,7 @@ def _run_once(self) -> int:
620
619
else :
621
620
data = _dict_from_message_payload (message )
622
621
623
- if self .socketpair [ 0 ] in can_read :
622
+ if self .wakeup_selector_key in can_read :
624
623
# Clear the socket's buffer
625
624
self .socketpair [0 ].recv (128 )
626
625
@@ -765,6 +764,7 @@ def _cleanup(self) -> None:
765
764
766
765
self .socketpair [0 ].close ()
767
766
self .socketpair [1 ].close ()
767
+ self .selector .close ()
768
768
769
769
self .connecting = True
770
770
0 commit comments