diff --git a/winloop/dns.pyx b/winloop/dns.pyx index e6afb25..1835868 100644 --- a/winloop/dns.pyx +++ b/winloop/dns.pyx @@ -334,12 +334,18 @@ 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 + 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, @@ -379,6 +385,7 @@ cdef class AddrInfoRequest(UVRequest): self.request = &self._req_data self.callback = callback + self.fut = AddrInfoFuture(loop, self) self.request.data = self err = uv.uv_getaddrinfo(loop.uvloop, @@ -406,21 +413,34 @@ cdef class AddrInfoRequest(UVRequest): else: ex = convert_error(err) except Exception as ex: - callback(ex) + callback(self.fut, ex) else: - callback(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: - object callback + # TODO: ctypedef callbacks. + nameinfo_callback callback + NameInfoFuture fut uv.uv_getnameinfo_t _req_data - def __cinit__(self, Loop loop, callback): + def __cinit__(self, Loop loop): self.request = &self._req_data - self.callback = callback + 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, @@ -430,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): @@ -455,15 +516,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 +544,14 @@ cdef void __on_nameinfo_resolved( cdef: NameInfoRequest request = req.data Loop loop = request.loop - object callback = request.callback + object fut = request.fut + nameinfo_callback 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.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 ffaaa01..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,15 +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): + def callback(_fut, result): + cdef AddrInfoFuture fut = _fut if AddrInfo.isinstance(result): try: if unpack == 0: @@ -939,28 +950,15 @@ 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)) - 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): - 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, ft_partial(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, *,