second commit
This commit is contained in:
395
env/lib/python3.11/site-packages/uvloop/handles/handle.pyx
vendored
Normal file
395
env/lib/python3.11/site-packages/uvloop/handles/handle.pyx
vendored
Normal file
@ -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) noexcept:
|
||||
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()
|
Reference in New Issue
Block a user