You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
998 lines
31 KiB
998 lines
31 KiB
DEF __PREALLOCED_BUFS = 4 |
|
|
|
|
|
@cython.no_gc_clear |
|
@cython.freelist(DEFAULT_FREELIST_SIZE) |
|
cdef class _StreamWriteContext: |
|
# used to hold additional write request information for uv_write |
|
|
|
cdef: |
|
uv.uv_write_t req |
|
|
|
list buffers |
|
|
|
uv.uv_buf_t uv_bufs_sml[__PREALLOCED_BUFS] |
|
Py_buffer py_bufs_sml[__PREALLOCED_BUFS] |
|
bint py_bufs_sml_inuse |
|
|
|
uv.uv_buf_t* uv_bufs |
|
Py_buffer* py_bufs |
|
size_t py_bufs_len |
|
|
|
uv.uv_buf_t* uv_bufs_start |
|
size_t uv_bufs_len |
|
|
|
UVStream stream |
|
|
|
bint closed |
|
|
|
cdef free_bufs(self): |
|
cdef size_t i |
|
|
|
if self.uv_bufs is not NULL: |
|
PyMem_RawFree(self.uv_bufs) |
|
self.uv_bufs = NULL |
|
if UVLOOP_DEBUG: |
|
if self.py_bufs_sml_inuse: |
|
raise RuntimeError( |
|
'_StreamWriteContext.close: uv_bufs != NULL and ' |
|
'py_bufs_sml_inuse is True') |
|
|
|
if self.py_bufs is not NULL: |
|
for i from 0 <= i < self.py_bufs_len: |
|
PyBuffer_Release(&self.py_bufs[i]) |
|
PyMem_RawFree(self.py_bufs) |
|
self.py_bufs = NULL |
|
if UVLOOP_DEBUG: |
|
if self.py_bufs_sml_inuse: |
|
raise RuntimeError( |
|
'_StreamWriteContext.close: py_bufs != NULL and ' |
|
'py_bufs_sml_inuse is True') |
|
|
|
if self.py_bufs_sml_inuse: |
|
for i from 0 <= i < self.py_bufs_len: |
|
PyBuffer_Release(&self.py_bufs_sml[i]) |
|
self.py_bufs_sml_inuse = 0 |
|
|
|
self.py_bufs_len = 0 |
|
self.buffers = None |
|
|
|
cdef close(self): |
|
if self.closed: |
|
return |
|
self.closed = 1 |
|
self.free_bufs() |
|
Py_DECREF(self) |
|
|
|
cdef advance_uv_buf(self, size_t sent): |
|
# Advance the pointer to first uv_buf and the |
|
# pointer to first byte in that buffer. |
|
# |
|
# We do this after a "uv_try_write" call, which |
|
# sometimes sends only a portion of data. |
|
# We then call "advance_uv_buf" on the write |
|
# context, and reuse it in a "uv_write" call. |
|
|
|
cdef: |
|
uv.uv_buf_t* buf |
|
size_t idx |
|
|
|
for idx from 0 <= idx < self.uv_bufs_len: |
|
buf = &self.uv_bufs_start[idx] |
|
if buf.len > sent: |
|
buf.len -= sent |
|
buf.base = buf.base + sent |
|
self.uv_bufs_start = buf |
|
self.uv_bufs_len -= idx |
|
return |
|
else: |
|
sent -= self.uv_bufs_start[idx].len |
|
|
|
if UVLOOP_DEBUG: |
|
if sent < 0: |
|
raise RuntimeError('fatal: sent < 0 in advance_uv_buf') |
|
|
|
raise RuntimeError('fatal: Could not advance _StreamWriteContext') |
|
|
|
@staticmethod |
|
cdef _StreamWriteContext new(UVStream stream, list buffers): |
|
cdef: |
|
_StreamWriteContext ctx |
|
int uv_bufs_idx = 0 |
|
size_t py_bufs_len = 0 |
|
int i |
|
|
|
Py_buffer* p_pybufs |
|
uv.uv_buf_t* p_uvbufs |
|
|
|
ctx = _StreamWriteContext.__new__(_StreamWriteContext) |
|
ctx.stream = None |
|
ctx.closed = 1 |
|
ctx.py_bufs_len = 0 |
|
ctx.py_bufs_sml_inuse = 0 |
|
ctx.uv_bufs = NULL |
|
ctx.py_bufs = NULL |
|
ctx.buffers = buffers |
|
ctx.stream = stream |
|
|
|
if len(buffers) <= __PREALLOCED_BUFS: |
|
# We've got a small number of buffers to write, don't |
|
# need to use malloc. |
|
ctx.py_bufs_sml_inuse = 1 |
|
p_pybufs = <Py_buffer*>&ctx.py_bufs_sml |
|
p_uvbufs = <uv.uv_buf_t*>&ctx.uv_bufs_sml |
|
|
|
else: |
|
for buf in buffers: |
|
if UVLOOP_DEBUG: |
|
if not isinstance(buf, (bytes, bytearray, memoryview)): |
|
raise RuntimeError( |
|
'invalid data in writebuf: an instance of ' |
|
'bytes, bytearray or memoryview was expected, ' |
|
'got {}'.format(type(buf))) |
|
|
|
if not PyBytes_CheckExact(buf): |
|
py_bufs_len += 1 |
|
|
|
if py_bufs_len > 0: |
|
ctx.py_bufs = <Py_buffer*>PyMem_RawMalloc( |
|
py_bufs_len * sizeof(Py_buffer)) |
|
if ctx.py_bufs is NULL: |
|
raise MemoryError() |
|
|
|
ctx.uv_bufs = <uv.uv_buf_t*>PyMem_RawMalloc( |
|
len(buffers) * sizeof(uv.uv_buf_t)) |
|
if ctx.uv_bufs is NULL: |
|
raise MemoryError() |
|
|
|
p_pybufs = ctx.py_bufs |
|
p_uvbufs = ctx.uv_bufs |
|
|
|
py_bufs_len = 0 |
|
for buf in buffers: |
|
if PyBytes_CheckExact(buf): |
|
# We can only use this hack for bytes since it's |
|
# immutable. For everything else it is only safe to |
|
# use buffer protocol. |
|
p_uvbufs[uv_bufs_idx].base = PyBytes_AS_STRING(buf) |
|
p_uvbufs[uv_bufs_idx].len = Py_SIZE(buf) |
|
|
|
else: |
|
try: |
|
PyObject_GetBuffer( |
|
buf, &p_pybufs[py_bufs_len], PyBUF_SIMPLE) |
|
except Exception: |
|
# This shouldn't ever happen, as `UVStream._buffer_write` |
|
# casts non-bytes objects to `memoryviews`. |
|
ctx.py_bufs_len = py_bufs_len |
|
ctx.free_bufs() |
|
raise |
|
|
|
p_uvbufs[uv_bufs_idx].base = <char*>p_pybufs[py_bufs_len].buf |
|
p_uvbufs[uv_bufs_idx].len = p_pybufs[py_bufs_len].len |
|
|
|
py_bufs_len += 1 |
|
|
|
uv_bufs_idx += 1 |
|
|
|
ctx.uv_bufs_start = p_uvbufs |
|
ctx.uv_bufs_len = uv_bufs_idx |
|
|
|
ctx.py_bufs_len = py_bufs_len |
|
ctx.req.data = <void*> ctx |
|
|
|
if UVLOOP_DEBUG: |
|
stream._loop._debug_stream_write_ctx_total += 1 |
|
stream._loop._debug_stream_write_ctx_cnt += 1 |
|
|
|
# Do incref after everything else is done. |
|
# Under no circumstances we want `ctx` to be GCed while |
|
# libuv is still working with `ctx.uv_bufs`. |
|
Py_INCREF(ctx) |
|
ctx.closed = 0 |
|
return ctx |
|
|
|
def __dealloc__(self): |
|
if not self.closed: |
|
# Because we do an INCREF in _StreamWriteContext.new, |
|
# __dealloc__ shouldn't ever happen with `self.closed == 1` |
|
raise RuntimeError( |
|
'open _StreamWriteContext is being deallocated') |
|
|
|
if UVLOOP_DEBUG: |
|
if self.stream is not None: |
|
self.stream._loop._debug_stream_write_ctx_cnt -= 1 |
|
self.stream = None |
|
|
|
|
|
@cython.no_gc_clear |
|
cdef class UVStream(UVBaseTransport): |
|
|
|
def __cinit__(self): |
|
self.__shutting_down = 0 |
|
self.__reading = 0 |
|
self.__read_error_close = 0 |
|
self.__buffered = 0 |
|
self._eof = 0 |
|
self._buffer = [] |
|
self._buffer_size = 0 |
|
|
|
self._protocol_get_buffer = None |
|
self._protocol_buffer_updated = None |
|
|
|
self._read_pybuf_acquired = False |
|
|
|
cdef _set_protocol(self, object protocol): |
|
if protocol is None: |
|
raise TypeError('protocol is required') |
|
|
|
UVBaseTransport._set_protocol(self, protocol) |
|
|
|
if (hasattr(protocol, 'get_buffer') and |
|
not isinstance(protocol, aio_Protocol)): |
|
try: |
|
self._protocol_get_buffer = protocol.get_buffer |
|
self._protocol_buffer_updated = protocol.buffer_updated |
|
self.__buffered = 1 |
|
except AttributeError: |
|
pass |
|
else: |
|
self.__buffered = 0 |
|
|
|
cdef _clear_protocol(self): |
|
UVBaseTransport._clear_protocol(self) |
|
self._protocol_get_buffer = None |
|
self._protocol_buffer_updated = None |
|
self.__buffered = 0 |
|
|
|
cdef inline _shutdown(self): |
|
cdef int err |
|
|
|
if self.__shutting_down: |
|
return |
|
self.__shutting_down = 1 |
|
|
|
self._ensure_alive() |
|
|
|
self._shutdown_req.data = <void*> self |
|
err = uv.uv_shutdown(&self._shutdown_req, |
|
<uv.uv_stream_t*> self._handle, |
|
__uv_stream_on_shutdown) |
|
if err < 0: |
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
return |
|
|
|
cdef inline _accept(self, UVStream server): |
|
cdef int err |
|
self._ensure_alive() |
|
|
|
err = uv.uv_accept(<uv.uv_stream_t*>server._handle, |
|
<uv.uv_stream_t*>self._handle) |
|
if err < 0: |
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
return |
|
|
|
self._on_accept() |
|
|
|
cdef inline _close_on_read_error(self): |
|
self.__read_error_close = 1 |
|
|
|
cdef bint _is_reading(self): |
|
return self.__reading |
|
|
|
cdef _start_reading(self): |
|
cdef int err |
|
|
|
if self._closing: |
|
return |
|
|
|
self._ensure_alive() |
|
|
|
if self.__reading: |
|
return |
|
|
|
if self.__buffered: |
|
err = uv.uv_read_start(<uv.uv_stream_t*>self._handle, |
|
__uv_stream_buffered_alloc, |
|
__uv_stream_buffered_on_read) |
|
else: |
|
err = uv.uv_read_start(<uv.uv_stream_t*>self._handle, |
|
__loop_alloc_buffer, |
|
__uv_stream_on_read) |
|
if err < 0: |
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
return |
|
else: |
|
# UVStream must live until the read callback is called |
|
self.__reading_started() |
|
|
|
cdef inline __reading_started(self): |
|
if self.__reading: |
|
return |
|
self.__reading = 1 |
|
Py_INCREF(self) |
|
|
|
cdef inline __reading_stopped(self): |
|
if not self.__reading: |
|
return |
|
self.__reading = 0 |
|
Py_DECREF(self) |
|
|
|
cdef _stop_reading(self): |
|
cdef int err |
|
|
|
if not self.__reading: |
|
return |
|
|
|
self._ensure_alive() |
|
|
|
# From libuv docs: |
|
# This function is idempotent and may be safely |
|
# called on a stopped stream. |
|
err = uv.uv_read_stop(<uv.uv_stream_t*>self._handle) |
|
if err < 0: |
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
return |
|
else: |
|
self.__reading_stopped() |
|
|
|
cdef inline _try_write(self, object data): |
|
cdef: |
|
ssize_t written |
|
bint used_buf = 0 |
|
Py_buffer py_buf |
|
void* buf |
|
size_t blen |
|
int saved_errno |
|
int fd |
|
|
|
if (<uv.uv_stream_t*>self._handle).write_queue_size != 0: |
|
raise RuntimeError( |
|
'UVStream._try_write called with data in uv buffers') |
|
|
|
if PyBytes_CheckExact(data): |
|
# We can only use this hack for bytes since it's |
|
# immutable. For everything else it is only safe to |
|
# use buffer protocol. |
|
buf = <void*>PyBytes_AS_STRING(data) |
|
blen = Py_SIZE(data) |
|
else: |
|
PyObject_GetBuffer(data, &py_buf, PyBUF_SIMPLE) |
|
used_buf = 1 |
|
buf = py_buf.buf |
|
blen = py_buf.len |
|
|
|
if blen == 0: |
|
# Empty data, do nothing. |
|
return 0 |
|
|
|
fd = self._fileno() |
|
# Use `unistd.h/write` directly, it's faster than |
|
# uv_try_write -- less layers of code. The error |
|
# checking logic is copied from libuv. |
|
written = system.write(fd, buf, blen) |
|
while written == -1 and ( |
|
errno.errno == errno.EINTR or |
|
(system.PLATFORM_IS_APPLE and |
|
errno.errno == errno.EPROTOTYPE)): |
|
# From libuv code (unix/stream.c): |
|
# Due to a possible kernel bug at least in OS X 10.10 "Yosemite", |
|
# EPROTOTYPE can be returned while trying to write to a socket |
|
# that is shutting down. If we retry the write, we should get |
|
# the expected EPIPE instead. |
|
written = system.write(fd, buf, blen) |
|
saved_errno = errno.errno |
|
|
|
if used_buf: |
|
PyBuffer_Release(&py_buf) |
|
|
|
if written < 0: |
|
if saved_errno == errno.EAGAIN or \ |
|
saved_errno == system.EWOULDBLOCK: |
|
return -1 |
|
else: |
|
exc = convert_error(-saved_errno) |
|
self._fatal_error(exc, True) |
|
return |
|
|
|
if UVLOOP_DEBUG: |
|
self._loop._debug_stream_write_tries += 1 |
|
|
|
if <size_t>written == blen: |
|
return 0 |
|
|
|
return written |
|
|
|
cdef inline _buffer_write(self, object data): |
|
cdef int dlen |
|
|
|
if not PyBytes_CheckExact(data): |
|
data = memoryview(data).cast('b') |
|
|
|
dlen = len(data) |
|
if not dlen: |
|
return |
|
|
|
self._buffer_size += dlen |
|
self._buffer.append(data) |
|
|
|
cdef inline _initiate_write(self): |
|
if (not self._protocol_paused and |
|
(<uv.uv_stream_t*>self._handle).write_queue_size == 0 and |
|
self._buffer_size > self._high_water): |
|
# Fast-path. If: |
|
# - the protocol isn't yet paused, |
|
# - there is no data in libuv buffers for this stream, |
|
# - the protocol will be paused if we continue to buffer data |
|
# |
|
# Then: |
|
# - Try to write all buffered data right now. |
|
all_sent = self._exec_write() |
|
if UVLOOP_DEBUG: |
|
if self._buffer_size != 0 or self._buffer != []: |
|
raise RuntimeError( |
|
'_buffer_size is not 0 after a successful _exec_write') |
|
|
|
# There is no need to call `_queue_write` anymore, |
|
# as `uv_write` should be called already. |
|
|
|
if not all_sent: |
|
# If not all of the data was sent successfully, |
|
# we might need to pause the protocol. |
|
self._maybe_pause_protocol() |
|
|
|
elif self._buffer_size > 0: |
|
self._maybe_pause_protocol() |
|
self._loop._queue_write(self) |
|
|
|
cdef inline _exec_write(self): |
|
cdef: |
|
int err |
|
int buf_len |
|
_StreamWriteContext ctx = None |
|
|
|
if self._closed: |
|
# If the handle is closed, just return, it's too |
|
# late to do anything. |
|
return |
|
|
|
buf_len = len(self._buffer) |
|
if not buf_len: |
|
return |
|
|
|
if (<uv.uv_stream_t*>self._handle).write_queue_size == 0: |
|
# libuv internal write buffers for this stream are empty. |
|
if buf_len == 1: |
|
# If we only have one piece of data to send, let's |
|
# use our fast implementation of try_write. |
|
data = self._buffer[0] |
|
sent = self._try_write(data) |
|
|
|
if sent is None: |
|
# A `self._fatal_error` was called. |
|
# It might not raise an exception under some |
|
# conditions. |
|
self._buffer_size = 0 |
|
self._buffer.clear() |
|
if not self._closing: |
|
# This should never happen. |
|
raise RuntimeError( |
|
'stream is open after UVStream._try_write ' |
|
'returned None') |
|
return |
|
|
|
if sent == 0: |
|
# All data was successfully written. |
|
self._buffer_size = 0 |
|
self._buffer.clear() |
|
# on_write will call "maybe_resume_protocol". |
|
self._on_write() |
|
return True |
|
|
|
if sent > 0: |
|
if UVLOOP_DEBUG: |
|
if sent == len(data): |
|
raise RuntimeError( |
|
'_try_write sent all data and returned ' |
|
'non-zero') |
|
|
|
if PyBytes_CheckExact(data): |
|
# Cast bytes to memoryview to avoid copying |
|
# data that wasn't sent. |
|
data = memoryview(data) |
|
data = data[sent:] |
|
|
|
self._buffer_size -= sent |
|
self._buffer[0] = data |
|
|
|
# At this point it's either data was sent partially, |
|
# or an EAGAIN has happened. |
|
|
|
else: |
|
ctx = _StreamWriteContext.new(self, self._buffer) |
|
|
|
err = uv.uv_try_write(<uv.uv_stream_t*>self._handle, |
|
ctx.uv_bufs_start, |
|
ctx.uv_bufs_len) |
|
|
|
if err > 0: |
|
# Some data was successfully sent. |
|
|
|
if <size_t>err == self._buffer_size: |
|
# Everything was sent. |
|
ctx.close() |
|
self._buffer.clear() |
|
self._buffer_size = 0 |
|
# on_write will call "maybe_resume_protocol". |
|
self._on_write() |
|
return True |
|
|
|
try: |
|
# Advance pointers to uv_bufs in `ctx`, |
|
# we will reuse it soon for a uv_write |
|
# call. |
|
ctx.advance_uv_buf(<ssize_t>err) |
|
except Exception as ex: # This should never happen. |
|
# Let's try to close the `ctx` anyways. |
|
ctx.close() |
|
self._fatal_error(ex, True) |
|
self._buffer.clear() |
|
self._buffer_size = 0 |
|
return |
|
|
|
elif err != uv.UV_EAGAIN: |
|
ctx.close() |
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
self._buffer.clear() |
|
self._buffer_size = 0 |
|
return |
|
|
|
# fall through |
|
|
|
if ctx is None: |
|
ctx = _StreamWriteContext.new(self, self._buffer) |
|
|
|
err = uv.uv_write(&ctx.req, |
|
<uv.uv_stream_t*>self._handle, |
|
ctx.uv_bufs_start, |
|
ctx.uv_bufs_len, |
|
__uv_stream_on_write) |
|
|
|
self._buffer_size = 0 |
|
# Can't use `_buffer.clear()` here: `ctx` holds a reference to |
|
# the `_buffer`. |
|
self._buffer = [] |
|
|
|
if err < 0: |
|
# close write context |
|
ctx.close() |
|
|
|
exc = convert_error(err) |
|
self._fatal_error(exc, True) |
|
return |
|
|
|
self._maybe_resume_protocol() |
|
|
|
cdef size_t _get_write_buffer_size(self): |
|
if self._handle is NULL: |
|
return 0 |
|
return ((<uv.uv_stream_t*>self._handle).write_queue_size + |
|
self._buffer_size) |
|
|
|
cdef _close(self): |
|
try: |
|
if self._read_pybuf_acquired: |
|
# Should never happen. libuv always calls uv_alloc/uv_read |
|
# in pairs. |
|
self._loop.call_exception_handler({ |
|
'transport': self, |
|
'message': 'XXX: an allocated buffer in transport._close()' |
|
}) |
|
self._read_pybuf_acquired = 0 |
|
PyBuffer_Release(&self._read_pybuf) |
|
|
|
self._stop_reading() |
|
finally: |
|
UVSocketHandle._close(<UVHandle>self) |
|
|
|
cdef inline _on_accept(self): |
|
# Ultimately called by __uv_stream_on_listen. |
|
self._init_protocol() |
|
|
|
cdef inline _on_eof(self): |
|
# Any exception raised here will be caught in |
|
# __uv_stream_on_read. |
|
|
|
try: |
|
meth = self._protocol.eof_received |
|
except AttributeError: |
|
keep_open = False |
|
else: |
|
keep_open = run_in_context(self.context, meth) |
|
|
|
if keep_open: |
|
# We're keeping the connection open so the |
|
# protocol can write more, but we still can't |
|
# receive more, so remove the reader callback. |
|
self._stop_reading() |
|
else: |
|
self.close() |
|
|
|
cdef inline _on_write(self): |
|
self._maybe_resume_protocol() |
|
if not self._get_write_buffer_size(): |
|
if self._closing: |
|
self._schedule_call_connection_lost(None) |
|
elif self._eof: |
|
self._shutdown() |
|
|
|
cdef inline _init(self, Loop loop, object protocol, Server server, |
|
object waiter, object context): |
|
self.context = context |
|
self._set_protocol(protocol) |
|
self._start_init(loop) |
|
|
|
if server is not None: |
|
self._set_server(server) |
|
|
|
if waiter is not None: |
|
self._set_waiter(waiter) |
|
|
|
cdef inline _on_connect(self, object exc): |
|
# Called from __tcp_connect_callback (tcp.pyx) and |
|
# __pipe_connect_callback (pipe.pyx). |
|
if exc is None: |
|
self._init_protocol() |
|
else: |
|
if self._waiter is None: |
|
self._fatal_error(exc, False, "connect failed") |
|
elif self._waiter.cancelled(): |
|
# Connect call was cancelled; just close the transport |
|
# silently. |
|
self._close() |
|
elif self._waiter.done(): |
|
self._fatal_error(exc, False, "connect failed") |
|
else: |
|
self._waiter.set_exception(exc) |
|
self._close() |
|
|
|
# === Public API === |
|
|
|
def __repr__(self): |
|
return '<{} closed={} reading={} {:#x}>'.format( |
|
self.__class__.__name__, |
|
self._closed, |
|
self.__reading, |
|
id(self)) |
|
|
|
def write(self, object buf): |
|
self._ensure_alive() |
|
|
|
if self._eof: |
|
raise RuntimeError('Cannot call write() after write_eof()') |
|
if not buf: |
|
return |
|
if self._conn_lost: |
|
self._conn_lost += 1 |
|
return |
|
self._buffer_write(buf) |
|
self._initiate_write() |
|
|
|
def writelines(self, bufs): |
|
self._ensure_alive() |
|
|
|
if self._eof: |
|
raise RuntimeError('Cannot call writelines() after write_eof()') |
|
if self._conn_lost: |
|
self._conn_lost += 1 |
|
return |
|
for buf in bufs: |
|
self._buffer_write(buf) |
|
self._initiate_write() |
|
|
|
def write_eof(self): |
|
self._ensure_alive() |
|
|
|
if self._eof: |
|
return |
|
|
|
self._eof = 1 |
|
if not self._get_write_buffer_size(): |
|
self._shutdown() |
|
|
|
def can_write_eof(self): |
|
return True |
|
|
|
def is_reading(self): |
|
return self._is_reading() |
|
|
|
def pause_reading(self): |
|
if self._closing or not self._is_reading(): |
|
return |
|
self._stop_reading() |
|
|
|
def resume_reading(self): |
|
if self._is_reading() or self._closing: |
|
return |
|
self._start_reading() |
|
|
|
|
|
cdef void __uv_stream_on_shutdown(uv.uv_shutdown_t* req, |
|
int status) with gil: |
|
|
|
# callback for uv_shutdown |
|
|
|
if req.data is NULL: |
|
aio_logger.error( |
|
'UVStream.shutdown callback called with NULL req.data, status=%r', |
|
status) |
|
return |
|
|
|
cdef UVStream stream = <UVStream> req.data |
|
|
|
if status < 0 and status != uv.UV_ECANCELED: |
|
# From libuv source code: |
|
# The ECANCELED error code is a lie, the shutdown(2) syscall is a |
|
# fait accompli at this point. Maybe we should revisit this in |
|
# v0.11. A possible reason for leaving it unchanged is that it |
|
# informs the callee that the handle has been destroyed. |
|
|
|
if UVLOOP_DEBUG: |
|
stream._loop._debug_stream_shutdown_errors_total += 1 |
|
|
|
exc = convert_error(status) |
|
stream._fatal_error( |
|
exc, False, "error status in uv_stream_t.shutdown callback") |
|
return |
|
|
|
|
|
cdef inline bint __uv_stream_on_read_common(UVStream sc, Loop loop, |
|
ssize_t nread): |
|
if sc._closed: |
|
# The stream was closed, there is no reason to |
|
# do any work now. |
|
sc.__reading_stopped() # Just in case. |
|
return True |
|
|
|
if nread == uv.UV_EOF: |
|
# From libuv docs: |
|
# The callee is responsible for stopping closing the stream |
|
# when an error happens by calling uv_read_stop() or uv_close(). |
|
# Trying to read from the stream again is undefined. |
|
try: |
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_eof_total += 1 |
|
|
|
sc._stop_reading() |
|
sc._on_eof() |
|
except BaseException as ex: |
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_eof_cb_errors_total += 1 |
|
|
|
sc._fatal_error(ex, False) |
|
finally: |
|
return True |
|
|
|
if nread == 0: |
|
# From libuv docs: |
|
# nread might be 0, which does not indicate an error or EOF. |
|
# This is equivalent to EAGAIN or EWOULDBLOCK under read(2). |
|
return True |
|
|
|
if nread < 0: |
|
# From libuv docs: |
|
# The callee is responsible for stopping closing the stream |
|
# when an error happens by calling uv_read_stop() or uv_close(). |
|
# Trying to read from the stream again is undefined. |
|
# |
|
# Therefore, we're closing the stream. Since "UVHandle._close()" |
|
# doesn't raise exceptions unless uvloop is built with DEBUG=1, |
|
# we don't need try...finally here. |
|
|
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_errors_total += 1 |
|
|
|
if sc.__read_error_close: |
|
# Used for getting notified when a pipe is closed. |
|
# See WriteUnixTransport for the explanation. |
|
sc._on_eof() |
|
return True |
|
|
|
exc = convert_error(nread) |
|
sc._fatal_error( |
|
exc, False, "error status in uv_stream_t.read callback") |
|
return True |
|
|
|
return False |
|
|
|
|
|
cdef inline void __uv_stream_on_read_impl(uv.uv_stream_t* stream, |
|
ssize_t nread, |
|
const uv.uv_buf_t* buf): |
|
cdef: |
|
UVStream sc = <UVStream>stream.data |
|
Loop loop = sc._loop |
|
|
|
# It's OK to free the buffer early, since nothing will |
|
# be able to touch it until this method is done. |
|
__loop_free_buffer(loop) |
|
|
|
if __uv_stream_on_read_common(sc, loop, nread): |
|
return |
|
|
|
try: |
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_cb_total += 1 |
|
|
|
run_in_context1( |
|
sc.context, |
|
sc._protocol_data_received, |
|
loop._recv_buffer[:nread], |
|
) |
|
except BaseException as exc: |
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_cb_errors_total += 1 |
|
|
|
sc._fatal_error(exc, False) |
|
|
|
|
|
cdef inline void __uv_stream_on_write_impl(uv.uv_write_t* req, int status): |
|
cdef: |
|
_StreamWriteContext ctx = <_StreamWriteContext> req.data |
|
UVStream stream = <UVStream>ctx.stream |
|
|
|
ctx.close() |
|
|
|
if stream._closed: |
|
# The stream was closed, there is nothing to do. |
|
# Even if there is an error, like EPIPE, there |
|
# is no reason to report it. |
|
return |
|
|
|
if status < 0: |
|
if UVLOOP_DEBUG: |
|
stream._loop._debug_stream_write_errors_total += 1 |
|
|
|
exc = convert_error(status) |
|
stream._fatal_error( |
|
exc, False, "error status in uv_stream_t.write callback") |
|
return |
|
|
|
try: |
|
stream._on_write() |
|
except BaseException as exc: |
|
if UVLOOP_DEBUG: |
|
stream._loop._debug_stream_write_cb_errors_total += 1 |
|
|
|
stream._fatal_error(exc, False) |
|
|
|
|
|
cdef void __uv_stream_on_read(uv.uv_stream_t* stream, |
|
ssize_t nread, |
|
const uv.uv_buf_t* buf) with gil: |
|
|
|
if __ensure_handle_data(<uv.uv_handle_t*>stream, |
|
"UVStream read callback") == 0: |
|
return |
|
|
|
# Don't need try-finally, __uv_stream_on_read_impl is void |
|
__uv_stream_on_read_impl(stream, nread, buf) |
|
|
|
|
|
cdef void __uv_stream_on_write(uv.uv_write_t* req, int status) with gil: |
|
|
|
if UVLOOP_DEBUG: |
|
if req.data is NULL: |
|
aio_logger.error( |
|
'UVStream.write callback called with NULL req.data, status=%r', |
|
status) |
|
return |
|
|
|
# Don't need try-finally, __uv_stream_on_write_impl is void |
|
__uv_stream_on_write_impl(req, status) |
|
|
|
|
|
cdef void __uv_stream_buffered_alloc(uv.uv_handle_t* stream, |
|
size_t suggested_size, |
|
uv.uv_buf_t* uvbuf) with gil: |
|
|
|
if __ensure_handle_data(<uv.uv_handle_t*>stream, |
|
"UVStream alloc buffer callback") == 0: |
|
return |
|
|
|
cdef: |
|
UVStream sc = <UVStream>stream.data |
|
Loop loop = sc._loop |
|
Py_buffer* pybuf = &sc._read_pybuf |
|
int got_buf = 0 |
|
|
|
if sc._read_pybuf_acquired: |
|
uvbuf.len = 0 |
|
uvbuf.base = NULL |
|
return |
|
|
|
sc._read_pybuf_acquired = 0 |
|
try: |
|
buf = run_in_context1( |
|
sc.context, |
|
sc._protocol_get_buffer, |
|
suggested_size, |
|
) |
|
PyObject_GetBuffer(buf, pybuf, PyBUF_WRITABLE) |
|
got_buf = 1 |
|
except BaseException as exc: |
|
# Can't call 'sc._fatal_error' or 'sc._close', libuv will SF. |
|
# We'll do it later in __uv_stream_buffered_on_read when we |
|
# receive UV_ENOBUFS. |
|
uvbuf.len = 0 |
|
uvbuf.base = NULL |
|
return |
|
|
|
if not pybuf.len: |
|
uvbuf.len = 0 |
|
uvbuf.base = NULL |
|
if got_buf: |
|
PyBuffer_Release(pybuf) |
|
return |
|
|
|
sc._read_pybuf_acquired = 1 |
|
uvbuf.base = <char*>pybuf.buf |
|
uvbuf.len = pybuf.len |
|
|
|
|
|
cdef void __uv_stream_buffered_on_read(uv.uv_stream_t* stream, |
|
ssize_t nread, |
|
const uv.uv_buf_t* buf) with gil: |
|
|
|
if __ensure_handle_data(<uv.uv_handle_t*>stream, |
|
"UVStream buffered read callback") == 0: |
|
return |
|
|
|
cdef: |
|
UVStream sc = <UVStream>stream.data |
|
Loop loop = sc._loop |
|
Py_buffer* pybuf = &sc._read_pybuf |
|
|
|
if nread == uv.UV_ENOBUFS: |
|
sc._fatal_error( |
|
RuntimeError( |
|
'unhandled error (or an empty buffer) in get_buffer()'), |
|
False) |
|
return |
|
|
|
try: |
|
if nread > 0 and not sc._read_pybuf_acquired: |
|
# From libuv docs: |
|
# nread is > 0 if there is data available or < 0 on error. When |
|
# we’ve reached EOF, nread will be set to UV_EOF. When |
|
# nread < 0, the buf parameter might not point to a valid |
|
# buffer; in that case buf.len and buf.base are both set to 0. |
|
raise RuntimeError( |
|
f'no python buffer is allocated in on_read; nread={nread}') |
|
|
|
if nread == 0: |
|
# From libuv docs: |
|
# nread might be 0, which does not indicate an error or EOF. |
|
# This is equivalent to EAGAIN or EWOULDBLOCK under read(2). |
|
return |
|
|
|
if __uv_stream_on_read_common(sc, loop, nread): |
|
return |
|
|
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_cb_total += 1 |
|
|
|
run_in_context1(sc.context, sc._protocol_buffer_updated, nread) |
|
except BaseException as exc: |
|
if UVLOOP_DEBUG: |
|
loop._debug_stream_read_cb_errors_total += 1 |
|
|
|
sc._fatal_error(exc, False) |
|
finally: |
|
sc._read_pybuf_acquired = 0 |
|
PyBuffer_Release(pybuf)
|
|
|