Skip to content

Commit 560f293

Browse files
committed
feat: speed up core with a cython pxd
1 parent 7ffbed8 commit 560f293

6 files changed

Lines changed: 126 additions & 26 deletions

File tree

build_ext.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ def build(setup_kwargs: Any) -> None:
2525
[
2626
"src/zeroconf/_dns.py",
2727
"src/zeroconf/_cache.py",
28+
"src/zeroconf/_core.py",
2829
"src/zeroconf/_history.py",
2930
"src/zeroconf/_record_update.py",
3031
"src/zeroconf/_listener.py",
@@ -38,6 +39,7 @@ def build(setup_kwargs: Any) -> None:
3839
"src/zeroconf/_services/browser.py",
3940
"src/zeroconf/_services/info.py",
4041
"src/zeroconf/_services/registry.py",
42+
"src/zeroconf/_transport.py",
4143
"src/zeroconf/_updates.py",
4244
"src/zeroconf/_utils/ipaddress.py",
4345
"src/zeroconf/_utils/time.py",

src/zeroconf/_core.pxd

Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
2+
import cython
3+
4+
5+
cdef bint TYPE_CHECKING
6+
cdef object _MDNS_ADDR6,_MDNS_ADDR
7+
8+
9+
from ._cache cimport DNSCache
10+
from ._handlers.multicast_outgoing_queue cimport MulticastOutgoingQueue
11+
from ._handlers.query_handler cimport QueryHandler, QuestionAnswers
12+
from ._history cimport QuestionHistory
13+
from ._listener cimport AsyncListener
14+
from ._protocol.incoming cimport DNSIncoming
15+
from ._protocol.outgoing cimport DNSOutgoing
16+
from ._services.registry cimport ServiceRegistry
17+
from ._transport cimport _WrappedTransport
18+
from ._updates cimport RecordUpdateListener
19+
20+
21+
cdef void async_send_with_transport(
22+
bint log_debug,
23+
_WrappedTransport transport,
24+
object packet,
25+
object packet_num,
26+
DNSOutgoing out,
27+
object addr,
28+
object port,
29+
tuple v6_flow_scope
30+
)
31+
32+
cdef class Zeroconf:
33+
34+
cdef public bint done
35+
cdef public bint unicast
36+
cdef public object engine
37+
cdef public dict browsers
38+
cdef public ServiceRegistry registry
39+
cdef public DNSCache cache
40+
cdef public QuestionHistory question_history
41+
cdef public QueryHandler query_handler
42+
cdef public object record_manager
43+
cdef public set _notify_futures
44+
cdef public object loop
45+
cdef public object _loop_thread
46+
cdef public MulticastOutgoingQueue _out_queue
47+
cdef public MulticastOutgoingQueue _out_delay_queue
48+
49+
cdef bint _debug_enabled(self)
50+
51+
@cython.locals(first_packet=DNSIncoming)
52+
cpdef handle_assembled_query(
53+
self,
54+
list packets,
55+
object addr,
56+
object port,
57+
_WrappedTransport transport,
58+
tuple v6_flow_scope
59+
)
60+
61+
@cython.locals(max_size="unsigned int")
62+
cpdef _async_send(
63+
self,
64+
DNSOutgoing out,
65+
object addr,
66+
object port,
67+
tuple v6_flow_scope,
68+
_WrappedTransport transport
69+
)

src/zeroconf/_core.py

Lines changed: 41 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,20 @@
102102

103103
_REGISTER_BROADCASTS = 3
104104

105+
_str = str
106+
_int = int
107+
_bytes = bytes
108+
105109

106110
def async_send_with_transport(
107111
log_debug: bool,
108112
transport: _WrappedTransport,
109-
packet: bytes,
110-
packet_num: int,
113+
packet: _bytes,
114+
packet_num: _int,
111115
out: DNSOutgoing,
112-
addr: Optional[str],
113-
port: int,
114-
v6_flow_scope: Union[Tuple[()], Tuple[int, int]] = (),
116+
addr: Optional[_str],
117+
port: _int,
118+
v6_flow_scope: Union[Tuple[()], Tuple[int, int]],
115119
) -> None:
116120
ipv6_socket = transport.is_ipv6
117121
if addr is None:
@@ -140,7 +144,7 @@ def async_send_with_transport(
140144
transport.transport.sendto(packet, (real_addr, port or _MDNS_PORT, *v6_flow_scope))
141145

142146

143-
class Zeroconf(QuietLogger):
147+
class Zeroconf:
144148

145149
"""Implementation of Zeroconf Multicast DNS Service Discovery
146150
@@ -561,17 +565,11 @@ def async_remove_listener(self, listener: RecordUpdateListener) -> None:
561565
"""
562566
self.record_manager.async_remove_listener(listener)
563567

564-
def handle_response(self, msg: DNSIncoming) -> None:
565-
"""Deal with incoming response packets. All answers
566-
are held in the cache, and listeners are notified."""
567-
self.log_warning_once("handle_response is deprecated, use record_manager.async_updates_from_response")
568-
self.record_manager.async_updates_from_response(msg)
569-
570568
def handle_assembled_query(
571569
self,
572570
packets: List[DNSIncoming],
573-
addr: str,
574-
port: int,
571+
addr: _str,
572+
port: _int,
575573
transport: _WrappedTransport,
576574
v6_flow_scope: Union[Tuple[()], Tuple[int, int]],
577575
) -> None:
@@ -587,17 +585,20 @@ def handle_assembled_query(
587585
question_answers = self.query_handler.async_response(packets, ucast_source)
588586
if not question_answers:
589587
return
590-
now = packets[0].now
588+
first_packet = packets[0]
589+
now = first_packet.now
591590
if question_answers.ucast:
592-
questions = packets[0].questions
593-
id_ = packets[0].id
591+
questions = first_packet.questions
592+
id_ = first_packet.id
594593
out = construct_outgoing_unicast_answers(question_answers.ucast, ucast_source, questions, id_)
595594
# When sending unicast, only send back the reply
596595
# via the same socket that it was recieved from
597596
# as we know its reachable from that socket
598-
self.async_send(out, addr, port, v6_flow_scope, transport)
597+
self._async_send(out, addr, port, v6_flow_scope, transport)
599598
if question_answers.mcast_now:
600-
self.async_send(construct_outgoing_multicast_answers(question_answers.mcast_now))
599+
self._async_send(
600+
construct_outgoing_multicast_answers(question_answers.mcast_now), None, _MDNS_PORT, (), None
601+
)
601602
if question_answers.mcast_aggregate:
602603
self._out_queue.async_add(now, question_answers.mcast_aggregate)
603604
if question_answers.mcast_aggregate_last_second:
@@ -616,7 +617,10 @@ def send(
616617
) -> None:
617618
"""Sends an outgoing packet threadsafe."""
618619
assert self.loop is not None
619-
self.loop.call_soon_threadsafe(self.async_send, out, addr, port, v6_flow_scope, transport)
620+
self.loop.call_soon_threadsafe(self._async_send, out, addr, port, v6_flow_scope, transport)
621+
622+
def _debug_enabled(self) -> bool:
623+
return log.isEnabledFor(logging.DEBUG)
620624

621625
def async_send(
622626
self,
@@ -625,6 +629,17 @@ def async_send(
625629
port: int = _MDNS_PORT,
626630
v6_flow_scope: Union[Tuple[()], Tuple[int, int]] = (),
627631
transport: Optional[_WrappedTransport] = None,
632+
) -> None:
633+
"""Sends an outgoing packet."""
634+
self._async_send(out, addr, port, v6_flow_scope, transport)
635+
636+
def _async_send(
637+
self,
638+
out: DNSOutgoing,
639+
addr: Optional[str],
640+
port: _int,
641+
v6_flow_scope: Union[Tuple[()], Tuple[int, int]],
642+
transport: Optional[_WrappedTransport],
628643
) -> None:
629644
"""Sends an outgoing packet."""
630645
if self.done:
@@ -633,11 +648,14 @@ def async_send(
633648
# If no transport is specified, we send to all the ones
634649
# with the same address family
635650
transports = [transport] if transport else self.engine.senders
636-
log_debug = log.isEnabledFor(logging.DEBUG)
651+
log_debug = self._debug_enabled()
652+
max_size = _MAX_MSG_ABSOLUTE
637653

638654
for packet_num, packet in enumerate(out.packets()):
639-
if len(packet) > _MAX_MSG_ABSOLUTE:
640-
self.log_warning_once("Dropping %r over-sized packet (%d bytes) %r", out, len(packet), packet)
655+
if len(packet) > max_size:
656+
QuietLogger.log_warning_once(
657+
"Dropping %r over-sized packet (%d bytes) %r", out, len(packet), packet
658+
)
641659
return
642660
for send_transport in transports:
643661
async_send_with_transport(

src/zeroconf/_handlers/query_handler.pxd

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -93,7 +93,7 @@ cdef class QueryHandler:
9393
is_probe=object,
9494
now=double
9595
)
96-
cpdef async_response(self, cython.list msgs, cython.bint unicast_source)
96+
cpdef QuestionAnswers async_response(self, cython.list msgs, cython.bint unicast_source)
9797

9898
@cython.locals(name=str, question_lower_name=str)
9999
cdef _get_answer_strategies(self, DNSQuestion question)

src/zeroconf/_transport.pxd

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
2+
import cython
3+
4+
5+
cdef class _WrappedTransport:
6+
7+
cdef public object transport
8+
cdef public bint is_ipv6
9+
cdef public object socket
10+
cdef public object fileno
11+
cdef public tuple sock_name

src/zeroconf/_transport.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@
2222

2323
import asyncio
2424
import socket
25-
from typing import Any
25+
from typing import Tuple
2626

2727

2828
class _WrappedTransport:
@@ -42,7 +42,7 @@ def __init__(
4242
is_ipv6: bool,
4343
sock: socket.socket,
4444
fileno: int,
45-
sock_name: Any,
45+
sock_name: Tuple,
4646
) -> None:
4747
"""Initialize the wrapped transport.
4848

0 commit comments

Comments
 (0)