Skip to content

Commit c3f563f

Browse files
authored
Merge pull request python-zeroconf#72 from stephenrauch/Catch-and-log-sendto-exceptions
Catch and log sendto() exceptions
2 parents 254c207 + 0924310 commit c3f563f

File tree

2 files changed

+100
-18
lines changed

2 files changed

+100
-18
lines changed

test_zeroconf.py

Lines changed: 87 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -212,34 +212,36 @@ def test_same_name(self):
212212
r.DNSIncoming(generated.packet())
213213

214214
def test_lots_of_names(self):
215+
215216
# instantiate a zeroconf instance
216-
zeroconf = Zeroconf(interfaces=['127.0.0.1'])
217+
zc = Zeroconf(interfaces=['127.0.0.1'])
217218

218219
# we are going to monkey patch the zeroconf send to check packet sizes
219-
old_send = zeroconf.send
220+
old_send = zc.send
220221

221222
# needs to be a list so that we can modify it in our phony send
222-
longest_packet = [0]
223+
longest_packet = [0, None]
223224

224225
def send(out, addr=r._MDNS_ADDR, port=r._MDNS_PORT):
225226
"""Sends an outgoing packet."""
226227
packet = out.packet()
227228
if longest_packet[0] < len(packet):
228229
longest_packet[0] = len(packet)
230+
longest_packet[1] = out
229231
old_send(out, addr=addr, port=port)
230232

231233
# monkey patch the zeroconf send
232-
zeroconf.send = send
234+
zc.send = send
233235

234236
# create a bunch of servers
235237
type_ = "_my-service._tcp.local."
236238
server_count = 300
237239
records_per_server = 2
238240
for i in range(int(server_count / 10)):
239-
self.generate_many_hosts(zeroconf, type_, 10)
241+
self.generate_many_hosts(zc, type_, 10)
240242
sleep_count = 0
241-
while sleep_count < 20 and server_count * records_per_server > len(
242-
zeroconf.cache.entries_with_name(type_)):
243+
while sleep_count < 100 and server_count * records_per_server > len(
244+
zc.cache.entries_with_name(type_)):
243245
sleep_count += 1
244246
time.sleep(0.01)
245247

@@ -248,13 +250,88 @@ def on_service_state_change(zeroconf, service_type, state_change, name):
248250
pass
249251

250252
# start a browser and run for a bit
251-
browser = ServiceBrowser(zeroconf, type_, [on_service_state_change])
252-
time.sleep(0.1)
253+
browser = ServiceBrowser(zc, type_, [on_service_state_change])
254+
sleep_count = 0
255+
while sleep_count < 100 and \
256+
longest_packet[0] < r._MAX_MSG_ABSOLUTE - 100:
257+
sleep_count += 1
258+
time.sleep(0.1)
259+
253260
browser.cancel()
254-
zeroconf.close()
261+
time.sleep(0.5)
262+
263+
import zeroconf
264+
zeroconf.log.debug('sleep_count %d, sized %d',
265+
sleep_count, longest_packet[0])
255266

256267
# now the browser has sent at least one request, verify the size
257268
assert longest_packet[0] < r._MAX_MSG_ABSOLUTE
269+
assert longest_packet[0] >= r._MAX_MSG_ABSOLUTE - 100
270+
271+
# mock zeroconf's logger warning() and debug()
272+
from mock import patch
273+
patch_warn = patch('zeroconf.log.warning')
274+
patch_debug = patch('zeroconf.log.debug')
275+
mocked_log_warn = patch_warn.start()
276+
mocked_log_debug = patch_debug.start()
277+
278+
# now that we have a long packet in our possession, let's verify the
279+
# exception handling.
280+
out = longest_packet[1]
281+
out.data.append(b'\0' * 1000)
282+
283+
# mock the zeroconf logger and check for the correct logging backoff
284+
call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count
285+
# try to send an oversized packet
286+
zc.send(out)
287+
assert mocked_log_warn.call_count == call_counts[0] + 1
288+
assert mocked_log_debug.call_count == call_counts[0]
289+
zc.send(out)
290+
assert mocked_log_warn.call_count == call_counts[0] + 1
291+
assert mocked_log_debug.call_count == call_counts[0] + 1
292+
293+
# force a receive of an oversized packet
294+
packet = out.packet()
295+
s = zc._respond_sockets[0]
296+
297+
# mock the zeroconf logger and check for the correct logging backoff
298+
call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count
299+
# force receive on oversized packet
300+
s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT))
301+
s.sendto(packet, 0, (r._MDNS_ADDR, r._MDNS_PORT))
302+
time.sleep(2.0)
303+
zeroconf.log.debug('warn %d debug %d was %s',
304+
mocked_log_warn.call_count,
305+
mocked_log_debug.call_count,
306+
call_counts)
307+
assert mocked_log_debug.call_count > call_counts[0]
308+
309+
# close our zeroconf which will close the sockets
310+
zc.close()
311+
312+
# pop the big chunk off the end of the data and send on a closed socket
313+
out.data.pop()
314+
zc._GLOBAL_DONE = False
315+
316+
# mock the zeroconf logger and check for the correct logging backoff
317+
call_counts = mocked_log_warn.call_count, mocked_log_debug.call_count
318+
# send on a closed socket (force a socket error)
319+
zc.send(out)
320+
zeroconf.log.debug('warn %d debug %d was %s',
321+
mocked_log_warn.call_count,
322+
mocked_log_debug.call_count,
323+
call_counts)
324+
assert mocked_log_warn.call_count > call_counts[0]
325+
assert mocked_log_debug.call_count > call_counts[0]
326+
zc.send(out)
327+
zeroconf.log.debug('warn %d debug %d was %s',
328+
mocked_log_warn.call_count,
329+
mocked_log_debug.call_count,
330+
call_counts)
331+
assert mocked_log_debug.call_count > call_counts[0] + 2
332+
333+
mocked_log_warn.stop()
334+
mocked_log_debug.stop()
258335

259336
def generate_many_hosts(self, zc, type_, number_hosts):
260337
import random

zeroconf.py

Lines changed: 13 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@
4141

4242
__author__ = 'Paul Scott-Murphy, William McBrine'
4343
__maintainer__ = 'Jakub Stasiak <jakub@stasiak.at>'
44-
__version__ = '0.17.6'
44+
__version__ = '0.17.7.dev'
4545
__license__ = 'LGPL'
4646

4747

@@ -1948,8 +1948,8 @@ def handle_query(self, msg, addr, port):
19481948
out.add_additional_answer(DNSAddress(
19491949
service.server, _TYPE_A, _CLASS_IN | _CLASS_UNIQUE,
19501950
_DNS_TTL, service.address))
1951-
except Exception as e: # TODO stop catching all Exceptions
1952-
log.exception('Unknown error, possibly benign: %r', e)
1951+
except Exception: # TODO stop catching all Exceptions
1952+
self.log_exception_warning()
19531953

19541954
if out is not None and out.answers:
19551955
out.id = msg.id
@@ -1966,11 +1966,16 @@ def send(self, out, addr=_MDNS_ADDR, port=_MDNS_PORT):
19661966
for s in self._respond_sockets:
19671967
if self._GLOBAL_DONE:
19681968
return
1969-
bytes_sent = s.sendto(packet, 0, (addr, port))
1970-
if bytes_sent != len(packet):
1971-
self.log_warning_once(
1972-
'!!! sent %d out of %d bytes to %r' % (
1973-
bytes_sent, len(packet)), s)
1969+
try:
1970+
bytes_sent = s.sendto(packet, 0, (addr, port))
1971+
except Exception: # TODO stop catching all Exceptions
1972+
# on send errors, log the exception and keep going
1973+
self.log_exception_warning()
1974+
else:
1975+
if bytes_sent != len(packet):
1976+
self.log_warning_once(
1977+
'!!! sent %d out of %d bytes to %r' % (
1978+
bytes_sent, len(packet)), s)
19741979

19751980
def close(self):
19761981
"""Ends the background threads, and prevent this instance from

0 commit comments

Comments
 (0)