fix: set change during iteration when dispatching listeners (#1370) · python-zeroconf/python-zeroconf@e9f8aa5

@@ -1762,3 +1762,77 @@ def async_update_records(self, zc: 'Zeroconf', now: float, records: List[r.Recor

17621762

)

1763176317641764

await aiozc.async_close()

1765+1766+1767+

@pytest.mark.asyncio

1768+

async def test_async_updates_iteration_safe():

1769+

"""Ensure we can safely iterate over the async_updates."""

1770+1771+

aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])

1772+

zc: Zeroconf = aiozc.zeroconf

1773+

updated = []

1774+

good_bye_answer = r.DNSPointer(

1775+

"myservicelow_tcp._tcp.local.",

1776+

const._TYPE_PTR,

1777+

const._CLASS_IN | const._CLASS_UNIQUE,

1778+

0,

1779+

'goodbye.local.',

1780+

)

1781+1782+

class OtherListener(r.RecordUpdateListener):

1783+

"""A RecordUpdateListener that does not implement update_records."""

1784+1785+

def async_update_records(self, zc: 'Zeroconf', now: float, records: List[r.RecordUpdate]) -> None:

1786+

"""Update multiple records in one shot."""

1787+

updated.extend(records)

1788+1789+

other = OtherListener()

1790+1791+

class ListenerThatAddsListener(r.RecordUpdateListener):

1792+

"""A RecordUpdateListener that does not implement update_records."""

1793+1794+

def async_update_records(self, zc: 'Zeroconf', now: float, records: List[r.RecordUpdate]) -> None:

1795+

"""Update multiple records in one shot."""

1796+

updated.extend(records)

1797+

zc.async_add_listener(other, None)

1798+1799+

zc.async_add_listener(ListenerThatAddsListener(), None)

1800+

await asyncio.sleep(0) # flush out any call soons

1801+1802+

# This should not raise RuntimeError: set changed size during iteration

1803+

zc.record_manager.async_updates(

1804+

now=current_time_millis(), records=[r.RecordUpdate(good_bye_answer, None)]

1805+

)

1806+1807+

assert len(updated) == 1

1808+

await aiozc.async_close()

1809+1810+1811+

@pytest.mark.asyncio

1812+

async def test_async_updates_complete_iteration_safe():

1813+

"""Ensure we can safely iterate over the async_updates_complete."""

1814+1815+

aiozc = AsyncZeroconf(interfaces=['127.0.0.1'])

1816+

zc: Zeroconf = aiozc.zeroconf

1817+1818+

class OtherListener(r.RecordUpdateListener):

1819+

"""A RecordUpdateListener that does not implement update_records."""

1820+1821+

def async_update_records_complete(self) -> None:

1822+

"""Update multiple records in one shot."""

1823+1824+

other = OtherListener()

1825+1826+

class ListenerThatAddsListener(r.RecordUpdateListener):

1827+

"""A RecordUpdateListener that does not implement update_records."""

1828+1829+

def async_update_records_complete(self) -> None:

1830+

"""Update multiple records in one shot."""

1831+

zc.async_add_listener(other, None)

1832+1833+

zc.async_add_listener(ListenerThatAddsListener(), None)

1834+

await asyncio.sleep(0) # flush out any call soons

1835+1836+

# This should not raise RuntimeError: set changed size during iteration

1837+

zc.record_manager.async_updates_complete(False)

1838+

await aiozc.async_close()