Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve Windows standalone package #176

Closed
wants to merge 20 commits into from
1 change: 1 addition & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ on:
- master
- bugfix/*
- feature/*
- standalone
jobs:
bdist:
name: build bdist wheels and test
Expand Down
22 changes: 13 additions & 9 deletions curl_cffi/_asyncio_selector.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
"""Ensure asyncio selector methods (add_reader, etc.) are available
"""
Ensure asyncio selector methods (add_reader, etc.) are available
tornado 6.1 adds AddThreadSelectorEventLoop event loop,
running select in a thread and defining these methods on the running event loop.
This factors out the functionality of AddThreadSelectorEventLoop
into a standalone SelectorThread object which can be attached to any running event loop.
Vendored from tornado v6.1.0-64-g289c834b (PR #3029)
Redistributed under license Apache-2.0
"""

import asyncio
import atexit
import errno
Expand All @@ -16,8 +18,6 @@
import typing
from typing import (
Any,
TypeVar,
Awaitable,
Callable,
Union,
Optional,
Expand All @@ -27,7 +27,11 @@
) # noqa: F401

from typing import Set # noqa: F401
from typing_extensions import Protocol

try:
from typing import Protocol
except ModuleNotFoundError:
from typing_extensions import Protocol

class _HasFileno(Protocol):
def fileno(self) -> int:
Expand All @@ -37,7 +41,7 @@ def fileno(self) -> int:


# Collection of selector thread event loops to shut down on exit.
_selector_loops = set() # type: Set[SelectorThread]
_selector_loops: Set["SelectorThread"] = set()


def _atexit_callback() -> None:
Expand Down Expand Up @@ -327,17 +331,17 @@ def close(self) -> None:
self._real_loop.close()

def add_reader(
self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any
self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
) -> None:
return self._selector.add_reader(fd, callback, *args)

def add_writer(
self, fd: "_FileDescriptorLike", callback: Callable[..., None], *args: Any
self, fd: _FileDescriptorLike, callback: Callable[..., None], *args: Any
) -> None:
return self._selector.add_writer(fd, callback, *args)

def remove_reader(self, fd: "_FileDescriptorLike") -> bool:
def remove_reader(self, fd: _FileDescriptorLike) -> bool:
return self._selector.remove_reader(fd)

def remove_writer(self, fd: "_FileDescriptorLike") -> bool:
def remove_writer(self, fd: _FileDescriptorLike) -> bool:
return self._selector.remove_writer(fd)
74 changes: 35 additions & 39 deletions curl_cffi/aio.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,65 +4,60 @@
from typing import Any
from weakref import WeakSet, WeakKeyDictionary

import _cffi_backend # required by _wrapper

from ._wrapper import ffi, lib # type: ignore
from .const import CurlMOpt
from .curl import Curl, DEFAULT_CACERT

__all__ = ["AsyncCurl"]

# registry of asyncio loop : selector thread
_selectors: WeakKeyDictionary = WeakKeyDictionary()
PROACTOR_WARNING = """
Proactor event loop does not implement add_reader family of methods required.
Registering an additional selector thread for add_reader support.
To avoid this warning use:
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
"""

def _get_selector_windows(asyncio_loop) -> asyncio.AbstractEventLoop:
"""Get selector-compatible loop

Returns an object with ``add_reader`` family of methods,
either the loop itself or a SelectorThread instance.

Workaround Windows proactor removal of *reader methods.
if sys.platform == "win32":
PROACTOR_WARNING = """
Proactor event loop does not implement add_reader family of methods required.
Registering an additional selector thread for add_reader support.
To avoid this warning use:
asyncio.set_event_loop_policy(WindowsSelectorEventLoopPolicy())
"""
_selectors: WeakKeyDictionary = WeakKeyDictionary()

if asyncio_loop in _selectors:
return _selectors[asyncio_loop]
def _get_selector(asyncio_loop) -> asyncio.AbstractEventLoop:
"""Get selector-compatible loop

if not isinstance(asyncio_loop, getattr(asyncio, "ProactorEventLoop", type(None))):
return asyncio_loop
Returns an object with ``add_reader`` family of methods,
either the loop itself or a SelectorThread instance.

from ._asyncio_selector import AddThreadSelectorEventLoop
Workaround Windows proactor removal of *reader methods.
"""

warnings.warn(PROACTOR_WARNING, RuntimeWarning)
if asyncio_loop in _selectors:
return _selectors[asyncio_loop]

selector_loop = _selectors[asyncio_loop] = AddThreadSelectorEventLoop(asyncio_loop) # type: ignore
if not isinstance(asyncio_loop, getattr(asyncio, "ProactorEventLoop", type(None))):
return asyncio_loop

# patch loop.close to also close the selector thread
loop_close = asyncio_loop.close
from ._asyncio_selector import AddThreadSelectorEventLoop

def _close_selector_and_loop():
# restore original before calling selector.close,
# which in turn calls eventloop.close!
asyncio_loop.close = loop_close
_selectors.pop(asyncio_loop, None)
selector_loop.close()
warnings.warn(PROACTOR_WARNING, RuntimeWarning)

asyncio_loop.close = _close_selector_and_loop # type: ignore # mypy bug - assign a function to method
return selector_loop
selector_loop = _selectors[asyncio_loop] = AddThreadSelectorEventLoop(asyncio_loop) # type: ignore

# patch loop.close to also close the selector thread
loop_close = asyncio_loop.close

def _get_selector_noop(loop) -> asyncio.AbstractEventLoop:
"""no-op on non-Windows"""
return loop
def _close_selector_and_loop():
# restore original before calling selector.close,
# which in turn calls eventloop.close!
asyncio_loop.close = loop_close
_selectors.pop(asyncio_loop, None)
selector_loop.close()

asyncio_loop.close = _close_selector_and_loop # type: ignore # mypy bug - assign a function to method
return selector_loop

if sys.platform == "win32":
_get_selector = _get_selector_windows
else:
_get_selector = _get_selector_noop
def _get_selector(loop) -> asyncio.AbstractEventLoop:
return loop


CURL_POLL_NONE = 0
Expand Down Expand Up @@ -123,6 +118,7 @@ def socket_function(curl, sockfd: int, what: int, clientp: Any, data: Any):
loop.add_writer(sockfd, async_curl.process_data, sockfd, CURL_CSELECT_OUT)
async_curl._sockfds.add(sockfd)


class AsyncCurl:
"""Wrapper around curl_multi handle to provide asyncio support. It uses the libcurl
socket_action APIs."""
Expand Down
Loading