From c8531c2431a45beeb3c86ec04dcd9a778dd67066 Mon Sep 17 00:00:00 2001 From: avkarenow Date: Thu, 15 Aug 2024 18:39:16 +0200 Subject: [PATCH 1/9] Use len(os.sched_getaffinity(0)) instead of os.cpu_count() (#591) Co-authored-by: Fantix King --- .github/workflows/tests.yml | 1 + setup.py | 6 +++++- 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 61cf881f..345362fd 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -13,6 +13,7 @@ jobs: test: runs-on: ${{ matrix.os }} strategy: + fail-fast: false matrix: python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] os: [ubuntu-latest, macos-latest] diff --git a/setup.py b/setup.py index ba15af50..c369ac80 100644 --- a/setup.py +++ b/setup.py @@ -176,7 +176,11 @@ def build_libuv(self): cmd, cwd=LIBUV_BUILD_DIR, env=env, check=True) - j_flag = '-j{}'.format(os.cpu_count() or 1) + try: + njobs = len(os.sched_getaffinity(0)) + except AttributeError: + njobs = os.cpu_count() + j_flag = '-j{}'.format(njobs or 1) c_flag = "CFLAGS={}".format(env['CFLAGS']) subprocess.run( ['make', j_flag, c_flag], From deb2cf9d653305b55d0d97a85b4067e33db0f1c0 Mon Sep 17 00:00:00 2001 From: Niklas Rousset <75939868+niklasr22@users.noreply.github.com> Date: Thu, 15 Aug 2024 18:52:42 +0200 Subject: [PATCH 2/9] Fix Python version in README.rst (#599) --- README.rst | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.rst b/README.rst index fe6c000d..c279822e 100644 --- a/README.rst +++ b/README.rst @@ -109,7 +109,7 @@ To build uvloop, you'll need Python 3.8 or greater: .. code:: - $ python3.7 -m venv uvloop-dev + $ python3 -m venv uvloop-dev $ source uvloop-dev/bin/activate 3. Install development dependencies: From 8511ba1fc2131ee0ceaecfd5ce57973012e94ef9 Mon Sep 17 00:00:00 2001 From: Karolina Surma <33810531+befeleme@users.noreply.github.com> Date: Thu, 15 Aug 2024 19:31:00 +0200 Subject: [PATCH 3/9] Inline _Py_RestoreSignals() from CPython (#604) private _Py_RestoreSignals() has been moved to CPython internals as of Python 3.13 See: https://github.com/python/cpython/pull/106400 Its implementation has been the same in all supported by uvloop Pythons (3.8+), so the inlining was not conditionalized. --- uvloop/includes/compat.h | 20 ++++++++++++++++++++ uvloop/includes/python.pxd | 4 ++-- 2 files changed, 22 insertions(+), 2 deletions(-) diff --git a/uvloop/includes/compat.h b/uvloop/includes/compat.h index 7ae39e73..0c408c9e 100644 --- a/uvloop/includes/compat.h +++ b/uvloop/includes/compat.h @@ -1,5 +1,6 @@ #include #include +#include #include #include #include "Python.h" @@ -83,3 +84,22 @@ int Context_Exit(PyObject *ctx) { } #endif + +/* inlined from cpython/Modules/signalmodule.c + * https://github.com/python/cpython/blob/v3.13.0a6/Modules/signalmodule.c#L1931-L1951 + * private _Py_RestoreSignals has been moved to CPython internals in Python 3.13 + * https://github.com/python/cpython/pull/106400 */ + +void +_Py_RestoreSignals(void) +{ +#ifdef SIGPIPE + PyOS_setsig(SIGPIPE, SIG_DFL); +#endif +#ifdef SIGXFZ + PyOS_setsig(SIGXFZ, SIG_DFL); +#endif +#ifdef SIGXFSZ + PyOS_setsig(SIGXFSZ, SIG_DFL); +#endif +} diff --git a/uvloop/includes/python.pxd b/uvloop/includes/python.pxd index 454d5c77..94007e53 100644 --- a/uvloop/includes/python.pxd +++ b/uvloop/includes/python.pxd @@ -11,8 +11,6 @@ cdef extern from "Python.h": object PyUnicode_EncodeFSDefault(object) void PyErr_SetInterrupt() nogil - void _Py_RestoreSignals() - object PyMemoryView_FromMemory(char *mem, ssize_t size, int flags) object PyMemoryView_FromObject(object obj) int PyMemoryView_Check(object obj) @@ -29,3 +27,5 @@ cdef extern from "includes/compat.h": void PyOS_BeforeFork() void PyOS_AfterFork_Parent() void PyOS_AfterFork_Child() + + void _Py_RestoreSignals() From 2d35f106d3ccc51d648b4ab90c964c0748b5873f Mon Sep 17 00:00:00 2001 From: Fantix King Date: Thu, 15 Aug 2024 13:47:18 -0400 Subject: [PATCH 4/9] uvloop 0.20.0 Changes ======= * Upgrade libuv to v1.48.0 (#600) (by @niklasr22 @fantix in 77778525 for #596 #615) Fixes ===== * Fix test_create_server_4 with Python 3.12.5 (#614) (by @shadchin in 62f92393) * Use len(os.sched_getaffinity(0)) instead of os.cpu_count() (#591) (by @avkarenow in c8531c24 for #591) * Inline _Py_RestoreSignals() from CPython (#604) (by @befeleme in 8511ba1f for #603) --- uvloop/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvloop/_version.py b/uvloop/_version.py index 0667c6a0..3c67960c 100644 --- a/uvloop/_version.py +++ b/uvloop/_version.py @@ -10,4 +10,4 @@ # supported platforms, publish the packages on PyPI, merge the PR # to the target branch, create a Git tag pointing to the commit. -__version__ = '0.19.0' +__version__ = '0.20.0' From 4083a94e5c1c3ab681ffe0b2c0b60fba16540220 Mon Sep 17 00:00:00 2001 From: Peter Tribble Date: Fri, 16 Aug 2024 01:14:19 +0100 Subject: [PATCH 5/9] Use cythonized SO_REUSEPORT rather than the unwrapped native one. (#609) Fixes #550 Co-authored-by: Fantix King --- uvloop/includes/uv.pxd | 2 +- uvloop/loop.pyx | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/uvloop/includes/uv.pxd b/uvloop/includes/uv.pxd index 87651306..2756a306 100644 --- a/uvloop/includes/uv.pxd +++ b/uvloop/includes/uv.pxd @@ -57,7 +57,7 @@ cdef extern from "uv.h" nogil: cdef int SOL_SOCKET cdef int SO_ERROR cdef int SO_REUSEADDR - cdef int SO_REUSEPORT + # use has_SO_REUSEPORT and SO_REUSEPORT in stdlib.pxi instead cdef int AF_INET cdef int AF_INET6 cdef int AF_UNIX diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index 334d8d50..de88b575 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -1775,7 +1775,7 @@ cdef class Loop: if reuse_address: sock.setsockopt(uv.SOL_SOCKET, uv.SO_REUSEADDR, 1) if reuse_port: - sock.setsockopt(uv.SOL_SOCKET, uv.SO_REUSEPORT, 1) + sock.setsockopt(uv.SOL_SOCKET, SO_REUSEPORT, 1) # Disable IPv4/IPv6 dual stack support (enabled by # default on Linux) which makes a single socket # listen on both address families. From 3c3bbeff3418c60b14f793a6541ad01d8036b706 Mon Sep 17 00:00:00 2001 From: jensbjorgensen Date: Mon, 26 Aug 2024 15:04:49 -0500 Subject: [PATCH 6/9] udp errors should result in protocol.error_received (#601) --- uvloop/handles/udp.pyx | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/uvloop/handles/udp.pyx b/uvloop/handles/udp.pyx index bbe60d56..ef20c3f7 100644 --- a/uvloop/handles/udp.pyx +++ b/uvloop/handles/udp.pyx @@ -244,16 +244,15 @@ cdef class UDPTransport(UVBaseTransport): ctx.close() exc = convert_error(err) - self._fatal_error(exc, True) + if isinstance(exc, OSError): + run_in_context1(self.context.copy(), self._protocol.error_received, exc) + else: + self._fatal_error(exc, True) else: self._maybe_pause_protocol() else: - if err < 0: - exc = convert_error(err) - self._fatal_error(exc, True) - else: - self._on_sent(None, self.context.copy()) + self._on_sent(convert_error(err) if err < 0 else None, self.context.copy()) cdef _on_receive(self, bytes data, object exc, object addr): if exc is None: From 3fba9fab1e8d66c333cf3946d79e29b5bad8fa73 Mon Sep 17 00:00:00 2001 From: alan-brooks <12380017+alan-brooks@users.noreply.github.com> Date: Wed, 28 Aug 2024 19:02:42 +0100 Subject: [PATCH 7/9] Updates for Cython3 (#587) * Remove SSL depreciation warnings * Use Cython 3.0 and fix deprecated test --------- Co-authored-by: Fantix King --- Makefile | 2 +- pyproject.toml | 10 +++++---- setup.py | 6 +++-- tests/test_tcp.py | 21 ++++++++++-------- uvloop/_testbase.py | 4 +++- uvloop/dns.pyx | 2 +- uvloop/handles/handle.pyx | 2 +- uvloop/handles/pipe.pyx | 2 +- uvloop/handles/poll.pxd | 2 +- uvloop/handles/poll.pyx | 2 +- uvloop/handles/stream.pyx | 6 ++++- uvloop/includes/consts.pxi | 40 ++++++++++++++++++++-------------- uvloop/includes/fork_handler.h | 6 ++++- uvloop/includes/uv.pxd | 6 ++--- uvloop/loop.pyx | 15 +++++++------ 15 files changed, 76 insertions(+), 50 deletions(-) diff --git a/Makefile b/Makefile index 4375e5d2..6a0475a9 100644 --- a/Makefile +++ b/Makefile @@ -9,7 +9,7 @@ _default: compile clean: - rm -fr dist/ doc/_build/ *.egg-info uvloop/loop.*.pyd + rm -fr dist/ doc/_build/ *.egg-info uvloop/loop.*.pyd uvloop/loop_d.*.pyd rm -fr uvloop/*.c uvloop/*.html uvloop/*.so rm -fr uvloop/handles/*.html uvloop/includes/*.html find . -name '__pycache__' | xargs rm -rf diff --git a/pyproject.toml b/pyproject.toml index c4f93f0b..85c59bf5 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -36,14 +36,16 @@ test = [ # pycodestyle is a dependency of flake8, but it must be frozen because # their combination breaks too often # (example breakage: https://gitlab.com/pycqa/flake8/issues/427) - 'aiohttp>=3.8.1; python_version < "3.12"', - 'aiohttp==3.9.0b0; python_version >= "3.12"', + 'aiohttp>=3.10.5', 'flake8~=5.0', 'psutil', 'pycodestyle~=2.9.0', 'pyOpenSSL~=23.0.0', 'mypy>=0.800', - 'Cython(>=0.29.36,<0.30.0)', +] +dev = [ + 'setuptools>=60', + 'Cython~=3.0', ] docs = [ 'Sphinx~=4.1.2', @@ -55,7 +57,7 @@ docs = [ requires = [ "setuptools>=60", "wheel", - "Cython(>=0.29.36,<0.30.0)", + "Cython~=3.0", ] build-backend = "setuptools.build_meta" diff --git a/setup.py b/setup.py index c369ac80..22a61e0c 100644 --- a/setup.py +++ b/setup.py @@ -21,7 +21,7 @@ from setuptools.command.sdist import sdist -CYTHON_DEPENDENCY = 'Cython(>=0.29.36,<0.30.0)' +CYTHON_DEPENDENCY = 'Cython~=3.0' MACHINE = platform.machine() MODULES_CFLAGS = [os.getenv('UVLOOP_OPT_CFLAGS', '-O2')] _ROOT = pathlib.Path(__file__).parent @@ -144,7 +144,9 @@ def finalize_options(self): self.distribution.ext_modules[:] = cythonize( self.distribution.ext_modules, compiler_directives=directives, - annotate=self.cython_annotate) + annotate=self.cython_annotate, + compile_time_env=dict(DEFAULT_FREELIST_SIZE=250), + emit_linenums=self.debug) super().finalize_options() diff --git a/tests/test_tcp.py b/tests/test_tcp.py index d7a73fbf..8759383d 100644 --- a/tests/test_tcp.py +++ b/tests/test_tcp.py @@ -1631,17 +1631,22 @@ async def client(addr): self.fail("unexpected call to connection_made()") def test_ssl_connect_accepted_socket(self): - if hasattr(ssl, 'PROTOCOL_TLS'): - proto = ssl.PROTOCOL_TLS + if hasattr(ssl, 'PROTOCOL_TLS_SERVER'): + server_proto = ssl.PROTOCOL_TLS_SERVER + client_proto = ssl.PROTOCOL_TLS_CLIENT else: - proto = ssl.PROTOCOL_SSLv23 - server_context = ssl.SSLContext(proto) + if hasattr(ssl, 'PROTOCOL_TLS'): + client_proto = server_proto = ssl.PROTOCOL_TLS + else: + client_proto = server_proto = ssl.PROTOCOL_SSLv23 + + server_context = ssl.SSLContext(server_proto) server_context.load_cert_chain(self.ONLYCERT, self.ONLYKEY) if hasattr(server_context, 'check_hostname'): server_context.check_hostname = False server_context.verify_mode = ssl.CERT_NONE - client_context = ssl.SSLContext(proto) + client_context = ssl.SSLContext(client_proto) if hasattr(server_context, 'check_hostname'): client_context.check_hostname = False client_context.verify_mode = ssl.CERT_NONE @@ -2234,8 +2239,7 @@ def test_renegotiation(self): sslctx.use_privatekey_file(self.ONLYKEY) sslctx.use_certificate_chain_file(self.ONLYCERT) client_sslctx = self._create_client_ssl_context() - if hasattr(ssl, 'OP_NO_TLSv1_3'): - client_sslctx.options |= ssl.OP_NO_TLSv1_3 + client_sslctx.maximum_version = ssl.TLSVersion.TLSv1_2 def server(sock): conn = openssl_ssl.Connection(sslctx, sock) @@ -2593,8 +2597,7 @@ def test_flush_before_shutdown(self): sslctx_openssl.use_privatekey_file(self.ONLYKEY) sslctx_openssl.use_certificate_chain_file(self.ONLYCERT) client_sslctx = self._create_client_ssl_context() - if hasattr(ssl, 'OP_NO_TLSv1_3'): - client_sslctx.options |= ssl.OP_NO_TLSv1_3 + client_sslctx.maximum_version = ssl.TLSVersion.TLSv1_2 future = None diff --git a/uvloop/_testbase.py b/uvloop/_testbase.py index c4a7595b..e620e158 100644 --- a/uvloop/_testbase.py +++ b/uvloop/_testbase.py @@ -269,7 +269,9 @@ def find_free_port(start_from=50000): class SSLTestCase: def _create_server_ssl_context(self, certfile, keyfile=None): - if hasattr(ssl, 'PROTOCOL_TLS'): + if hasattr(ssl, 'PROTOCOL_TLS_SERVER'): + sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + elif hasattr(ssl, 'PROTOCOL_TLS'): sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS) else: sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23) diff --git a/uvloop/dns.pyx b/uvloop/dns.pyx index c6be7cbe..67aeb595 100644 --- a/uvloop/dns.pyx +++ b/uvloop/dns.pyx @@ -298,7 +298,7 @@ cdef class AddrInfo: uv.uv_freeaddrinfo(self.data) # returns void self.data = NULL - cdef void set_data(self, system.addrinfo *data): + cdef void set_data(self, system.addrinfo *data) noexcept: self.data = data cdef unpack(self): diff --git a/uvloop/handles/handle.pyx b/uvloop/handles/handle.pyx index 6efe3755..2c96458b 100644 --- a/uvloop/handles/handle.pyx +++ b/uvloop/handles/handle.pyx @@ -363,7 +363,7 @@ cdef void __uv_close_handle_cb(uv.uv_handle_t* handle) noexcept with gil: Py_DECREF(h) # Was INCREFed in UVHandle._close -cdef void __close_all_handles(Loop loop): +cdef void __close_all_handles(Loop loop) noexcept: uv.uv_walk(loop.uvloop, __uv_walk_close_all_handles_cb, loop) # void diff --git a/uvloop/handles/pipe.pyx b/uvloop/handles/pipe.pyx index 195576c7..d30a7366 100644 --- a/uvloop/handles/pipe.pyx +++ b/uvloop/handles/pipe.pyx @@ -25,7 +25,7 @@ cdef __pipe_init_uv_handle(UVStream handle, Loop loop): cdef __pipe_open(UVStream handle, int fd): cdef int err err = uv.uv_pipe_open(handle._handle, - fd) + fd) if err < 0: exc = convert_error(err) raise exc diff --git a/uvloop/handles/poll.pxd b/uvloop/handles/poll.pxd index d07030b5..c2205402 100644 --- a/uvloop/handles/poll.pxd +++ b/uvloop/handles/poll.pxd @@ -10,7 +10,7 @@ cdef class UVPoll(UVHandle): cdef inline _poll_start(self, int flags) cdef inline _poll_stop(self) - cdef int is_active(self) + cdef int is_active(self) noexcept cdef is_reading(self) cdef is_writing(self) diff --git a/uvloop/handles/poll.pyx b/uvloop/handles/poll.pyx index fca5981e..c905e9b0 100644 --- a/uvloop/handles/poll.pyx +++ b/uvloop/handles/poll.pyx @@ -29,7 +29,7 @@ cdef class UVPoll(UVHandle): handle._init(loop, fd) return handle - cdef int is_active(self): + cdef int is_active(self) noexcept: return (self.reading_handle is not None or self.writing_handle is not None) diff --git a/uvloop/handles/stream.pyx b/uvloop/handles/stream.pyx index d4e02e3e..9fbc5a51 100644 --- a/uvloop/handles/stream.pyx +++ b/uvloop/handles/stream.pyx @@ -1,4 +1,8 @@ -DEF __PREALLOCED_BUFS = 4 +cdef extern from *: + ''' + enum {__PREALLOCED_BUFS = 4}; + ''' + const bint __PREALLOCED_BUFS @cython.no_gc_clear diff --git a/uvloop/includes/consts.pxi b/uvloop/includes/consts.pxi index f765053d..82f3c327 100644 --- a/uvloop/includes/consts.pxi +++ b/uvloop/includes/consts.pxi @@ -1,25 +1,33 @@ -DEF UV_STREAM_RECV_BUF_SIZE = 256000 # 250kb +cdef enum: + UV_STREAM_RECV_BUF_SIZE = 256000 # 250kb -DEF FLOW_CONTROL_HIGH_WATER = 64 # KiB -DEF FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB -DEF FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB + FLOW_CONTROL_HIGH_WATER = 64 # KiB + FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB + FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB -DEF DEFAULT_FREELIST_SIZE = 250 -DEF DNS_PYADDR_TO_SOCKADDR_CACHE_SIZE = 2048 + DEFAULT_FREELIST_SIZE = 250 + DNS_PYADDR_TO_SOCKADDR_CACHE_SIZE = 2048 -DEF DEBUG_STACK_DEPTH = 10 + DEBUG_STACK_DEPTH = 10 -DEF __PROCESS_DEBUG_SLEEP_AFTER_FORK = 1 + __PROCESS_DEBUG_SLEEP_AFTER_FORK = 1 -DEF LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 + LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5 + SSL_READ_MAX_SIZE = 256 * 1024 -# Number of seconds to wait for SSL handshake to complete -# The default timeout matches that of Nginx. -DEF SSL_HANDSHAKE_TIMEOUT = 60.0 -# Number of seconds to wait for SSL shutdown to complete -# The default timeout mimics lingering_time -DEF SSL_SHUTDOWN_TIMEOUT = 30.0 -DEF SSL_READ_MAX_SIZE = 256 * 1024 +cdef extern from *: + ''' + // Number of seconds to wait for SSL handshake to complete + // The default timeout matches that of Nginx. + #define SSL_HANDSHAKE_TIMEOUT 60.0 + + // Number of seconds to wait for SSL shutdown to complete + // The default timeout mimics lingering_time + #define SSL_SHUTDOWN_TIMEOUT 30.0 + ''' + + const float SSL_HANDSHAKE_TIMEOUT + const float SSL_SHUTDOWN_TIMEOUT diff --git a/uvloop/includes/fork_handler.h b/uvloop/includes/fork_handler.h index 47bbe036..9d3573ae 100644 --- a/uvloop/includes/fork_handler.h +++ b/uvloop/includes/fork_handler.h @@ -1,7 +1,10 @@ +#ifndef UVLOOP_FORK_HANDLER_H_ +#define UVLOOP_FORK_HANDLER_H_ + volatile uint64_t MAIN_THREAD_ID = 0; volatile int8_t MAIN_THREAD_ID_SET = 0; -typedef void (*OnForkHandler)(); +typedef void (*OnForkHandler)(void); OnForkHandler __forkHandler = NULL; @@ -36,3 +39,4 @@ void setMainThreadID(uint64_t id) { MAIN_THREAD_ID = id; MAIN_THREAD_ID_SET = 1; } +#endif diff --git a/uvloop/includes/uv.pxd b/uvloop/includes/uv.pxd index 2756a306..510b1498 100644 --- a/uvloop/includes/uv.pxd +++ b/uvloop/includes/uv.pxd @@ -220,7 +220,7 @@ cdef extern from "uv.h" nogil: UV_LEAVE_GROUP = 0, UV_JOIN_GROUP - cpdef enum uv_fs_event: + cdef enum uv_fs_event: UV_RENAME = 1, UV_CHANGE = 2 @@ -282,7 +282,7 @@ cdef extern from "uv.h" nogil: int uv_loop_close(uv_loop_t* loop) int uv_loop_alive(uv_loop_t* loop) int uv_loop_fork(uv_loop_t* loop) - int uv_backend_fd(uv_loop_t* loop) + uv_os_fd_t uv_backend_fd(uv_loop_t* loop) void uv_update_time(uv_loop_t* loop) uint64_t uv_now(const uv_loop_t*) @@ -378,7 +378,7 @@ cdef extern from "uv.h" nogil: # Pipes int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc) - int uv_pipe_open(uv_pipe_t* handle, uv_file file) + int uv_pipe_open(uv_pipe_t* handle, uv_os_fd_t file) int uv_pipe_bind(uv_pipe_t* handle, const char* name) void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle, diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index de88b575..24df3e8a 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -43,7 +43,6 @@ from cpython.pycapsule cimport PyCapsule_New, PyCapsule_GetPointer from . import _noop -include "includes/consts.pxi" include "includes/stdlib.pxi" include "errors.pyx" @@ -1118,7 +1117,7 @@ cdef class Loop: cdef _sock_set_reuseport(self, int fd): cdef: - int err + int err = 0 int reuseport_flag = 1 err = system.setsockopt( @@ -1396,8 +1395,7 @@ cdef class Loop: def set_debug(self, enabled): self._debug = bool(enabled) if self.is_running(): - self.call_soon_threadsafe( - self._set_coroutine_debug, self, self._debug) + self.call_soon_threadsafe(self._set_coroutine_debug, self._debug) def is_running(self): """Return whether the event loop is currently running.""" @@ -2749,8 +2747,7 @@ cdef class Loop: start_new_session=False, executable=None, pass_fds=(), - # For tests only! Do not use in your code. Ever. - __uvloop_sleep_after_fork=False): + **kwargs): # TODO: Implement close_fds (might not be very important in # Python 3.5, since all FDs aren't inheritable by default.) @@ -2770,8 +2767,12 @@ cdef class Loop: if executable is not None: args[0] = executable - if __uvloop_sleep_after_fork: + # For tests only! Do not use in your code. Ever. + if kwargs.pop("__uvloop_sleep_after_fork", False): debug_flags |= __PROCESS_DEBUG_SLEEP_AFTER_FORK + if kwargs: + raise ValueError( + 'unexpected kwargs: {}'.format(', '.join(kwargs.keys()))) waiter = self._new_future() protocol = protocol_factory() From fb5a1397d162959894b941cf33d2e7519224a5f1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Edgar=20Ram=C3=ADrez=20Mondrag=C3=B3n?= <16805946+edgarrmondragon@users.noreply.github.com> Date: Mon, 2 Sep 2024 14:57:59 -0600 Subject: [PATCH 8/9] Add cleanup_socket param on create_unix_server() (#623) This is derived from python/cpython#111483 but available on all Python versions with uvloop, only that it's only enabled by default for Python 3.13 and above to be consistent with CPython behavior. * Also add Python 3.13 to CI (#610) * Enable CI in debug mode * Fix CI to include [dev] --------- Co-authored-by: Fantix King --- .github/workflows/release.yml | 10 +++++++-- .github/workflows/tests.yml | 16 ++++++++++---- pyproject.toml | 1 + setup.py | 1 + tests/test_unix.py | 40 +++++++++++++++++++++-------------- uvloop/handles/pipe.pyx | 21 ++++++++++++++++++ uvloop/includes/stdlib.pxi | 1 + uvloop/loop.pxd | 1 + uvloop/loop.pyx | 24 +++++++++++++++++++-- 9 files changed, 91 insertions(+), 24 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index cf707d07..c5075a32 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -76,7 +76,13 @@ jobs: fail-fast: false matrix: os: [ubuntu-latest, macos-latest] - cibw_python: ["cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"] + cibw_python: + - "cp38-*" + - "cp39-*" + - "cp310-*" + - "cp311-*" + - "cp312-*" + - "cp313-*" cibw_arch: ["x86_64", "aarch64", "universal2"] exclude: - os: ubuntu-latest @@ -108,7 +114,7 @@ jobs: run: | brew install gnu-sed libtool autoconf automake - - uses: pypa/cibuildwheel@fff9ec32ed25a9c576750c91e06b410ed0c15db7 # v2.16.2 + - uses: pypa/cibuildwheel@bd033a44476646b606efccdd5eed92d5ea1d77ad # v2.20.0 env: CIBW_BUILD_VERBOSITY: 1 CIBW_BUILD: ${{ matrix.cibw_python }} diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 345362fd..ed6accb9 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -15,7 +15,13 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] + python-version: + - "3.8" + - "3.9" + - "3.10" + - "3.11" + - "3.12" + - "3.13" os: [ubuntu-latest, macos-latest] env: @@ -42,6 +48,7 @@ jobs: if: steps.release.outputs.version == 0 with: python-version: ${{ matrix.python-version }} + allow-prereleases: true - name: Install macOS deps if: matrix.os == 'macos-latest' && steps.release.outputs.version == 0 @@ -50,8 +57,10 @@ jobs: - name: Install Python Deps if: steps.release.outputs.version == 0 + env: + PIP_PRE: ${{ matrix.python-version == '3.13' && '1' || '0' }} run: | - pip install -e .[test] + pip install -e .[test,dev] - name: Test if: steps.release.outputs.version == 0 @@ -59,8 +68,7 @@ jobs: make test - name: Test (debug build) - # XXX Re-enable 3.12 once we migrate to Cython 3 - if: steps.release.outputs.version == 0 && matrix.python-version != '3.12' + if: steps.release.outputs.version == 0 run: | make distclean && make debug && make test diff --git a/pyproject.toml b/pyproject.toml index 85c59bf5..a656b4f8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -24,6 +24,7 @@ classifiers = [ "Programming Language :: Python :: 3.10", "Programming Language :: Python :: 3.11", "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", "Programming Language :: Python :: Implementation :: CPython", "Topic :: System :: Networking", ] diff --git a/setup.py b/setup.py index 22a61e0c..32d94ae8 100644 --- a/setup.py +++ b/setup.py @@ -140,6 +140,7 @@ def finalize_options(self): v = True directives[k] = v + self.cython_directives = directives self.distribution.ext_modules[:] = cythonize( self.distribution.ext_modules, diff --git a/tests/test_unix.py b/tests/test_unix.py index 8adedeef..0d670e39 100644 --- a/tests/test_unix.py +++ b/tests/test_unix.py @@ -96,10 +96,18 @@ async def start_server(): self.assertFalse(srv.is_serving()) - # asyncio doesn't cleanup the sock file - self.assertTrue(os.path.exists(sock_name)) + if sys.version_info < (3, 13): + # asyncio doesn't cleanup the sock file under Python 3.13 + self.assertTrue(os.path.exists(sock_name)) + else: + self.assertFalse(os.path.exists(sock_name)) + + async def start_server_sock(start_server, is_unix_api=True): + # is_unix_api indicates whether `start_server` is calling + # `loop.create_unix_server()` or `loop.create_server()`, + # because asyncio `loop.create_server()` doesn't cleanup + # the socket file even if it's a UNIX socket. - async def start_server_sock(start_server): nonlocal CNT CNT = 0 @@ -140,8 +148,11 @@ async def start_server_sock(start_server): self.assertFalse(srv.is_serving()) - # asyncio doesn't cleanup the sock file - self.assertTrue(os.path.exists(sock_name)) + if sys.version_info < (3, 13) or not is_unix_api: + # asyncio doesn't cleanup the sock file under Python 3.13 + self.assertTrue(os.path.exists(sock_name)) + else: + self.assertFalse(os.path.exists(sock_name)) with self.subTest(func='start_unix_server(host, port)'): self.loop.run_until_complete(start_server()) @@ -160,7 +171,7 @@ async def start_server_sock(start_server): lambda sock: asyncio.start_server( handle_client, None, None, - sock=sock))) + sock=sock), is_unix_api=False)) self.assertEqual(CNT, TOTAL_CNT) def test_create_unix_server_2(self): @@ -455,16 +466,13 @@ def test_create_unix_server_path_stream_bittype(self): socket.AF_UNIX, socket.SOCK_STREAM | socket.SOCK_NONBLOCK) with tempfile.NamedTemporaryFile() as file: fn = file.name - try: - with sock: - sock.bind(fn) - coro = self.loop.create_unix_server(lambda: None, path=None, - sock=sock) - srv = self.loop.run_until_complete(coro) - srv.close() - self.loop.run_until_complete(srv.wait_closed()) - finally: - os.unlink(fn) + with sock: + sock.bind(fn) + coro = self.loop.create_unix_server(lambda: None, path=None, + sock=sock, cleanup_socket=True) + srv = self.loop.run_until_complete(coro) + srv.close() + self.loop.run_until_complete(srv.wait_closed()) @unittest.skipUnless(sys.platform.startswith('linux'), 'requires epoll') def test_epollhup(self): diff --git a/uvloop/handles/pipe.pyx b/uvloop/handles/pipe.pyx index d30a7366..4b95ed6e 100644 --- a/uvloop/handles/pipe.pyx +++ b/uvloop/handles/pipe.pyx @@ -80,6 +80,27 @@ cdef class UnixServer(UVStreamServer): context) return tr + cdef _close(self): + sock = self._fileobj + if sock is not None and sock in self._loop._unix_server_sockets: + path = sock.getsockname() + else: + path = None + + UVStreamServer._close(self) + + if path is not None: + prev_ino = self._loop._unix_server_sockets[sock] + del self._loop._unix_server_sockets[sock] + try: + if os_stat(path).st_ino == prev_ino: + os_unlink(path) + except FileNotFoundError: + pass + except OSError as err: + aio_logger.error('Unable to clean up listening UNIX socket ' + '%r: %r', path, err) + @cython.no_gc_clear cdef class UnixTransport(UVStream): diff --git a/uvloop/includes/stdlib.pxi b/uvloop/includes/stdlib.pxi index e7957fe2..4152b8a7 100644 --- a/uvloop/includes/stdlib.pxi +++ b/uvloop/includes/stdlib.pxi @@ -112,6 +112,7 @@ cdef os_pipe = os.pipe cdef os_read = os.read cdef os_remove = os.remove cdef os_stat = os.stat +cdef os_unlink = os.unlink cdef os_fspath = os.fspath cdef stat_S_ISSOCK = stat.S_ISSOCK diff --git a/uvloop/loop.pxd b/uvloop/loop.pxd index 56134733..01e39ae1 100644 --- a/uvloop/loop.pxd +++ b/uvloop/loop.pxd @@ -58,6 +58,7 @@ cdef class Loop: set _processes dict _fd_to_reader_fileobj dict _fd_to_writer_fileobj + dict _unix_server_sockets set _signals dict _signal_handlers diff --git a/uvloop/loop.pyx b/uvloop/loop.pyx index 24df3e8a..f9a5a239 100644 --- a/uvloop/loop.pyx +++ b/uvloop/loop.pyx @@ -50,6 +50,7 @@ include "errors.pyx" cdef: int PY39 = PY_VERSION_HEX >= 0x03090000 int PY311 = PY_VERSION_HEX >= 0x030b0000 + int PY313 = PY_VERSION_HEX >= 0x030d0000 uint64_t MAX_SLEEP = 3600 * 24 * 365 * 100 @@ -155,6 +156,8 @@ cdef class Loop: self._fd_to_reader_fileobj = {} self._fd_to_writer_fileobj = {} + self._unix_server_sockets = {} + self._timers = set() self._polls = {} @@ -1704,7 +1707,10 @@ cdef class Loop: 'host/port and sock can not be specified at the same time') return await self.create_unix_server( protocol_factory, sock=sock, backlog=backlog, ssl=ssl, - start_serving=start_serving) + start_serving=start_serving, + # asyncio won't clean up socket file using create_server() API + cleanup_socket=False, + ) server = Server(self) @@ -2089,7 +2095,7 @@ cdef class Loop: *, backlog=100, sock=None, ssl=None, ssl_handshake_timeout=None, ssl_shutdown_timeout=None, - start_serving=True): + start_serving=True, cleanup_socket=PY313): """A coroutine which creates a UNIX Domain Socket server. The return value is a Server object, which can be used to stop @@ -2114,6 +2120,11 @@ cdef class Loop: ssl_shutdown_timeout is the time in seconds that an SSL server will wait for completion of the SSL shutdown before aborting the connection. Default is 30s. + + If *cleanup_socket* is true then the Unix socket will automatically + be removed from the filesystem when the server is closed, unless the + socket has been replaced after the server has been created. + This defaults to True on Python 3.13 and above, or False otherwise. """ cdef: UnixServer pipe @@ -2191,6 +2202,15 @@ cdef class Loop: # we want Python socket object to notice that. sock.setblocking(False) + if cleanup_socket: + path = sock.getsockname() + # Check for abstract socket. `str` and `bytes` paths are supported. + if path[0] not in (0, '\x00'): + try: + self._unix_server_sockets[sock] = os_stat(path).st_ino + except FileNotFoundError: + pass + pipe = UnixServer.new( self, protocol_factory, server, backlog, ssl, ssl_handshake_timeout, ssl_shutdown_timeout) From 79d5dcdb0785d9319b8b04f99e7a733ed56e9b46 Mon Sep 17 00:00:00 2001 From: Fantix King Date: Mon, 2 Sep 2024 17:01:46 -0400 Subject: [PATCH 9/9] uvloop 0.21.0beta1 --- uvloop/_version.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/uvloop/_version.py b/uvloop/_version.py index 3c67960c..8210e8e8 100644 --- a/uvloop/_version.py +++ b/uvloop/_version.py @@ -10,4 +10,4 @@ # supported platforms, publish the packages on PyPI, merge the PR # to the target branch, create a Git tag pointing to the commit. -__version__ = '0.20.0' +__version__ = '0.21.0beta1'