Skip to content

Commit 44457be

Browse files
authored
feat: eliminate async_timeout dep on python less than 3.11 (#1500)
1 parent bcf4a44 commit 44457be

7 files changed

Lines changed: 44 additions & 46 deletions

File tree

poetry.lock

Lines changed: 1 addition & 14 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -70,7 +70,6 @@ prerelease = true
7070

7171
[tool.poetry.dependencies]
7272
python = "^3.9"
73-
async-timeout = {version = ">=3.0.0", python = "<3.11"}
7473
ifaddr = ">=0.1.7"
7574

7675
[tool.poetry.group.dev.dependencies]

src/zeroconf/_core.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@
5555
get_running_loop,
5656
run_coro_with_timeout,
5757
shutdown_loop,
58-
wait_event_or_timeout,
5958
wait_for_future_set_or_timeout,
59+
wait_future_or_timeout,
6060
)
6161
from ._utils.name import service_type_name
6262
from ._utils.net import (
@@ -203,7 +203,15 @@ def __init__(
203203
@property
204204
def started(self) -> bool:
205205
"""Check if the instance has started."""
206-
return bool(not self.done and self.engine.running_event and self.engine.running_event.is_set())
206+
running_future = self.engine.running_future
207+
return bool(
208+
not self.done
209+
and running_future
210+
and running_future.done()
211+
and not running_future.cancelled()
212+
and not running_future.exception()
213+
and running_future.result()
214+
)
207215

208216
def start(self) -> None:
209217
"""Start Zeroconf."""
@@ -227,17 +235,17 @@ def _run_loop() -> None:
227235
self._loop_thread.start()
228236
loop_thread_ready.wait()
229237

230-
async def async_wait_for_start(self) -> None:
238+
async def async_wait_for_start(self, timeout: float = _STARTUP_TIMEOUT) -> None:
231239
"""Wait for start up for actions that require a running Zeroconf instance.
232240
233241
Throws NotRunningException if the instance is not running or could
234242
not be started.
235243
"""
236244
if self.done: # If the instance was shutdown from under us, raise immediately
237245
raise NotRunningException
238-
assert self.engine.running_event is not None
239-
await wait_event_or_timeout(self.engine.running_event, timeout=_STARTUP_TIMEOUT)
240-
if not self.engine.running_event.is_set() or self.done:
246+
assert self.engine.running_future is not None
247+
await wait_future_or_timeout(self.engine.running_future, timeout=timeout)
248+
if not self.started:
241249
raise NotRunningException
242250

243251
@property

src/zeroconf/_engine.py

Lines changed: 8 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ class AsyncEngine:
5353
"loop",
5454
"protocols",
5555
"readers",
56-
"running_event",
56+
"running_future",
5757
"senders",
5858
"zc",
5959
)
@@ -69,7 +69,7 @@ def __init__(
6969
self.protocols: list[AsyncListener] = []
7070
self.readers: list[_WrappedTransport] = []
7171
self.senders: list[_WrappedTransport] = []
72-
self.running_event: asyncio.Event | None = None
72+
self.running_future: asyncio.Future[bool | None] | None = None
7373
self._listen_socket = listen_socket
7474
self._respond_sockets = respond_sockets
7575
self._cleanup_timer: asyncio.TimerHandle | None = None
@@ -81,15 +81,15 @@ def setup(
8181
) -> None:
8282
"""Set up the instance."""
8383
self.loop = loop
84-
self.running_event = asyncio.Event()
84+
self.running_future = loop.create_future()
8585
self.loop.create_task(self._async_setup(loop_thread_ready))
8686

8787
async def _async_setup(self, loop_thread_ready: threading.Event | None) -> None:
8888
"""Set up the instance."""
8989
self._async_schedule_next_cache_cleanup()
9090
await self._async_create_endpoints()
91-
assert self.running_event is not None
92-
self.running_event.set()
91+
assert self.running_future is not None
92+
self.running_future.set_result(True)
9393
if loop_thread_ready:
9494
loop_thread_ready.set()
9595

@@ -142,8 +142,9 @@ async def _async_close(self) -> None:
142142

143143
def _async_shutdown(self) -> None:
144144
"""Shutdown transports and sockets."""
145-
assert self.running_event is not None
146-
self.running_event.clear()
145+
assert self.running_future is not None
146+
assert self.loop is not None
147+
self.running_future = self.loop.create_future()
147148
for wrapped_transport in itertools.chain(self.senders, self.readers):
148149
wrapped_transport.transport.close()
149150

src/zeroconf/_utils/asyncio.py

Lines changed: 11 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,6 @@
2828
import sys
2929
from typing import Any, Awaitable, Coroutine
3030

31-
if sys.version_info[:2] < (3, 11):
32-
from async_timeout import timeout as asyncio_timeout
33-
else:
34-
from asyncio import timeout as asyncio_timeout # type: ignore[attr-defined]
35-
3631
from .._exceptions import EventLoopBlocked
3732
from ..const import _LOADED_SYSTEM_TIMEOUT
3833
from .time import millis_to_seconds
@@ -70,11 +65,17 @@ async def wait_for_future_set_or_timeout(
7065
future_set.discard(future)
7166

7267

73-
async def wait_event_or_timeout(event: asyncio.Event, timeout: float) -> None:
74-
"""Wait for an event or timeout."""
75-
with contextlib.suppress(asyncio.TimeoutError):
76-
async with asyncio_timeout(timeout):
77-
await event.wait()
68+
async def wait_future_or_timeout(future: asyncio.Future[bool | None], timeout: float) -> None:
69+
"""Wait for a future or timeout."""
70+
loop = asyncio.get_running_loop()
71+
handle = loop.call_later(timeout, _set_future_none_if_not_done, future)
72+
try:
73+
await future
74+
except asyncio.CancelledError:
75+
if sys.version_info >= (3, 11) and (task := asyncio.current_task()) and task.cancelling():
76+
raise
77+
finally:
78+
handle.cancel()
7879

7980

8081
async def _async_get_all_tasks(loop: asyncio.AbstractEventLoop) -> set[asyncio.Task]:

src/zeroconf/asyncio.py

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

3030
from ._core import Zeroconf
3131
from ._dns import DNSQuestionType
32+
from ._exceptions import NotRunningException
3233
from ._services import ServiceListener
3334
from ._services.browser import _ServiceBrowserBase
3435
from ._services.info import AsyncServiceInfo, ServiceInfo
@@ -227,8 +228,8 @@ async def async_close(self) -> None:
227228
"""Ends the background threads, and prevent this instance from
228229
servicing further queries."""
229230
if not self.zeroconf.done:
230-
with contextlib.suppress(asyncio.TimeoutError):
231-
await asyncio.wait_for(self.zeroconf.async_wait_for_start(), timeout=1)
231+
with contextlib.suppress(NotRunningException):
232+
await self.zeroconf.async_wait_for_start(timeout=1.0)
232233
await self.async_remove_all_service_listeners()
233234
await self.async_unregister_all_services()
234235
await self.zeroconf._async_close() # pylint: disable=protected-access

tests/utils/test_asyncio.py

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,16 +45,17 @@ def test_get_running_loop_no_loop() -> None:
4545

4646

4747
@pytest.mark.asyncio
48-
async def test_wait_event_or_timeout_times_out() -> None:
49-
"""Test wait_event_or_timeout will timeout."""
50-
test_event = asyncio.Event()
51-
await aioutils.wait_event_or_timeout(test_event, 0.1)
48+
async def test_wait_future_or_timeout_times_out() -> None:
49+
"""Test wait_future_or_timeout will timeout."""
50+
loop = asyncio.get_running_loop()
51+
test_future = loop.create_future()
52+
await aioutils.wait_future_or_timeout(test_future, 0.1)
5253

53-
task = asyncio.ensure_future(test_event.wait())
54+
task = asyncio.ensure_future(test_future)
5455
await asyncio.sleep(0.1)
5556

5657
async def _async_wait_or_timeout():
57-
await aioutils.wait_event_or_timeout(test_event, 0.1)
58+
await aioutils.wait_future_or_timeout(test_future, 0.1)
5859

5960
# Test high lock contention
6061
await asyncio.gather(*[_async_wait_or_timeout() for _ in range(100)])

0 commit comments

Comments
 (0)