Skip to content

Commit 10ee205

Browse files
authored
Fix ServiceBrowsers not getting ServiceStateChange.Removed callbacks on PTR record expire (#1064)
1 parent 31662b7 commit 10ee205

2 files changed

Lines changed: 72 additions & 3 deletions

File tree

tests/services/test_browser.py

Lines changed: 71 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,6 @@
33

44
""" Unit tests for zeroconf._services.browser. """
55

6-
import asyncio
76
import logging
87
import socket
98
import time
@@ -17,7 +16,7 @@
1716
import zeroconf as r
1817
from zeroconf import DNSPointer, DNSQuestion, const, current_time_millis, millis_to_seconds
1918
import zeroconf._services.browser as _services_browser
20-
from zeroconf import Zeroconf
19+
from zeroconf import _core, _handlers, Zeroconf
2120
from zeroconf._services import ServiceStateChange
2221
from zeroconf._services.browser import ServiceBrowser
2322
from zeroconf._services.info import ServiceInfo
@@ -1100,3 +1099,73 @@ def mock_incoming_msg(records) -> r.DNSIncoming:
11001099
browser.cancel()
11011100

11021101
zc.close()
1102+
1103+
1104+
@patch.object(_handlers, '_DNS_PTR_MIN_TTL', 1)
1105+
@patch.object(_core, "_CACHE_CLEANUP_INTERVAL", 10)
1106+
def test_service_browser_expire_callbacks():
1107+
"""Test that the ServiceBrowser matching does not match partial names."""
1108+
# instantiate a zeroconf instance
1109+
zc = Zeroconf(interfaces=['127.0.0.1'])
1110+
# start a browser
1111+
type_ = "_http._tcp.local."
1112+
registration_name = "xxxyyy.%s" % type_
1113+
callbacks = []
1114+
1115+
class MyServiceListener(r.ServiceListener):
1116+
def add_service(self, zc, type_, name) -> None:
1117+
nonlocal callbacks
1118+
if name == registration_name:
1119+
callbacks.append(("add", type_, name))
1120+
1121+
def remove_service(self, zc, type_, name) -> None:
1122+
nonlocal callbacks
1123+
if name == registration_name:
1124+
callbacks.append(("remove", type_, name))
1125+
1126+
def update_service(self, zc, type_, name) -> None:
1127+
nonlocal callbacks
1128+
if name == registration_name:
1129+
callbacks.append(("update", type_, name))
1130+
1131+
listener = MyServiceListener()
1132+
1133+
browser = r.ServiceBrowser(zc, type_, None, listener)
1134+
1135+
desc = {'path': '/~paulsm/'}
1136+
address_parsed = "10.0.1.2"
1137+
address = socket.inet_aton(address_parsed)
1138+
info = ServiceInfo(
1139+
type_, registration_name, 80, 0, 0, desc, "ash-2.local.", host_ttl=1, other_ttl=1, addresses=[address]
1140+
)
1141+
1142+
def mock_incoming_msg(records) -> r.DNSIncoming:
1143+
generated = r.DNSOutgoing(const._FLAGS_QR_RESPONSE)
1144+
for record in records:
1145+
generated.add_answer_at_time(record, 0)
1146+
return r.DNSIncoming(generated.packets()[0])
1147+
1148+
_inject_response(
1149+
zc,
1150+
mock_incoming_msg([info.dns_pointer(), info.dns_service(), info.dns_text(), *info.dns_addresses()]),
1151+
)
1152+
time.sleep(0.2)
1153+
info.port = 400
1154+
_inject_response(
1155+
zc,
1156+
mock_incoming_msg([info.dns_service()]),
1157+
)
1158+
1159+
assert callbacks == [
1160+
('add', type_, registration_name),
1161+
('update', type_, registration_name),
1162+
]
1163+
time.sleep(1.2)
1164+
assert callbacks == [
1165+
('add', type_, registration_name),
1166+
('update', type_, registration_name),
1167+
('remove', type_, registration_name),
1168+
]
1169+
browser.cancel()
1170+
1171+
zc.close()

zeroconf/_core.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -167,7 +167,7 @@ def _async_cache_cleanup(self) -> None:
167167
now = current_time_millis()
168168
self.zc.question_history.async_expire(now)
169169
self.zc.record_manager.async_updates(
170-
now, [RecordUpdate(record, None) for record in self.zc.cache.async_expire(now)]
170+
now, [RecordUpdate(record, record) for record in self.zc.cache.async_expire(now)]
171171
)
172172
self.zc.record_manager.async_updates_complete()
173173
assert self.loop is not None

0 commit comments

Comments
 (0)