Skip to content

Port uvloop to Python 3.12 #570

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

Merged
merged 17 commits into from
Oct 10, 2023
Merged
Show file tree
Hide file tree
Changes from 11 commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ jobs:
strategy:
matrix:
os: [ubuntu-latest, macos-latest]
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*"]
cibw_python: ["cp37-*", "cp38-*", "cp39-*", "cp310-*", "cp311-*", "cp312-*"]
cibw_arch: ["x86_64", "aarch64", "universal2"]
exclude:
- os: ubuntu-latest
Expand Down
10 changes: 8 additions & 2 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ${{ matrix.os }}
strategy:
matrix:
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"]
python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"]
os: [ubuntu-latest, macos-latest]

env:
Expand Down Expand Up @@ -47,6 +47,11 @@ jobs:
run: |
brew install gnu-sed libtool autoconf automake

- name: Install setuptools for 3.12
if: matrix.python-version == '3.12'
run: |
pip install setuptools

- name: Install Python Deps
if: steps.release.outputs.version == 0
run: |
Expand All @@ -58,7 +63,8 @@ jobs:
make test

- name: Test (debug build)
if: steps.release.outputs.version == 0
# XXX Re-enable 3.12 once we migrate to Cython 3
if: steps.release.outputs.version == 0 && matrix.python-version != '3.12'
run: |
make distclean && make debug && make test

Expand Down
12 changes: 10 additions & 2 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,13 @@
from setuptools.command.sdist import sdist


CYTHON_DEPENDENCY = 'Cython(>=0.29.32,<0.30.0)'
CYTHON_DEPENDENCY = 'Cython(>=0.29.36,<0.30.0)'

# Minimal dependencies required to test uvloop.
TEST_DEPENDENCIES = [
# 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',
'flake8~=5.0',
'psutil',
'pycodestyle~=2.9.0',
Expand All @@ -37,6 +36,15 @@
CYTHON_DEPENDENCY,
]

if vi < (3, 12):
# XXX Revert this change later.
#
# Python 3.12 is new and there's no aiohttp wheel for it yet.
# And pip helfully fails on building a wheel for it and I've
# no idea how to disable that.
TEST_DEPENDENCIES += ['aiohttp>=3.8.1']


# Dependencies required to build documentation.
DOC_DEPENDENCIES = [
'Sphinx~=4.1.2',
Expand Down
5 changes: 0 additions & 5 deletions tests/cython_helper.pyx

This file was deleted.

23 changes: 12 additions & 11 deletions tests/test_libuv_api.py
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
from uvloop import _testbase as tb
from uvloop.loop import libuv_get_loop_t_ptr, libuv_get_version
from uvloop.loop import _testhelper_unwrap_capsuled_pointer as unwrap


class Test_UV_libuv(tb.UVTestCase):
def test_libuv_get_loop_t_ptr(self):
loop = self.new_loop()
cap1 = libuv_get_loop_t_ptr(loop)
cap2 = libuv_get_loop_t_ptr(loop)
cap3 = libuv_get_loop_t_ptr(self.new_loop())
loop1 = self.new_loop()
cap1 = libuv_get_loop_t_ptr(loop1)
cap2 = libuv_get_loop_t_ptr(loop1)

import pyximport
loop2 = self.new_loop()
cap3 = libuv_get_loop_t_ptr(loop2)

pyximport.install()

import cython_helper

self.assertTrue(cython_helper.capsule_equals(cap1, cap2))
self.assertFalse(cython_helper.capsule_equals(cap1, cap3))
try:
self.assertEqual(unwrap(cap1), unwrap(cap2))
self.assertNotEqual(unwrap(cap1), unwrap(cap3))
finally:
loop1.close()
loop2.close()

def test_libuv_get_version(self):
self.assertGreater(libuv_get_version(), 0)
27 changes: 27 additions & 0 deletions tests/test_tcp.py
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,15 @@ async def start_server():
self.loop.call_soon(srv.close)
await srv.wait_closed()

if (
self.implementation == 'asyncio'
and sys.version_info[:3] >= (3, 12, 0)
):
# asyncio regression in 3.12 -- wait_closed()
# doesn't wait for `close()` to actually complete.
# https://github.com/python/cpython/issues/79033
await asyncio.sleep(1)

# Check that the server cleaned-up proxy-sockets
for srv_sock in srv_socks:
self.assertEqual(srv_sock.fileno(), -1)
Expand Down Expand Up @@ -168,6 +177,15 @@ async def start_server_sock():
srv.close()
await srv.wait_closed()

if (
self.implementation == 'asyncio'
and sys.version_info[:3] >= (3, 12, 0)
):
# asyncio regression in 3.12 -- wait_closed()
# doesn't wait for `close()` to actually complete.
# https://github.com/python/cpython/issues/79033
await asyncio.sleep(1)

# Check that the server cleaned-up proxy-sockets
for srv_sock in srv_socks:
self.assertEqual(srv_sock.fileno(), -1)
Expand Down Expand Up @@ -205,6 +223,15 @@ async def start_server_ephemeral_ports():
self.loop.call_soon(srv.close)
await srv.wait_closed()

if (
self.implementation == 'asyncio'
and sys.version_info[:3] >= (3, 12, 0)
):
# asyncio regression in 3.12 -- wait_closed()
# doesn't wait for `close()` to actually complete.
# https://github.com/python/cpython/issues/79033
await asyncio.sleep(1)

# Check that the server cleaned-up proxy-sockets
for srv_sock in srv_socks:
self.assertEqual(srv_sock.fileno(), -1)
Expand Down
18 changes: 18 additions & 0 deletions tests/test_unix.py
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,15 @@ async def start_server():
self.loop.call_soon(srv.close)
await srv.wait_closed()

if (
self.implementation == 'asyncio'
and sys.version_info[:3] >= (3, 12, 0)
):
# asyncio regression in 3.12 -- wait_closed()
# doesn't wait for `close()` to actually complete.
# https://github.com/python/cpython/issues/79033
await asyncio.sleep(1)

# Check that the server cleaned-up proxy-sockets
for srv_sock in srv_socks:
self.assertEqual(srv_sock.fileno(), -1)
Expand Down Expand Up @@ -116,6 +125,15 @@ async def start_server_sock(start_server):
self.loop.call_soon(srv.close)
await srv.wait_closed()

if (
self.implementation == 'asyncio'
and sys.version_info[:3] >= (3, 12, 0)
):
# asyncio regression in 3.12 -- wait_closed()
# doesn't wait for `close()` to actually complete.
# https://github.com/python/cpython/issues/79033
await asyncio.sleep(1)

# Check that the server cleaned-up proxy-sockets
for srv_sock in srv_socks:
self.assertEqual(srv_sock.fileno(), -1)
Expand Down
17 changes: 11 additions & 6 deletions uvloop/dns.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -414,8 +414,11 @@ cdef _intenum_converter(value, enum_klass):
return value


cdef void __on_addrinfo_resolved(uv.uv_getaddrinfo_t *resolver,
int status, system.addrinfo *res) with gil:
cdef void __on_addrinfo_resolved(
uv.uv_getaddrinfo_t *resolver,
int status,
system.addrinfo *res,
) noexcept with gil:

if resolver.data is NULL:
aio_logger.error(
Expand Down Expand Up @@ -443,10 +446,12 @@ cdef void __on_addrinfo_resolved(uv.uv_getaddrinfo_t *resolver,
request.on_done()


cdef void __on_nameinfo_resolved(uv.uv_getnameinfo_t* req,
int status,
const char* hostname,
const char* service) with gil:
cdef void __on_nameinfo_resolved(
uv.uv_getnameinfo_t* req,
int status,
const char* hostname,
const char* service,
) noexcept with gil:
cdef:
NameInfoRequest request = <NameInfoRequest> req.data
Loop loop = request.loop
Expand Down
4 changes: 3 additions & 1 deletion uvloop/handles/async_.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,9 @@ cdef class UVAsync(UVHandle):
return handle


cdef void __uvasync_callback(uv.uv_async_t* handle) with gil:
cdef void __uvasync_callback(
uv.uv_async_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVAsync callback") == 0:
return

Expand Down
4 changes: 3 additions & 1 deletion uvloop/handles/check.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ cdef class UVCheck(UVHandle):
return handle


cdef void cb_check_callback(uv.uv_check_t* handle) with gil:
cdef void cb_check_callback(
uv.uv_check_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVCheck callback") == 0:
return

Expand Down
8 changes: 6 additions & 2 deletions uvloop/handles/fsevent.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,12 @@ cdef class UVFSEvent(UVHandle):
return handle


cdef void __uvfsevent_callback(uv.uv_fs_event_t* handle, const char *filename,
int events, int status) with gil:
cdef void __uvfsevent_callback(
uv.uv_fs_event_t* handle,
const char *filename,
int events,
int status,
) noexcept with gil:
if __ensure_handle_data(
<uv.uv_handle_t*>handle, "UVFSEvent callback"
) == 0:
Expand Down
6 changes: 4 additions & 2 deletions uvloop/handles/handle.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -335,7 +335,7 @@ cdef inline bint __ensure_handle_data(uv.uv_handle_t* handle,
return 1


cdef void __uv_close_handle_cb(uv.uv_handle_t* handle) with gil:
cdef void __uv_close_handle_cb(uv.uv_handle_t* handle) noexcept with gil:
cdef UVHandle h

if handle.data is NULL:
Expand Down Expand Up @@ -370,7 +370,9 @@ cdef void __close_all_handles(Loop loop):


cdef void __uv_walk_close_all_handles_cb(
uv.uv_handle_t* handle, void* arg) with gil:
uv.uv_handle_t* handle,
void* arg,
) noexcept with gil:

cdef:
Loop loop = <Loop>arg
Expand Down
4 changes: 3 additions & 1 deletion uvloop/handles/idle.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,9 @@ cdef class UVIdle(UVHandle):
return handle


cdef void cb_idle_callback(uv.uv_idle_t* handle) with gil:
cdef void cb_idle_callback(
uv.uv_idle_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVIdle callback") == 0:
return

Expand Down
5 changes: 4 additions & 1 deletion uvloop/handles/pipe.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -202,7 +202,10 @@ cdef class _PipeConnectRequest(UVRequest):
addr,
__pipe_connect_callback)

cdef void __pipe_connect_callback(uv.uv_connect_t* req, int status) with gil:
cdef void __pipe_connect_callback(
uv.uv_connect_t* req,
int status,
) noexcept with gil:
cdef:
_PipeConnectRequest wrapper
UnixTransport transport
Expand Down
7 changes: 5 additions & 2 deletions uvloop/handles/poll.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -190,8 +190,11 @@ cdef class UVPoll(UVHandle):
self._close()


cdef void __on_uvpoll_event(uv.uv_poll_t* handle,
int status, int events) with gil:
cdef void __on_uvpoll_event(
uv.uv_poll_t* handle,
int status,
int events,
) noexcept with gil:

if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVPoll callback") == 0:
return
Expand Down
12 changes: 8 additions & 4 deletions uvloop/handles/process.pyx
Original file line number Diff line number Diff line change
Expand Up @@ -730,9 +730,11 @@ cdef __process_convert_fileno(object obj):
return fileno


cdef void __uvprocess_on_exit_callback(uv.uv_process_t *handle,
int64_t exit_status,
int term_signal) with gil:
cdef void __uvprocess_on_exit_callback(
uv.uv_process_t *handle,
int64_t exit_status,
int term_signal,
) noexcept with gil:

if __ensure_handle_data(<uv.uv_handle_t*>handle,
"UVProcess exit callback") == 0:
Expand Down Expand Up @@ -761,5 +763,7 @@ cdef __socketpair():
return fds[0], fds[1]


cdef void __uv_close_process_handle_cb(uv.uv_handle_t* handle) with gil:
cdef void __uv_close_process_handle_cb(
uv.uv_handle_t* handle
) noexcept with gil:
PyMem_RawFree(handle)
Loading