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 )
1763176317641764await 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()