1cdef class UVHandle:
2    """A base class for all libuv handles.
3
4    Automatically manages memory deallocation and closing.
5
6    Important:
7
8       1. call "_ensure_alive()" before calling any libuv functions on
9          your handles.
10
11       2. call "__ensure_handle_data" in *all* libuv handle callbacks.
12    """
13
14    def __cinit__(self):
15        self._closed = 0
16        self._inited = 0
17        self._has_handle = 1
18        self._handle = NULL
19        self._loop = None
20        self._source_traceback = None
21
22    def __init__(self):
23        raise TypeError(
24            '{} is not supposed to be instantiated from Python'.format(
25                self.__class__.__name__))
26
27    def __dealloc__(self):
28        if UVLOOP_DEBUG:
29            if self._loop is not None:
30                if self._inited:
31                    self._loop._debug_handles_current.subtract([
32                        self.__class__.__name__])
33            else:
34                # No "@cython.no_gc_clear" decorator on this UVHandle
35                raise RuntimeError(
36                    '{} without @no_gc_clear; loop was set to None by GC'
37                    .format(self.__class__.__name__))
38
39        if self._handle is NULL:
40            return
41
42        # -> When we're at this point, something is wrong <-
43
44        if self._handle.loop is NULL:
45            # The handle wasn't initialized with "uv_{handle}_init"
46            self._closed = 1
47            self._free()
48            raise RuntimeError(
49                '{} is open in __dealloc__ with loop set to NULL'
50                .format(self.__class__.__name__))
51
52        if self._closed:
53            # So _handle is not NULL and self._closed == 1?
54            raise RuntimeError(
55                '{}.__dealloc__: _handle is NULL, _closed == 1'.format(
56                    self.__class__.__name__))
57
58        # The handle is dealloced while open.  Let's try to close it.
59        # Situations when this is possible include unhandled exceptions,
60        # errors during Handle.__cinit__/__init__ etc.
61        if self._inited:
62            self._handle.data = NULL
63            uv.uv_close(self._handle, __uv_close_handle_cb)  # void; no errors
64            self._handle = NULL
65            self._warn_unclosed()
66        else:
67            # The handle was allocated, but not initialized
68            self._closed = 1
69            self._free()
70
71    cdef _free(self):
72        if self._handle == NULL:
73            return
74
75        if UVLOOP_DEBUG and self._inited:
76            self._loop._debug_uv_handles_freed += 1
77
78        PyMem_RawFree(self._handle)
79        self._handle = NULL
80
81    cdef _warn_unclosed(self):
82        if self._source_traceback is not None:
83            try:
84                tb = ''.join(tb_format_list(self._source_traceback))
85                tb = 'object created at (most recent call last):\n{}'.format(
86                    tb.rstrip())
87            except Exception as ex:
88                msg = (
89                    'unclosed resource {!r}; could not serialize '
90                    'debug traceback: {}: {}'
91                ).format(self, type(ex).__name__, ex)
92            else:
93                msg = 'unclosed resource {!r}; {}'.format(self, tb)
94        else:
95            msg = 'unclosed resource {!r}'.format(self)
96        warnings_warn(msg, ResourceWarning)
97
98    cdef inline _abort_init(self):
99        if self._handle is not NULL:
100            self._free()
101
102        try:
103            if UVLOOP_DEBUG:
104                name = self.__class__.__name__
105                if self._inited:
106                    raise RuntimeError(
107                        '_abort_init: {}._inited is set'.format(name))
108                if self._closed:
109                    raise RuntimeError(
110                        '_abort_init: {}._closed is set'.format(name))
111        finally:
112            self._closed = 1
113
114    cdef inline _finish_init(self):
115        self._inited = 1
116        if self._has_handle == 1:
117            self._handle.data = <void*>self
118        if self._loop._debug:
119            self._source_traceback = extract_stack()
120        if UVLOOP_DEBUG:
121            cls_name = self.__class__.__name__
122            self._loop._debug_uv_handles_total += 1
123            self._loop._debug_handles_total.update([cls_name])
124            self._loop._debug_handles_current.update([cls_name])
125
126    cdef inline _start_init(self, Loop loop):
127        if UVLOOP_DEBUG:
128            if self._loop is not None:
129                raise RuntimeError(
130                    '{}._start_init can only be called once'.format(
131                        self.__class__.__name__))
132
133        self._loop = loop
134
135    cdef inline bint _is_alive(self):
136        cdef bint res
137        res = self._closed != 1 and self._inited == 1
138        if UVLOOP_DEBUG:
139            if res and self._has_handle == 1:
140                name = self.__class__.__name__
141                if self._handle is NULL:
142                    raise RuntimeError(
143                        '{} is alive, but _handle is NULL'.format(name))
144                if self._loop is None:
145                    raise RuntimeError(
146                        '{} is alive, but _loop is None'.format(name))
147                if self._handle.loop is not self._loop.uvloop:
148                    raise RuntimeError(
149                        '{} is alive, but _handle.loop is not '
150                        'initialized'.format(name))
151                if self._handle.data is not <void*>self:
152                    raise RuntimeError(
153                        '{} is alive, but _handle.data is not '
154                        'initialized'.format(name))
155        return res
156
157    cdef inline _ensure_alive(self):
158        if not self._is_alive():
159            raise RuntimeError(
160                'unable to perform operation on {!r}; '
161                'the handler is closed'.format(self))
162
163    cdef _fatal_error(self, exc, throw, reason=None):
164        # Fatal error means an error that was returned by the
165        # underlying libuv handle function.  We usually can't
166        # recover from that, hence we just close the handle.
167        self._close()
168
169        if throw or self._loop is None:
170            raise exc
171        else:
172            self._loop._handle_exception(exc)
173
174    cdef _error(self, exc, throw):
175        # A non-fatal error is usually an error that was caught
176        # by the handler, but was originated in the client code
177        # (not in libuv).  In this case we either want to simply
178        # raise or log it.
179        if throw or self._loop is None:
180            raise exc
181        else:
182            self._loop._handle_exception(exc)
183
184    cdef _close(self):
185        if self._closed == 1:
186            return
187
188        self._closed = 1
189
190        if self._handle is NULL:
191            return
192
193        if UVLOOP_DEBUG:
194            if self._handle.data is NULL:
195                raise RuntimeError(
196                    '{}._close: _handle.data is NULL'.format(
197                        self.__class__.__name__))
198
199            if <object>self._handle.data is not self:
200                raise RuntimeError(
201                    '{}._close: _handle.data is not UVHandle/self'.format(
202                        self.__class__.__name__))
203
204            if uv.uv_is_closing(self._handle):
205                raise RuntimeError(
206                    '{}._close: uv_is_closing() is true'.format(
207                        self.__class__.__name__))
208
209        # We want the handle wrapper (UVHandle) to stay alive until
210        # the closing callback fires.
211        Py_INCREF(self)
212        uv.uv_close(self._handle, __uv_close_handle_cb)  # void; no errors
213
214    def __repr__(self):
215        return '<{} closed={} {:#x}>'.format(
216            self.__class__.__name__,
217            self._closed,
218            id(self))
219
220
221cdef class UVSocketHandle(UVHandle):
222
223    def __cinit__(self):
224        self._fileobj = None
225        self.__cached_socket = None
226
227    cdef _fileno(self):
228        cdef:
229            int fd
230            int err
231
232        self._ensure_alive()
233        err = uv.uv_fileno(self._handle, <uv.uv_os_fd_t*>&fd)
234        if err < 0:
235            raise convert_error(err)
236
237        return fd
238
239    cdef _new_socket(self):
240        raise NotImplementedError
241
242    cdef inline _get_socket(self):
243        if self.__cached_socket is not None:
244            return self.__cached_socket
245
246        if not self._is_alive():
247            return None
248
249        self.__cached_socket = self._new_socket()
250        if UVLOOP_DEBUG:
251            # We don't "dup" for the "__cached_socket".
252            assert self.__cached_socket.fileno() == self._fileno()
253        return self.__cached_socket
254
255    cdef inline _attach_fileobj(self, object file):
256        # When we create a TCP/PIPE/etc connection/server based on
257        # a Python file object, we need to close the file object when
258        # the uv handle is closed.
259        socket_inc_io_ref(file)
260        self._fileobj = file
261
262    cdef _close(self):
263        if self.__cached_socket is not None:
264            (<PseudoSocket>self.__cached_socket)._fd = -1
265
266        UVHandle._close(self)
267
268        try:
269            # This code will only run for transports created from
270            # Python sockets, i.e. with `loop.create_server(sock=sock)` etc.
271            if self._fileobj is not None:
272                if isinstance(self._fileobj, socket_socket):
273                    # Detaching the socket object is the ideal solution:
274                    # * libuv will actually close the FD;
275                    # * detach() call will reset FD for the Python socket
276                    #   object, which means that it won't be closed 2nd time
277                    #   when the socket object is GCed.
278                    #
279                    # No need to call `socket_dec_io_ref()`, as
280                    # `socket.detach()` ignores `socket._io_refs`.
281                    self._fileobj.detach()
282                else:
283                    try:
284                        # `socket.close()` will raise an EBADF because libuv
285                        # has already closed the underlying FD.
286                        self._fileobj.close()
287                    except OSError as ex:
288                        if ex.errno != errno_EBADF:
289                            raise
290        except Exception as ex:
291            self._loop.call_exception_handler({
292                'exception': ex,
293                'transport': self,
294                'message': f'could not close attached file object '
295                           f'{self._fileobj!r}',
296            })
297        finally:
298            self._fileobj = None
299
300    cdef _open(self, int sockfd):
301        raise NotImplementedError
302
303
304cdef inline bint __ensure_handle_data(uv.uv_handle_t* handle,
305                                      const char* handle_ctx):
306
307    cdef Loop loop
308
309    if UVLOOP_DEBUG:
310        if handle.loop is NULL:
311            raise RuntimeError(
312                'handle.loop is NULL in __ensure_handle_data')
313
314        if handle.loop.data is NULL:
315            raise RuntimeError(
316                'handle.loop.data is NULL in __ensure_handle_data')
317
318    if handle.data is NULL:
319        loop = <Loop>handle.loop.data
320        loop.call_exception_handler({
321            'message': '{} called with handle.data == NULL'.format(
322                handle_ctx.decode('latin-1'))
323        })
324        return 0
325
326    if handle.data is NULL:
327        # The underlying UVHandle object was GCed with an open uv_handle_t.
328        loop = <Loop>handle.loop.data
329        loop.call_exception_handler({
330            'message': '{} called after destroying the UVHandle'.format(
331                handle_ctx.decode('latin-1'))
332        })
333        return 0
334
335    return 1
336
337
338cdef void __uv_close_handle_cb(uv.uv_handle_t* handle) with gil:
339    cdef UVHandle h
340
341    if handle.data is NULL:
342        # The original UVHandle is long dead. Just free the mem of
343        # the uv_handle_t* handler.
344
345        if UVLOOP_DEBUG:
346            if handle.loop == NULL or handle.loop.data == NULL:
347                raise RuntimeError(
348                    '__uv_close_handle_cb: handle.loop is invalid')
349            (<Loop>handle.loop.data)._debug_uv_handles_freed += 1
350
351        PyMem_RawFree(handle)
352    else:
353        h = <UVHandle>handle.data
354        try:
355            if UVLOOP_DEBUG:
356                if not h._has_handle:
357                    raise RuntimeError(
358                        'has_handle=0 in __uv_close_handle_cb')
359                h._loop._debug_handles_closed.update([
360                    h.__class__.__name__])
361            h._free()
362        finally:
363            Py_DECREF(h)  # Was INCREFed in UVHandle._close
364
365
366cdef void __close_all_handles(Loop loop):
367    uv.uv_walk(loop.uvloop,
368               __uv_walk_close_all_handles_cb,
369               <void*>loop)  # void
370
371
372cdef void __uv_walk_close_all_handles_cb(
373        uv.uv_handle_t* handle, void* arg) with gil:
374
375    cdef:
376        Loop loop = <Loop>arg
377        UVHandle h
378
379    if uv.uv_is_closing(handle):
380        # The handle is closed or is closing.
381        return
382
383    if handle.data is NULL:
384        # This shouldn't happen. Ever.
385        loop.call_exception_handler({
386            'message': 'handle.data is NULL in __close_all_handles_cb'
387        })
388        return
389
390    h = <UVHandle>handle.data
391    if not h._closed:
392        h._warn_unclosed()
393        h._close()
394