From 71c8a3584ee51776ea6a4a854d166e262e200562 Mon Sep 17 00:00:00 2001 From: Vizonex Date: Thu, 1 Oct 2026 11:36:09 -0500 Subject: [PATCH 1/2] add future attribute to dns requests --- winloop/dns.pyx | 26 ++++++++++++++++++-------- winloop/loop.pyx | 14 +++++++------- 2 files changed, 25 insertions(+), 15 deletions(-) diff --git a/winloop/dns.pyx b/winloop/dns.pyx index e6afb25..072442a 100644 --- a/winloop/dns.pyx +++ b/winloop/dns.pyx @@ -337,13 +337,16 @@ cdef class AddrInfo: cdef class AddrInfoRequest(UVRequest): cdef: system.addrinfo hints + # TODO: ctypedef callbacks. object callback + object fut uv.uv_getaddrinfo_t _req_data + def __cinit__(self, Loop loop, bytes host, bytes port, int family, int type, int proto, int flags, - object callback): + object callback, object fut): cdef: int err @@ -379,6 +382,7 @@ cdef class AddrInfoRequest(UVRequest): self.request = &self._req_data self.callback = callback + self.fut = fut self.request.data = self err = uv.uv_getaddrinfo(loop.uvloop, @@ -406,19 +410,22 @@ cdef class AddrInfoRequest(UVRequest): else: ex = convert_error(err) except Exception as ex: - callback(ex) + callback(fut, ex) else: - callback(ex) + callback(fut, ex) cdef class NameInfoRequest(UVRequest): cdef: + # TODO: ctypedef callbacks. object callback + object fut uv.uv_getnameinfo_t _req_data - def __cinit__(self, Loop loop, callback): + def __cinit__(self, Loop loop, callback, fut): self.request = &self._req_data self.callback = callback + self.fut = fut self.request.data = self cdef query(self, system.sockaddr *addr, int flags): @@ -455,15 +462,17 @@ cdef void __on_addrinfo_resolved( AddrInfoRequest request = resolver.data Loop loop = request.loop object callback = request.callback + object fut = request.fut AddrInfo ai + try: if status < 0: - callback(convert_error(status)) + callback(fut, convert_error(status)) else: ai = AddrInfo() ai.set_data(res) - callback(ai) + callback(fut, ai) except (KeyboardInterrupt, SystemExit): raise except BaseException as ex: @@ -481,13 +490,14 @@ cdef void __on_nameinfo_resolved( cdef: NameInfoRequest request = req.data Loop loop = request.loop + object fut = request.fut object callback = request.callback try: if status < 0: - callback(convert_error(status)) + callback(fut, convert_error(status)) else: - callback(((hostname).decode(), + callback(fut, ((hostname).decode(), (service).decode())) except (KeyboardInterrupt, SystemExit): raise diff --git a/winloop/loop.pyx b/winloop/loop.pyx index ffaaa01..51bf827 100644 --- a/winloop/loop.pyx +++ b/winloop/loop.pyx @@ -920,7 +920,8 @@ cdef class Loop: # Let's Convert this callback to a ctypedef callback # for added performance. # example: ctypedef int (*addrinfo_callback)(Future fut, object result) except -1 - def callback(fut, result): + def callback(_fut, result): + cdef Future fut = _fut if AddrInfo.isinstance(result): try: if unpack == 0: @@ -939,9 +940,7 @@ cdef class Loop: if not fut.cancelled(): fut.set_exception(result) - # NOTE: ft_partial is needed to hold future for long enough - # otherwise 3.14t could freeze. - AddrInfoRequest(self, host, port, family, type, proto, flags, ft_partial(callback, fut)) + AddrInfoRequest(self, host, port, family, type, proto, flags, callback, fut) return fut cdef Future _getnameinfo(self, system.sockaddr *addr, int flags): @@ -953,12 +952,13 @@ cdef class Loop: # Something like ctypedef int (*nameinfo_callback)(Future, object) except -1 # would be sufficient. def callback(fut, result): + cdef Future _fut = fut if isinstance(result, tuple): - fut.set_result(result) + _fut.set_result(result) else: - fut.set_exception(result) + _fut.set_exception(result) - nr = NameInfoRequest(self, ft_partial(callback, fut)) + nr = NameInfoRequest(self, callback, fut) nr.query(addr, flags) return fut From 0a2cdf7f8a71ecbb0ac69b8011c8b0e3defe31dd Mon Sep 17 00:00:00 2001 From: Vizonex Date: Fri, 2 Oct 2026 09:58:44 -0500 Subject: [PATCH 2/2] improve nameinfo and addrinfo with new awaitable containers for extra control and higher performance --- winloop/dns.pyx | 78 ++++++++++++++++++++++++++++++++++++++++-------- winloop/loop.pxd | 4 +-- winloop/loop.pyx | 62 +++++++++++++++++++------------------- 3 files changed, 100 insertions(+), 44 deletions(-) diff --git a/winloop/dns.pyx b/winloop/dns.pyx index 072442a..1835868 100644 --- a/winloop/dns.pyx +++ b/winloop/dns.pyx @@ -334,19 +334,22 @@ cdef class AddrInfo: return type(other) is AddrInfo +# part of addrinfo's todolist. For now please leave this function here for the library owner/author. +ctypedef int (*addrinfo_callback)(object fut, object result) except -1 + cdef class AddrInfoRequest(UVRequest): cdef: system.addrinfo hints # TODO: ctypedef callbacks. object callback - object fut + AddrInfoFuture fut uv.uv_getaddrinfo_t _req_data def __cinit__(self, Loop loop, bytes host, bytes port, int family, int type, int proto, int flags, - object callback, object fut): + object callback): cdef: int err @@ -382,7 +385,7 @@ cdef class AddrInfoRequest(UVRequest): self.request = &self._req_data self.callback = callback - self.fut = fut + self.fut = AddrInfoFuture(loop, self) self.request.data = self err = uv.uv_getaddrinfo(loop.uvloop, @@ -410,24 +413,34 @@ cdef class AddrInfoRequest(UVRequest): else: ex = convert_error(err) except Exception as ex: - callback(fut, ex) + callback(self.fut, ex) else: - callback(fut, ex) + callback(self.fut, ex) + + + +# TODO: Combine UVRequest subclasses and Future objects together. +# It will mean less callbacks & costly interpreter code required. +ctypedef int (*nameinfo_callback)(object fut, object exception) except -1 + cdef class NameInfoRequest(UVRequest): cdef: # TODO: ctypedef callbacks. - object callback - object fut + nameinfo_callback callback + NameInfoFuture fut uv.uv_getnameinfo_t _req_data - def __cinit__(self, Loop loop, callback, fut): + def __cinit__(self, Loop loop): self.request = &self._req_data - self.callback = callback - self.fut = fut + self.fut = NameInfoFuture(loop, self) self.request.data = self + cdef set_callback(self, nameinfo_callback cb): + self.callback = cb + + cdef query(self, system.sockaddr *addr, int flags): cdef int err err = uv.uv_getnameinfo(self.loop.uvloop, @@ -437,7 +450,48 @@ cdef class NameInfoRequest(UVRequest): flags) if err < 0: self.on_done() - self.callback(convert_error(err)) + self.callback(self.fut, convert_error(err)) + + +# XXX: These subclasses are to ensure awaiting these objects does not +# trigger deadlocks (Example: what if NameInfoRequest is considered as being +# out of scope) + +cdef class NameInfoFuture(Future): + cdef: + Loop loop + NameInfoRequest req + + def __init__(self, Loop loop, NameInfoRequest req): + super().__init__(loop=loop) + self.req = req + + cpdef object set_result(self, result): + return Future.set_result(self, result) + + cpdef object set_exception(self, exc): + return Future.set_exception(self, exc) + + + +cdef class AddrInfoFuture(Future): + cdef: + Loop loop + AddrInfoRequest req + + def __init__(self, Loop loop, AddrInfoRequest req): + super().__init__(loop=loop) + self.req = req + + cpdef object set_result(self, result): + return Future.set_result(self, result) + + cpdef object set_exception(self, exc): + return Future.set_exception(self, exc) + + cpdef bint cancelled(self) noexcept: + return Future.cancelled(self) + cdef _intenum_converter(value, enum_klass): @@ -491,7 +545,7 @@ cdef void __on_nameinfo_resolved( NameInfoRequest request = req.data Loop loop = request.loop object fut = request.fut - object callback = request.callback + nameinfo_callback callback = request.callback try: if status < 0: diff --git a/winloop/loop.pxd b/winloop/loop.pxd index c942d51..e70d923 100644 --- a/winloop/loop.pxd +++ b/winloop/loop.pxd @@ -159,12 +159,12 @@ cdef class Loop: cdef inline _check_closed(self) cdef inline _check_thread(self) - cdef Future _getaddrinfo(self, object host, object port, + cdef _getaddrinfo(self, object host, object port, int family, int type, int proto, int flags, int unpack) - cdef Future _getnameinfo(self, system.sockaddr *addr, int flags) + cdef _getnameinfo(self, system.sockaddr *addr, int flags) cdef _track_transport(self, UVBaseTransport transport) cdef _fileobj_to_fd(self, fileobj) diff --git a/winloop/loop.pyx b/winloop/loop.pyx index 51bf827..7ecc796 100644 --- a/winloop/loop.pyx +++ b/winloop/loop.pyx @@ -116,6 +116,19 @@ cdef inline run_in_context2(context, method, arg1, arg2): Context_Exit(context) +cdef int on_nameinfo_cb(fut, result) except -1: + cdef Future _fut = fut + + # Ensure we can still stop midway through for any reason + # cython does automatic returning of -1 + PyErr_CheckSignals() + + if isinstance(result, tuple): + _fut.set_result(result) + else: + _fut.set_exception(result) + return 0 + # Used for deprecation and removal of `loop.create_datagram_endpoint()`'s # *reuse_address* parameter _unset = object() @@ -892,12 +905,12 @@ cdef class Loop: return poll.is_writing() - cdef Future _getaddrinfo(self, object host, object port, + cdef _getaddrinfo(self, object host, object port, int family, int type, int proto, int flags, int unpack): - cdef Future fut + cdef AddrInfoRequest fut if isinstance(port, str): port = port.encode() @@ -912,16 +925,13 @@ cdef class Loop: if not isinstance(host, bytes): raise TypeError('host must be a str or bytes') - # TODO: Use _new_future to return compiled Future objects - # in a later update (0.8.0) - fut = Future(loop=self) # TODO: Sometime before or after 0.8.0, # Let's Convert this callback to a ctypedef callback # for added performance. # example: ctypedef int (*addrinfo_callback)(Future fut, object result) except -1 def callback(_fut, result): - cdef Future fut = _fut + cdef AddrInfoFuture fut = _fut if AddrInfo.isinstance(result): try: if unpack == 0: @@ -940,27 +950,15 @@ cdef class Loop: if not fut.cancelled(): fut.set_exception(result) - AddrInfoRequest(self, host, port, family, type, proto, flags, callback, fut) - return fut - - cdef Future _getnameinfo(self, system.sockaddr *addr, int flags): - cdef NameInfoRequest nr - cdef Future fut - fut = Future(loop=self) - - # Ditto of 0.8.0's TODO list: - # Something like ctypedef int (*nameinfo_callback)(Future, object) except -1 - # would be sufficient. - def callback(fut, result): - cdef Future _fut = fut - if isinstance(result, tuple): - _fut.set_result(result) - else: - _fut.set_exception(result) + fut = AddrInfoRequest(self, host, port, family, type, proto, flags, callback) + return fut.fut - nr = NameInfoRequest(self, callback, fut) - nr.query(addr, flags) - return fut + cdef _getnameinfo(self, system.sockaddr *addr, int flags): + cdef NameInfoRequest req + req = NameInfoRequest(self) + req.set_callback(on_nameinfo_cb) + req.query(addr, flags) + return req.fut cdef _sock_recv(self, _fut, sock, n): cdef _SyncSocketReaderFuture fut = <_SyncSocketReaderFuture>_fut @@ -1559,15 +1557,17 @@ cdef class Loop: @cython.iterable_coroutine async def getaddrinfo(self, object host, object port, *, int family=0, int type=0, int proto=0, int flags=0): - + cdef AddrInfoFuture aif addr = __static_getaddrinfo_pyaddr(host, port, family, type, proto, flags) if addr is not None: return [addr] - return await self._getaddrinfo( + # recast so that we don't need a dns.pxd file + # and we still get compilation benefits. + aif = self._getaddrinfo( host, port, family, type, proto, flags, 1) - + return await aif @cython.iterable_coroutine async def getnameinfo(self, sockaddr, int flags=0): @@ -1575,6 +1575,7 @@ cdef class Loop: AddrInfo ai_cnt system.addrinfo *ai system.sockaddr_in6 *sin6 + NameInfoFuture nif if not isinstance(sockaddr, tuple): raise TypeError('getnameinfo() argument 1 must be a tuple') @@ -1622,7 +1623,8 @@ cdef class Loop: sin6.sin6_flowinfo = system.htonl(flowinfo) sin6.sin6_scope_id = scope_id - return await self._getnameinfo(ai.ai_addr, flags) + nif = self._getnameinfo(ai.ai_addr, flags) + return await nif @cython.iterable_coroutine async def start_tls(self, transport, protocol, sslcontext, *,