diff options
Diffstat (limited to 'venv/lib/python3.11/site-packages/uvloop/handles/handle.pyx')
-rw-r--r-- | venv/lib/python3.11/site-packages/uvloop/handles/handle.pyx | 395 |
1 files changed, 395 insertions, 0 deletions
diff --git a/venv/lib/python3.11/site-packages/uvloop/handles/handle.pyx b/venv/lib/python3.11/site-packages/uvloop/handles/handle.pyx new file mode 100644 index 0000000..6efe375 --- /dev/null +++ b/venv/lib/python3.11/site-packages/uvloop/handles/handle.pyx @@ -0,0 +1,395 @@ +cdef class UVHandle: + """A base class for all libuv handles. + + Automatically manages memory deallocation and closing. + + Important: + + 1. call "_ensure_alive()" before calling any libuv functions on + your handles. + + 2. call "__ensure_handle_data" in *all* libuv handle callbacks. + """ + + def __cinit__(self): + self._closed = 0 + self._inited = 0 + self._has_handle = 1 + self._handle = NULL + self._loop = None + self._source_traceback = None + + def __init__(self): + raise TypeError( + '{} is not supposed to be instantiated from Python'.format( + self.__class__.__name__)) + + def __dealloc__(self): + if UVLOOP_DEBUG: + if self._loop is not None: + if self._inited: + self._loop._debug_handles_current.subtract([ + self.__class__.__name__]) + else: + # No "@cython.no_gc_clear" decorator on this UVHandle + raise RuntimeError( + '{} without @no_gc_clear; loop was set to None by GC' + .format(self.__class__.__name__)) + + if self._handle is NULL: + return + + # -> When we're at this point, something is wrong <- + + if self._handle.loop is NULL: + # The handle wasn't initialized with "uv_{handle}_init" + self._closed = 1 + self._free() + raise RuntimeError( + '{} is open in __dealloc__ with loop set to NULL' + .format(self.__class__.__name__)) + + if self._closed: + # So _handle is not NULL and self._closed == 1? + raise RuntimeError( + '{}.__dealloc__: _handle is NULL, _closed == 1'.format( + self.__class__.__name__)) + + # The handle is dealloced while open. Let's try to close it. + # Situations when this is possible include unhandled exceptions, + # errors during Handle.__cinit__/__init__ etc. + if self._inited: + self._handle.data = NULL + uv.uv_close(self._handle, __uv_close_handle_cb) # void; no errors + self._handle = NULL + self._warn_unclosed() + else: + # The handle was allocated, but not initialized + self._closed = 1 + self._free() + + cdef _free(self): + if self._handle == NULL: + return + + if UVLOOP_DEBUG and self._inited: + self._loop._debug_uv_handles_freed += 1 + + PyMem_RawFree(self._handle) + self._handle = NULL + + cdef _warn_unclosed(self): + if self._source_traceback is not None: + try: + tb = ''.join(tb_format_list(self._source_traceback)) + tb = 'object created at (most recent call last):\n{}'.format( + tb.rstrip()) + except Exception as ex: + msg = ( + 'unclosed resource {!r}; could not serialize ' + 'debug traceback: {}: {}' + ).format(self, type(ex).__name__, ex) + else: + msg = 'unclosed resource {!r}; {}'.format(self, tb) + else: + msg = 'unclosed resource {!r}'.format(self) + warnings_warn(msg, ResourceWarning) + + cdef inline _abort_init(self): + if self._handle is not NULL: + self._free() + + try: + if UVLOOP_DEBUG: + name = self.__class__.__name__ + if self._inited: + raise RuntimeError( + '_abort_init: {}._inited is set'.format(name)) + if self._closed: + raise RuntimeError( + '_abort_init: {}._closed is set'.format(name)) + finally: + self._closed = 1 + + cdef inline _finish_init(self): + self._inited = 1 + if self._has_handle == 1: + self._handle.data = <void*>self + if self._loop._debug: + self._source_traceback = extract_stack() + if UVLOOP_DEBUG: + cls_name = self.__class__.__name__ + self._loop._debug_uv_handles_total += 1 + self._loop._debug_handles_total.update([cls_name]) + self._loop._debug_handles_current.update([cls_name]) + + cdef inline _start_init(self, Loop loop): + if UVLOOP_DEBUG: + if self._loop is not None: + raise RuntimeError( + '{}._start_init can only be called once'.format( + self.__class__.__name__)) + + self._loop = loop + + cdef inline bint _is_alive(self): + cdef bint res + res = self._closed != 1 and self._inited == 1 + if UVLOOP_DEBUG: + if res and self._has_handle == 1: + name = self.__class__.__name__ + if self._handle is NULL: + raise RuntimeError( + '{} is alive, but _handle is NULL'.format(name)) + if self._loop is None: + raise RuntimeError( + '{} is alive, but _loop is None'.format(name)) + if self._handle.loop is not self._loop.uvloop: + raise RuntimeError( + '{} is alive, but _handle.loop is not ' + 'initialized'.format(name)) + if self._handle.data is not <void*>self: + raise RuntimeError( + '{} is alive, but _handle.data is not ' + 'initialized'.format(name)) + return res + + cdef inline _ensure_alive(self): + if not self._is_alive(): + raise RuntimeError( + 'unable to perform operation on {!r}; ' + 'the handler is closed'.format(self)) + + cdef _fatal_error(self, exc, throw, reason=None): + # Fatal error means an error that was returned by the + # underlying libuv handle function. We usually can't + # recover from that, hence we just close the handle. + self._close() + + if throw or self._loop is None: + raise exc + else: + self._loop._handle_exception(exc) + + cdef _error(self, exc, throw): + # A non-fatal error is usually an error that was caught + # by the handler, but was originated in the client code + # (not in libuv). In this case we either want to simply + # raise or log it. + if throw or self._loop is None: + raise exc + else: + self._loop._handle_exception(exc) + + cdef _close(self): + if self._closed == 1: + return + + self._closed = 1 + + if self._handle is NULL: + return + + if UVLOOP_DEBUG: + if self._handle.data is NULL: + raise RuntimeError( + '{}._close: _handle.data is NULL'.format( + self.__class__.__name__)) + + if <object>self._handle.data is not self: + raise RuntimeError( + '{}._close: _handle.data is not UVHandle/self'.format( + self.__class__.__name__)) + + if uv.uv_is_closing(self._handle): + raise RuntimeError( + '{}._close: uv_is_closing() is true'.format( + self.__class__.__name__)) + + # We want the handle wrapper (UVHandle) to stay alive until + # the closing callback fires. + Py_INCREF(self) + uv.uv_close(self._handle, __uv_close_handle_cb) # void; no errors + + def __repr__(self): + return '<{} closed={} {:#x}>'.format( + self.__class__.__name__, + self._closed, + id(self)) + + +cdef class UVSocketHandle(UVHandle): + + def __cinit__(self): + self._fileobj = None + self.__cached_socket = None + + cdef _fileno(self): + cdef: + int fd + int err + + self._ensure_alive() + err = uv.uv_fileno(self._handle, <uv.uv_os_fd_t*>&fd) + if err < 0: + raise convert_error(err) + + return fd + + cdef _new_socket(self): + raise NotImplementedError + + cdef inline _get_socket(self): + if self.__cached_socket is not None: + return self.__cached_socket + + if not self._is_alive(): + return None + + self.__cached_socket = self._new_socket() + if UVLOOP_DEBUG: + # We don't "dup" for the "__cached_socket". + assert self.__cached_socket.fileno() == self._fileno() + return self.__cached_socket + + cdef inline _attach_fileobj(self, object file): + # When we create a TCP/PIPE/etc connection/server based on + # a Python file object, we need to close the file object when + # the uv handle is closed. + socket_inc_io_ref(file) + self._fileobj = file + + cdef _close(self): + if self.__cached_socket is not None: + (<PseudoSocket>self.__cached_socket)._fd = -1 + + UVHandle._close(self) + + try: + # This code will only run for transports created from + # Python sockets, i.e. with `loop.create_server(sock=sock)` etc. + if self._fileobj is not None: + if isinstance(self._fileobj, socket_socket): + # Detaching the socket object is the ideal solution: + # * libuv will actually close the FD; + # * detach() call will reset FD for the Python socket + # object, which means that it won't be closed 2nd time + # when the socket object is GCed. + # + # No need to call `socket_dec_io_ref()`, as + # `socket.detach()` ignores `socket._io_refs`. + self._fileobj.detach() + else: + try: + # `socket.close()` will raise an EBADF because libuv + # has already closed the underlying FD. + self._fileobj.close() + except OSError as ex: + if ex.errno != errno_EBADF: + raise + except Exception as ex: + self._loop.call_exception_handler({ + 'exception': ex, + 'transport': self, + 'message': f'could not close attached file object ' + f'{self._fileobj!r}', + }) + finally: + self._fileobj = None + + cdef _open(self, int sockfd): + raise NotImplementedError + + +cdef inline bint __ensure_handle_data(uv.uv_handle_t* handle, + const char* handle_ctx): + + cdef Loop loop + + if UVLOOP_DEBUG: + if handle.loop is NULL: + raise RuntimeError( + 'handle.loop is NULL in __ensure_handle_data') + + if handle.loop.data is NULL: + raise RuntimeError( + 'handle.loop.data is NULL in __ensure_handle_data') + + if handle.data is NULL: + loop = <Loop>handle.loop.data + loop.call_exception_handler({ + 'message': '{} called with handle.data == NULL'.format( + handle_ctx.decode('latin-1')) + }) + return 0 + + if handle.data is NULL: + # The underlying UVHandle object was GCed with an open uv_handle_t. + loop = <Loop>handle.loop.data + loop.call_exception_handler({ + 'message': '{} called after destroying the UVHandle'.format( + handle_ctx.decode('latin-1')) + }) + return 0 + + return 1 + + +cdef void __uv_close_handle_cb(uv.uv_handle_t* handle) noexcept with gil: + cdef UVHandle h + + if handle.data is NULL: + # The original UVHandle is long dead. Just free the mem of + # the uv_handle_t* handler. + + if UVLOOP_DEBUG: + if handle.loop == NULL or handle.loop.data == NULL: + raise RuntimeError( + '__uv_close_handle_cb: handle.loop is invalid') + (<Loop>handle.loop.data)._debug_uv_handles_freed += 1 + + PyMem_RawFree(handle) + else: + h = <UVHandle>handle.data + try: + if UVLOOP_DEBUG: + if not h._has_handle: + raise RuntimeError( + 'has_handle=0 in __uv_close_handle_cb') + h._loop._debug_handles_closed.update([ + h.__class__.__name__]) + h._free() + finally: + Py_DECREF(h) # Was INCREFed in UVHandle._close + + +cdef void __close_all_handles(Loop loop): + uv.uv_walk(loop.uvloop, + __uv_walk_close_all_handles_cb, + <void*>loop) # void + + +cdef void __uv_walk_close_all_handles_cb( + uv.uv_handle_t* handle, + void* arg, +) noexcept with gil: + + cdef: + Loop loop = <Loop>arg + UVHandle h + + if uv.uv_is_closing(handle): + # The handle is closed or is closing. + return + + if handle.data is NULL: + # This shouldn't happen. Ever. + loop.call_exception_handler({ + 'message': 'handle.data is NULL in __close_all_handles_cb' + }) + return + + h = <UVHandle>handle.data + if not h._closed: + h._warn_unclosed() + h._close() |