second commit

This commit is contained in:
2024-12-27 22:31:23 +09:00
parent 2353324570
commit 10a0f110ca
8819 changed files with 1307198 additions and 28 deletions

View File

@ -0,0 +1,168 @@
import asyncio as __asyncio
import typing as _typing
import sys as _sys
import warnings as _warnings
from asyncio.events import BaseDefaultEventLoopPolicy as __BasePolicy
from . import includes as __includes # NOQA
from .loop import Loop as __BaseLoop # NOQA
from ._version import __version__ # NOQA
__all__ = ('new_event_loop', 'install', 'EventLoopPolicy')
_T = _typing.TypeVar("_T")
class Loop(__BaseLoop, __asyncio.AbstractEventLoop): # type: ignore[misc]
pass
def new_event_loop() -> Loop:
"""Return a new event loop."""
return Loop()
def install() -> None:
"""A helper function to install uvloop policy."""
if _sys.version_info[:2] >= (3, 12):
_warnings.warn(
'uvloop.install() is deprecated in favor of uvloop.run() '
'starting with Python 3.12.',
DeprecationWarning,
stacklevel=1,
)
__asyncio.set_event_loop_policy(EventLoopPolicy())
if _typing.TYPE_CHECKING:
def run(
main: _typing.Coroutine[_typing.Any, _typing.Any, _T],
*,
loop_factory: _typing.Optional[
_typing.Callable[[], Loop]
] = new_event_loop,
debug: _typing.Optional[bool]=None,
) -> _T:
"""The preferred way of running a coroutine with uvloop."""
else:
def run(main, *, loop_factory=new_event_loop, debug=None, **run_kwargs):
"""The preferred way of running a coroutine with uvloop."""
async def wrapper():
# If `loop_factory` is provided we want it to return
# either uvloop.Loop or a subtype of it, assuming the user
# is using `uvloop.run()` intentionally.
loop = __asyncio._get_running_loop()
if not isinstance(loop, Loop):
raise TypeError('uvloop.run() uses a non-uvloop event loop')
return await main
vi = _sys.version_info[:2]
if vi <= (3, 10):
# Copied from python/cpython
if __asyncio._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
if not __asyncio.iscoroutine(main):
raise ValueError(
"a coroutine was expected, got {!r}".format(main)
)
loop = loop_factory()
try:
__asyncio.set_event_loop(loop)
if debug is not None:
loop.set_debug(debug)
return loop.run_until_complete(wrapper())
finally:
try:
_cancel_all_tasks(loop)
loop.run_until_complete(loop.shutdown_asyncgens())
if hasattr(loop, 'shutdown_default_executor'):
loop.run_until_complete(
loop.shutdown_default_executor()
)
finally:
__asyncio.set_event_loop(None)
loop.close()
elif vi == (3, 11):
if __asyncio._get_running_loop() is not None:
raise RuntimeError(
"asyncio.run() cannot be called from a running event loop")
with __asyncio.Runner(
loop_factory=loop_factory,
debug=debug,
**run_kwargs
) as runner:
return runner.run(wrapper())
else:
assert vi >= (3, 12)
return __asyncio.run(
wrapper(),
loop_factory=loop_factory,
debug=debug,
**run_kwargs
)
def _cancel_all_tasks(loop: __asyncio.AbstractEventLoop) -> None:
# Copied from python/cpython
to_cancel = __asyncio.all_tasks(loop)
if not to_cancel:
return
for task in to_cancel:
task.cancel()
loop.run_until_complete(
__asyncio.gather(*to_cancel, return_exceptions=True)
)
for task in to_cancel:
if task.cancelled():
continue
if task.exception() is not None:
loop.call_exception_handler({
'message': 'unhandled exception during asyncio.run() shutdown',
'exception': task.exception(),
'task': task,
})
class EventLoopPolicy(__BasePolicy):
"""Event loop policy.
The preferred way to make your application use uvloop:
>>> import asyncio
>>> import uvloop
>>> asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
>>> asyncio.get_event_loop()
<uvloop.Loop running=False closed=False debug=False>
"""
def _loop_factory(self) -> Loop:
return new_event_loop()
if _typing.TYPE_CHECKING:
# EventLoopPolicy doesn't implement these, but since they are marked
# as abstract in typeshed, we have to put them in so mypy thinks
# the base methods are overridden. This is the same approach taken
# for the Windows event loop policy classes in typeshed.
def get_child_watcher(self) -> _typing.NoReturn:
...
def set_child_watcher(
self, watcher: _typing.Any
) -> _typing.NoReturn:
...

View File

@ -0,0 +1,3 @@
def noop() -> None:
"""Empty function to invoke CPython ceval loop."""
return

View File

@ -0,0 +1,552 @@
"""Test utilities. Don't use outside of the uvloop project."""
import asyncio
import asyncio.events
import collections
import contextlib
import gc
import logging
import os
import pprint
import re
import select
import socket
import ssl
import sys
import tempfile
import threading
import time
import unittest
import uvloop
class MockPattern(str):
def __eq__(self, other):
return bool(re.search(str(self), other, re.S))
class TestCaseDict(collections.UserDict):
def __init__(self, name):
super().__init__()
self.name = name
def __setitem__(self, key, value):
if key in self.data:
raise RuntimeError('duplicate test {}.{}'.format(
self.name, key))
super().__setitem__(key, value)
class BaseTestCaseMeta(type):
@classmethod
def __prepare__(mcls, name, bases):
return TestCaseDict(name)
def __new__(mcls, name, bases, dct):
for test_name in dct:
if not test_name.startswith('test_'):
continue
for base in bases:
if hasattr(base, test_name):
raise RuntimeError(
'duplicate test {}.{} (also defined in {} '
'parent class)'.format(
name, test_name, base.__name__))
return super().__new__(mcls, name, bases, dict(dct))
class BaseTestCase(unittest.TestCase, metaclass=BaseTestCaseMeta):
def new_loop(self):
raise NotImplementedError
def new_policy(self):
raise NotImplementedError
def mock_pattern(self, str):
return MockPattern(str)
async def wait_closed(self, obj):
if not isinstance(obj, asyncio.StreamWriter):
return
try:
await obj.wait_closed()
except (BrokenPipeError, ConnectionError):
pass
def is_asyncio_loop(self):
return type(self.loop).__module__.startswith('asyncio.')
def run_loop_briefly(self, *, delay=0.01):
self.loop.run_until_complete(asyncio.sleep(delay))
def loop_exception_handler(self, loop, context):
self.__unhandled_exceptions.append(context)
self.loop.default_exception_handler(context)
def setUp(self):
self.loop = self.new_loop()
asyncio.set_event_loop_policy(self.new_policy())
asyncio.set_event_loop(self.loop)
self._check_unclosed_resources_in_debug = True
self.loop.set_exception_handler(self.loop_exception_handler)
self.__unhandled_exceptions = []
def tearDown(self):
self.loop.close()
if self.__unhandled_exceptions:
print('Unexpected calls to loop.call_exception_handler():')
pprint.pprint(self.__unhandled_exceptions)
self.fail('unexpected calls to loop.call_exception_handler()')
return
if not self._check_unclosed_resources_in_debug:
return
# GC to show any resource warnings as the test completes
gc.collect()
gc.collect()
gc.collect()
if getattr(self.loop, '_debug_cc', False):
gc.collect()
gc.collect()
gc.collect()
self.assertEqual(
self.loop._debug_uv_handles_total,
self.loop._debug_uv_handles_freed,
'not all uv_handle_t handles were freed')
self.assertEqual(
self.loop._debug_cb_handles_count, 0,
'not all callbacks (call_soon) are GCed')
self.assertEqual(
self.loop._debug_cb_timer_handles_count, 0,
'not all timer callbacks (call_later) are GCed')
self.assertEqual(
self.loop._debug_stream_write_ctx_cnt, 0,
'not all stream write contexts are GCed')
for h_name, h_cnt in self.loop._debug_handles_current.items():
with self.subTest('Alive handle after test',
handle_name=h_name):
self.assertEqual(
h_cnt, 0,
'alive {} after test'.format(h_name))
for h_name, h_cnt in self.loop._debug_handles_total.items():
with self.subTest('Total/closed handles',
handle_name=h_name):
self.assertEqual(
h_cnt, self.loop._debug_handles_closed[h_name],
'total != closed for {}'.format(h_name))
asyncio.set_event_loop(None)
asyncio.set_event_loop_policy(None)
self.loop = None
def skip_unclosed_handles_check(self):
self._check_unclosed_resources_in_debug = False
def tcp_server(self, server_prog, *,
family=socket.AF_INET,
addr=None,
timeout=5,
backlog=1,
max_clients=10):
if addr is None:
if family == socket.AF_UNIX:
with tempfile.NamedTemporaryFile() as tmp:
addr = tmp.name
else:
addr = ('127.0.0.1', 0)
sock = socket.socket(family, socket.SOCK_STREAM)
if timeout is None:
raise RuntimeError('timeout is required')
if timeout <= 0:
raise RuntimeError('only blocking sockets are supported')
sock.settimeout(timeout)
try:
sock.bind(addr)
sock.listen(backlog)
except OSError as ex:
sock.close()
raise ex
return TestThreadedServer(
self, sock, server_prog, timeout, max_clients)
def tcp_client(self, client_prog,
family=socket.AF_INET,
timeout=10):
sock = socket.socket(family, socket.SOCK_STREAM)
if timeout is None:
raise RuntimeError('timeout is required')
if timeout <= 0:
raise RuntimeError('only blocking sockets are supported')
sock.settimeout(timeout)
return TestThreadedClient(
self, sock, client_prog, timeout)
def unix_server(self, *args, **kwargs):
return self.tcp_server(*args, family=socket.AF_UNIX, **kwargs)
def unix_client(self, *args, **kwargs):
return self.tcp_client(*args, family=socket.AF_UNIX, **kwargs)
@contextlib.contextmanager
def unix_sock_name(self):
with tempfile.TemporaryDirectory() as td:
fn = os.path.join(td, 'sock')
try:
yield fn
finally:
try:
os.unlink(fn)
except OSError:
pass
def _abort_socket_test(self, ex):
try:
self.loop.stop()
finally:
self.fail(ex)
def _cert_fullname(test_file_name, cert_file_name):
fullname = os.path.abspath(os.path.join(
os.path.dirname(test_file_name), 'certs', cert_file_name))
assert os.path.isfile(fullname)
return fullname
@contextlib.contextmanager
def silence_long_exec_warning():
class Filter(logging.Filter):
def filter(self, record):
return not (record.msg.startswith('Executing') and
record.msg.endswith('seconds'))
logger = logging.getLogger('asyncio')
filter = Filter()
logger.addFilter(filter)
try:
yield
finally:
logger.removeFilter(filter)
def find_free_port(start_from=50000):
for port in range(start_from, start_from + 500):
sock = socket.socket()
with sock:
try:
sock.bind(('', port))
except socket.error:
continue
else:
return port
raise RuntimeError('could not find a free port')
class SSLTestCase:
def _create_server_ssl_context(self, certfile, keyfile=None):
if hasattr(ssl, 'PROTOCOL_TLS_SERVER'):
sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER)
elif hasattr(ssl, 'PROTOCOL_TLS'):
sslcontext = ssl.SSLContext(ssl.PROTOCOL_TLS)
else:
sslcontext = ssl.SSLContext(ssl.PROTOCOL_SSLv23)
sslcontext.options |= ssl.OP_NO_SSLv2
sslcontext.load_cert_chain(certfile, keyfile)
return sslcontext
def _create_client_ssl_context(self, *, disable_verify=True):
sslcontext = ssl.create_default_context()
sslcontext.check_hostname = False
if disable_verify:
sslcontext.verify_mode = ssl.CERT_NONE
return sslcontext
@contextlib.contextmanager
def _silence_eof_received_warning(self):
# TODO This warning has to be fixed in asyncio.
logger = logging.getLogger('asyncio')
filter = logging.Filter('has no effect when using ssl')
logger.addFilter(filter)
try:
yield
finally:
logger.removeFilter(filter)
class UVTestCase(BaseTestCase):
implementation = 'uvloop'
def new_loop(self):
return uvloop.new_event_loop()
def new_policy(self):
return uvloop.EventLoopPolicy()
class AIOTestCase(BaseTestCase):
implementation = 'asyncio'
def setUp(self):
super().setUp()
if sys.version_info < (3, 12):
watcher = asyncio.SafeChildWatcher()
watcher.attach_loop(self.loop)
asyncio.set_child_watcher(watcher)
def tearDown(self):
if sys.version_info < (3, 12):
asyncio.set_child_watcher(None)
super().tearDown()
def new_loop(self):
return asyncio.new_event_loop()
def new_policy(self):
return asyncio.DefaultEventLoopPolicy()
def has_IPv6():
server_sock = socket.socket(socket.AF_INET6)
with server_sock:
try:
server_sock.bind(('::1', 0))
except OSError:
return False
else:
return True
has_IPv6 = has_IPv6()
###############################################################################
# Socket Testing Utilities
###############################################################################
class TestSocketWrapper:
def __init__(self, sock):
self.__sock = sock
def recv_all(self, n):
buf = b''
while len(buf) < n:
data = self.recv(n - len(buf))
if data == b'':
raise ConnectionAbortedError
buf += data
return buf
def starttls(self, ssl_context, *,
server_side=False,
server_hostname=None,
do_handshake_on_connect=True):
assert isinstance(ssl_context, ssl.SSLContext)
ssl_sock = ssl_context.wrap_socket(
self.__sock, server_side=server_side,
server_hostname=server_hostname,
do_handshake_on_connect=do_handshake_on_connect)
if server_side:
ssl_sock.do_handshake()
self.__sock.close()
self.__sock = ssl_sock
def __getattr__(self, name):
return getattr(self.__sock, name)
def __repr__(self):
return '<{} {!r}>'.format(type(self).__name__, self.__sock)
class SocketThread(threading.Thread):
def stop(self):
self._active = False
self.join()
def __enter__(self):
self.start()
return self
def __exit__(self, *exc):
self.stop()
class TestThreadedClient(SocketThread):
def __init__(self, test, sock, prog, timeout):
threading.Thread.__init__(self, None, None, 'test-client')
self.daemon = True
self._timeout = timeout
self._sock = sock
self._active = True
self._prog = prog
self._test = test
def run(self):
try:
self._prog(TestSocketWrapper(self._sock))
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
self._test._abort_socket_test(ex)
class TestThreadedServer(SocketThread):
def __init__(self, test, sock, prog, timeout, max_clients):
threading.Thread.__init__(self, None, None, 'test-server')
self.daemon = True
self._clients = 0
self._finished_clients = 0
self._max_clients = max_clients
self._timeout = timeout
self._sock = sock
self._active = True
self._prog = prog
self._s1, self._s2 = socket.socketpair()
self._s1.setblocking(False)
self._test = test
def stop(self):
try:
if self._s2 and self._s2.fileno() != -1:
try:
self._s2.send(b'stop')
except OSError:
pass
finally:
super().stop()
def run(self):
try:
with self._sock:
self._sock.setblocking(0)
self._run()
finally:
self._s1.close()
self._s2.close()
def _run(self):
while self._active:
if self._clients >= self._max_clients:
return
r, w, x = select.select(
[self._sock, self._s1], [], [], self._timeout)
if self._s1 in r:
return
if self._sock in r:
try:
conn, addr = self._sock.accept()
except BlockingIOError:
continue
except socket.timeout:
if not self._active:
return
else:
raise
else:
self._clients += 1
conn.settimeout(self._timeout)
try:
with conn:
self._handle_client(conn)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
self._active = False
try:
raise
finally:
self._test._abort_socket_test(ex)
def _handle_client(self, sock):
self._prog(TestSocketWrapper(sock))
@property
def addr(self):
return self._sock.getsockname()
###############################################################################
# A few helpers from asyncio/tests/testutils.py
###############################################################################
def run_briefly(loop):
async def once():
pass
gen = once()
t = loop.create_task(gen)
# Don't log a warning if the task is not done after run_until_complete().
# It occurs if the loop is stopped or if a task raises a BaseException.
t._log_destroy_pending = False
try:
loop.run_until_complete(t)
finally:
gen.close()
def run_until(loop, pred, timeout=30):
deadline = time.time() + timeout
while not pred():
if timeout is not None:
timeout = deadline - time.time()
if timeout <= 0:
raise asyncio.futures.TimeoutError()
loop.run_until_complete(asyncio.tasks.sleep(0.001))
@contextlib.contextmanager
def disable_logger():
"""Context manager to disable asyncio logger.
For example, it can be used to ignore warnings in debug mode.
"""
old_level = asyncio.log.logger.level
try:
asyncio.log.logger.setLevel(logging.CRITICAL + 1)
yield
finally:
asyncio.log.logger.setLevel(old_level)

View File

@ -0,0 +1,13 @@
# This file MUST NOT contain anything but the __version__ assignment.
#
# When making a release, change the value of __version__
# to an appropriate value, and open a pull request against
# the correct branch (master if making a new feature release).
# The commit message MUST contain a properly formatted release
# log, and the commit must be signed.
#
# The release automation will: build and test the packages for the
# supported platforms, publish the packages on PyPI, merge the PR
# to the target branch, create a Git tag pointing to the commit.
__version__ = '0.21.0'

View File

@ -0,0 +1,39 @@
cdef class Handle:
cdef:
Loop loop
object context
bint _cancelled
str meth_name
int cb_type
void *callback
object arg1, arg2, arg3, arg4
object __weakref__
readonly _source_traceback
cdef inline _set_loop(self, Loop loop)
cdef inline _set_context(self, object context)
cdef inline _run(self)
cdef _cancel(self)
cdef _format_handle(self)
cdef class TimerHandle:
cdef:
object callback
tuple args
bint _cancelled
UVTimer timer
Loop loop
object context
tuple _debug_info
object __weakref__
object _when
cdef _run(self)
cdef _cancel(self)
cdef inline _clear(self)

View File

@ -0,0 +1,434 @@
@cython.no_gc_clear
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class Handle:
def __cinit__(self):
self._cancelled = 0
self.cb_type = 0
self._source_traceback = None
cdef inline _set_loop(self, Loop loop):
self.loop = loop
if UVLOOP_DEBUG:
loop._debug_cb_handles_total += 1
loop._debug_cb_handles_count += 1
if loop._debug:
self._source_traceback = extract_stack()
cdef inline _set_context(self, object context):
if context is None:
context = Context_CopyCurrent()
self.context = context
def __dealloc__(self):
if UVLOOP_DEBUG and self.loop is not None:
self.loop._debug_cb_handles_count -= 1
if self.loop is None:
raise RuntimeError('Handle.loop is None in Handle.__dealloc__')
def __init__(self):
raise TypeError(
'{} is not supposed to be instantiated from Python'.format(
self.__class__.__name__))
cdef inline _run(self):
cdef:
int cb_type
object callback
if self._cancelled:
return
cb_type = self.cb_type
# Since _run is a cdef and there's no BoundMethod,
# we guard 'self' manually (since the callback
# might cause GC of the handle.)
Py_INCREF(self)
try:
assert self.context is not None
Context_Enter(self.context)
if cb_type == 1:
callback = self.arg1
if callback is None:
raise RuntimeError(
'cannot run Handle; callback is not set')
args = self.arg2
if args is None:
callback()
else:
callback(*args)
elif cb_type == 2:
(<method_t>self.callback)(self.arg1)
elif cb_type == 3:
(<method1_t>self.callback)(self.arg1, self.arg2)
elif cb_type == 4:
(<method2_t>self.callback)(self.arg1, self.arg2, self.arg3)
elif cb_type == 5:
(<method3_t>self.callback)(
self.arg1, self.arg2, self.arg3, self.arg4)
else:
raise RuntimeError('invalid Handle.cb_type: {}'.format(
cb_type))
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
if cb_type == 1:
msg = 'Exception in callback {}'.format(callback)
else:
msg = 'Exception in callback {}'.format(self.meth_name)
context = {
'message': msg,
'exception': ex,
'handle': self,
}
if self._source_traceback is not None:
context['source_traceback'] = self._source_traceback
self.loop.call_exception_handler(context)
finally:
context = self.context
Py_DECREF(self)
Context_Exit(context)
cdef _cancel(self):
self._cancelled = 1
self.callback = NULL
self.arg1 = self.arg2 = self.arg3 = self.arg4 = None
cdef _format_handle(self):
# Mirrors `asyncio.base_events._format_handle`.
if self.cb_type == 1 and self.arg1 is not None:
cb = self.arg1
if isinstance(getattr(cb, '__self__', None), aio_Task):
try:
return repr(cb.__self__)
except (AttributeError, TypeError, ValueError) as ex:
# Cython generates empty __code__ objects for coroutines
# that can crash asyncio.Task.__repr__ with an
# AttributeError etc. Guard against that.
self.loop.call_exception_handler({
'message': 'exception in Task.__repr__',
'task': cb.__self__,
'exception': ex,
'handle': self,
})
return repr(self)
# Public API
def __repr__(self):
info = [self.__class__.__name__]
if self._cancelled:
info.append('cancelled')
if self.cb_type == 1 and self.arg1 is not None:
func = self.arg1
# Cython can unset func.__qualname__/__name__, hence the checks.
if hasattr(func, '__qualname__') and func.__qualname__:
cb_name = func.__qualname__
elif hasattr(func, '__name__') and func.__name__:
cb_name = func.__name__
else:
cb_name = repr(func)
info.append(cb_name)
elif self.meth_name is not None:
info.append(self.meth_name)
if self._source_traceback is not None:
frame = self._source_traceback[-1]
info.append('created at {}:{}'.format(frame[0], frame[1]))
return '<' + ' '.join(info) + '>'
def cancel(self):
self._cancel()
def cancelled(self):
return self._cancelled
@cython.no_gc_clear
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class TimerHandle:
def __cinit__(self, Loop loop, object callback, object args,
uint64_t delay, object context):
self.loop = loop
self.callback = callback
self.args = args
self._cancelled = 0
if UVLOOP_DEBUG:
self.loop._debug_cb_timer_handles_total += 1
self.loop._debug_cb_timer_handles_count += 1
if context is None:
context = Context_CopyCurrent()
self.context = context
if loop._debug:
self._debug_info = (
format_callback_name(callback),
extract_stack()
)
else:
self._debug_info = None
self.timer = UVTimer.new(
loop, <method_t>self._run, self, delay)
self.timer.start()
self._when = self.timer.get_when() * 1e-3
# Only add to loop._timers when `self.timer` is successfully created
loop._timers.add(self)
property _source_traceback:
def __get__(self):
if self._debug_info is not None:
return self._debug_info[1]
def __dealloc__(self):
if UVLOOP_DEBUG:
self.loop._debug_cb_timer_handles_count -= 1
if self.timer is not None:
raise RuntimeError('active TimerHandle is deallacating')
cdef _cancel(self):
if self._cancelled == 1:
return
self._cancelled = 1
self._clear()
cdef inline _clear(self):
if self.timer is None:
return
self.callback = None
self.args = None
try:
self.loop._timers.remove(self)
finally:
self.timer._close()
self.timer = None # let the UVTimer handle GC
cdef _run(self):
if self._cancelled == 1:
return
if self.callback is None:
raise RuntimeError('cannot run TimerHandle; callback is not set')
callback = self.callback
args = self.args
# Since _run is a cdef and there's no BoundMethod,
# we guard 'self' manually.
Py_INCREF(self)
if self.loop._debug:
started = time_monotonic()
try:
assert self.context is not None
Context_Enter(self.context)
if args is not None:
callback(*args)
else:
callback()
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
context = {
'message': 'Exception in callback {}'.format(callback),
'exception': ex,
'handle': self,
}
if self._debug_info is not None:
context['source_traceback'] = self._debug_info[1]
self.loop.call_exception_handler(context)
else:
if self.loop._debug:
delta = time_monotonic() - started
if delta > self.loop.slow_callback_duration:
aio_logger.warning(
'Executing %r took %.3f seconds',
self, delta)
finally:
context = self.context
Py_DECREF(self)
Context_Exit(context)
self._clear()
# Public API
def __repr__(self):
info = [self.__class__.__name__]
if self._cancelled:
info.append('cancelled')
if self._debug_info is not None:
callback_name = self._debug_info[0]
source_traceback = self._debug_info[1]
else:
callback_name = None
source_traceback = None
if callback_name is not None:
info.append(callback_name)
elif self.callback is not None:
info.append(format_callback_name(self.callback))
if source_traceback is not None:
frame = source_traceback[-1]
info.append('created at {}:{}'.format(frame[0], frame[1]))
return '<' + ' '.join(info) + '>'
def cancelled(self):
return self._cancelled
def cancel(self):
self._cancel()
def when(self):
return self._when
cdef format_callback_name(func):
if hasattr(func, '__qualname__'):
cb_name = getattr(func, '__qualname__')
elif hasattr(func, '__name__'):
cb_name = getattr(func, '__name__')
else:
cb_name = repr(func)
return cb_name
cdef new_Handle(Loop loop, object callback, object args, object context):
cdef Handle handle
handle = Handle.__new__(Handle)
handle._set_loop(loop)
handle._set_context(context)
handle.cb_type = 1
handle.arg1 = callback
handle.arg2 = args
return handle
cdef new_MethodHandle(Loop loop, str name, method_t callback, object context,
object bound_to):
cdef Handle handle
handle = Handle.__new__(Handle)
handle._set_loop(loop)
handle._set_context(context)
handle.cb_type = 2
handle.meth_name = name
handle.callback = <void*> callback
handle.arg1 = bound_to
return handle
cdef new_MethodHandle1(Loop loop, str name, method1_t callback, object context,
object bound_to, object arg):
cdef Handle handle
handle = Handle.__new__(Handle)
handle._set_loop(loop)
handle._set_context(context)
handle.cb_type = 3
handle.meth_name = name
handle.callback = <void*> callback
handle.arg1 = bound_to
handle.arg2 = arg
return handle
cdef new_MethodHandle2(Loop loop, str name, method2_t callback, object context,
object bound_to, object arg1, object arg2):
cdef Handle handle
handle = Handle.__new__(Handle)
handle._set_loop(loop)
handle._set_context(context)
handle.cb_type = 4
handle.meth_name = name
handle.callback = <void*> callback
handle.arg1 = bound_to
handle.arg2 = arg1
handle.arg3 = arg2
return handle
cdef new_MethodHandle3(Loop loop, str name, method3_t callback, object context,
object bound_to, object arg1, object arg2, object arg3):
cdef Handle handle
handle = Handle.__new__(Handle)
handle._set_loop(loop)
handle._set_context(context)
handle.cb_type = 5
handle.meth_name = name
handle.callback = <void*> callback
handle.arg1 = bound_to
handle.arg2 = arg1
handle.arg3 = arg2
handle.arg4 = arg3
return handle
cdef extract_stack():
"""Replacement for traceback.extract_stack() that only does the
necessary work for asyncio debug mode.
"""
try:
f = sys_getframe()
# sys._getframe() might raise ValueError if being called without a frame, e.g.
# from Cython or similar C extensions.
except ValueError:
return None
if f is None:
return
try:
stack = tb_StackSummary.extract(tb_walk_stack(f),
limit=DEBUG_STACK_DEPTH,
lookup_lines=False)
finally:
f = None
stack.reverse()
return stack

View File

@ -0,0 +1,479 @@
cdef __port_to_int(port, proto):
if type(port) is int:
return port
if port is None or port == '' or port == b'':
return 0
try:
return int(port)
except (ValueError, TypeError):
pass
if isinstance(port, bytes):
port = port.decode()
if isinstance(port, str) and proto is not None:
if proto == uv.IPPROTO_TCP:
return socket_getservbyname(port, 'tcp')
elif proto == uv.IPPROTO_UDP:
return socket_getservbyname(port, 'udp')
raise OSError('service/proto not found')
cdef __convert_sockaddr_to_pyaddr(const system.sockaddr* addr):
# Converts sockaddr structs into what Python socket
# module can understand:
# - for IPv4 a tuple of (host, port)
# - for IPv6 a tuple of (host, port, flowinfo, scope_id)
cdef:
char buf[128] # INET6_ADDRSTRLEN is usually 46
int err
system.sockaddr_in *addr4
system.sockaddr_in6 *addr6
system.sockaddr_un *addr_un
if addr.sa_family == uv.AF_INET:
addr4 = <system.sockaddr_in*>addr
err = uv.uv_ip4_name(addr4, buf, sizeof(buf))
if err < 0:
raise convert_error(err)
return (
PyUnicode_FromString(buf),
system.ntohs(addr4.sin_port)
)
elif addr.sa_family == uv.AF_INET6:
addr6 = <system.sockaddr_in6*>addr
err = uv.uv_ip6_name(addr6, buf, sizeof(buf))
if err < 0:
raise convert_error(err)
return (
PyUnicode_FromString(buf),
system.ntohs(addr6.sin6_port),
system.ntohl(addr6.sin6_flowinfo),
addr6.sin6_scope_id
)
elif addr.sa_family == uv.AF_UNIX:
addr_un = <system.sockaddr_un*>addr
return system.MakeUnixSockPyAddr(addr_un)
raise RuntimeError("cannot convert sockaddr into Python object")
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class SockAddrHolder:
cdef:
int family
system.sockaddr_storage addr
Py_ssize_t addr_size
cdef LruCache sockaddrs = LruCache(maxsize=DNS_PYADDR_TO_SOCKADDR_CACHE_SIZE)
cdef __convert_pyaddr_to_sockaddr(int family, object addr,
system.sockaddr* res):
cdef:
int err
int addr_len
int scope_id = 0
int flowinfo = 0
char *buf
Py_ssize_t buflen
SockAddrHolder ret
ret = sockaddrs.get(addr, None)
if ret is not None and ret.family == family:
memcpy(res, &ret.addr, ret.addr_size)
return
ret = SockAddrHolder.__new__(SockAddrHolder)
if family == uv.AF_INET:
if not isinstance(addr, tuple):
raise TypeError('AF_INET address must be tuple')
if len(addr) != 2:
raise ValueError('AF_INET address must be tuple of (host, port)')
host, port = addr
if isinstance(host, str):
try:
# idna codec is rather slow, so we try ascii first.
host = host.encode('ascii')
except UnicodeEncodeError:
host = host.encode('idna')
if not isinstance(host, (bytes, bytearray)):
raise TypeError('host must be a string or bytes object')
port = __port_to_int(port, None)
ret.addr_size = sizeof(system.sockaddr_in)
err = uv.uv_ip4_addr(host, <int>port, <system.sockaddr_in*>&ret.addr)
if err < 0:
raise convert_error(err)
elif family == uv.AF_INET6:
if not isinstance(addr, tuple):
raise TypeError('AF_INET6 address must be tuple')
addr_len = len(addr)
if addr_len < 2 or addr_len > 4:
raise ValueError(
'AF_INET6 must be a tuple of 2-4 parameters: '
'(host, port, flowinfo?, scope_id?)')
host = addr[0]
if isinstance(host, str):
try:
# idna codec is rather slow, so we try ascii first.
host = host.encode('ascii')
except UnicodeEncodeError:
host = host.encode('idna')
if not isinstance(host, (bytes, bytearray)):
raise TypeError('host must be a string or bytes object')
port = __port_to_int(addr[1], None)
if addr_len > 2:
flowinfo = addr[2]
if addr_len > 3:
scope_id = addr[3]
ret.addr_size = sizeof(system.sockaddr_in6)
err = uv.uv_ip6_addr(host, port, <system.sockaddr_in6*>&ret.addr)
if err < 0:
raise convert_error(err)
(<system.sockaddr_in6*>&ret.addr).sin6_flowinfo = flowinfo
(<system.sockaddr_in6*>&ret.addr).sin6_scope_id = scope_id
elif family == uv.AF_UNIX:
if isinstance(addr, str):
addr = addr.encode(sys_getfilesystemencoding())
elif not isinstance(addr, bytes):
raise TypeError('AF_UNIX address must be a str or a bytes object')
PyBytes_AsStringAndSize(addr, &buf, &buflen)
if buflen > 107:
raise ValueError(
f'unix socket path {addr!r} is longer than 107 characters')
ret.addr_size = sizeof(system.sockaddr_un)
memset(&ret.addr, 0, sizeof(system.sockaddr_un))
(<system.sockaddr_un*>&ret.addr).sun_family = uv.AF_UNIX
memcpy((<system.sockaddr_un*>&ret.addr).sun_path, buf, buflen)
else:
raise ValueError(
f'expected AF_INET, AF_INET6, or AF_UNIX family, got {family}')
ret.family = family
sockaddrs[addr] = ret
memcpy(res, &ret.addr, ret.addr_size)
cdef __static_getaddrinfo(object host, object port,
int family, int type,
int proto,
system.sockaddr *addr):
if proto not in {0, uv.IPPROTO_TCP, uv.IPPROTO_UDP}:
return
if _is_sock_stream(type):
proto = uv.IPPROTO_TCP
elif _is_sock_dgram(type):
proto = uv.IPPROTO_UDP
else:
return
try:
port = __port_to_int(port, proto)
except Exception:
return
hp = (host, port)
if family == uv.AF_UNSPEC:
try:
__convert_pyaddr_to_sockaddr(uv.AF_INET, hp, addr)
except Exception:
pass
else:
return (uv.AF_INET, type, proto)
try:
__convert_pyaddr_to_sockaddr(uv.AF_INET6, hp, addr)
except Exception:
pass
else:
return (uv.AF_INET6, type, proto)
else:
try:
__convert_pyaddr_to_sockaddr(family, hp, addr)
except Exception:
pass
else:
return (family, type, proto)
cdef __static_getaddrinfo_pyaddr(object host, object port,
int family, int type,
int proto, int flags):
cdef:
system.sockaddr_storage addr
object triplet
triplet = __static_getaddrinfo(
host, port, family, type,
proto, <system.sockaddr*>&addr)
if triplet is None:
return
af, type, proto = triplet
try:
pyaddr = __convert_sockaddr_to_pyaddr(<system.sockaddr*>&addr)
except Exception:
return
# When the host is an IP while type is one of TCP or UDP, different libc
# implementations of getaddrinfo() behave differently:
# 1. When AI_CANONNAME is set:
# * glibc: returns ai_canonname
# * musl: returns ai_canonname
# * macOS: returns an empty string for ai_canonname
# 2. When AI_CANONNAME is NOT set:
# * glibc: returns an empty string for ai_canonname
# * musl: returns ai_canonname
# * macOS: returns an empty string for ai_canonname
# At the same time, libuv and CPython both uses libc directly, even though
# this different behavior is violating what is in the documentation.
#
# uvloop potentially should be a 100% drop-in replacement for asyncio,
# doing whatever asyncio does, especially when the libc implementations are
# also different in the same way. However, making our implementation to be
# consistent with libc/CPython would be complex and hard to maintain
# (including caching libc behaviors when flag is/not set), therefore we
# decided to simply normalize the behavior in uvloop for this very marginal
# case following the documentation, even though uvloop would behave
# differently to asyncio on macOS and musl platforms, when again the host
# is an IP and type is one of TCP or UDP.
# All other cases are still asyncio-compatible.
if flags & socket_AI_CANONNAME:
if isinstance(host, str):
canon_name = host
else:
canon_name = host.decode('ascii')
else:
canon_name = ''
return (
_intenum_converter(af, socket_AddressFamily),
_intenum_converter(type, socket_SocketKind),
proto,
canon_name,
pyaddr,
)
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class AddrInfo:
cdef:
system.addrinfo *data
def __cinit__(self):
self.data = NULL
def __dealloc__(self):
if self.data is not NULL:
uv.uv_freeaddrinfo(self.data) # returns void
self.data = NULL
cdef void set_data(self, system.addrinfo *data) noexcept:
self.data = data
cdef unpack(self):
cdef:
list result = []
system.addrinfo *ptr
if self.data is NULL:
raise RuntimeError('AddrInfo.data is NULL')
ptr = self.data
while ptr != NULL:
if ptr.ai_addr.sa_family in (uv.AF_INET, uv.AF_INET6):
result.append((
_intenum_converter(ptr.ai_family, socket_AddressFamily),
_intenum_converter(ptr.ai_socktype, socket_SocketKind),
ptr.ai_protocol,
('' if ptr.ai_canonname is NULL else
(<bytes>ptr.ai_canonname).decode()),
__convert_sockaddr_to_pyaddr(ptr.ai_addr)
))
ptr = ptr.ai_next
return result
@staticmethod
cdef int isinstance(object other):
return type(other) is AddrInfo
cdef class AddrInfoRequest(UVRequest):
cdef:
system.addrinfo hints
object callback
uv.uv_getaddrinfo_t _req_data
def __cinit__(self, Loop loop,
bytes host, bytes port,
int family, int type, int proto, int flags,
object callback):
cdef:
int err
char *chost
char *cport
if host is None:
chost = NULL
elif host == b'' and sys.platform == 'darwin':
# It seems `getaddrinfo("", ...)` on macOS is equivalent to
# `getaddrinfo("localhost", ...)`. This is inconsistent with
# libuv 1.48 which treats empty nodename as EINVAL.
chost = <char*>'localhost'
else:
chost = <char*>host
if port is None:
cport = NULL
else:
cport = <char*>port
memset(&self.hints, 0, sizeof(system.addrinfo))
self.hints.ai_flags = flags
self.hints.ai_family = family
self.hints.ai_socktype = type
self.hints.ai_protocol = proto
self.request = <uv.uv_req_t*> &self._req_data
self.callback = callback
self.request.data = <void*>self
err = uv.uv_getaddrinfo(loop.uvloop,
<uv.uv_getaddrinfo_t*>self.request,
__on_addrinfo_resolved,
chost,
cport,
&self.hints)
if err < 0:
self.on_done()
try:
if err == uv.UV_EINVAL:
# Convert UV_EINVAL to EAI_NONAME to match libc behavior
msg = system.gai_strerror(socket_EAI_NONAME).decode('utf-8')
ex = socket_gaierror(socket_EAI_NONAME, msg)
else:
ex = convert_error(err)
except Exception as ex:
callback(ex)
else:
callback(ex)
cdef class NameInfoRequest(UVRequest):
cdef:
object callback
uv.uv_getnameinfo_t _req_data
def __cinit__(self, Loop loop, callback):
self.request = <uv.uv_req_t*> &self._req_data
self.callback = callback
self.request.data = <void*>self
cdef query(self, system.sockaddr *addr, int flags):
cdef int err
err = uv.uv_getnameinfo(self.loop.uvloop,
<uv.uv_getnameinfo_t*>self.request,
__on_nameinfo_resolved,
addr,
flags)
if err < 0:
self.on_done()
self.callback(convert_error(err))
cdef _intenum_converter(value, enum_klass):
try:
return enum_klass(value)
except ValueError:
return value
cdef void __on_addrinfo_resolved(
uv.uv_getaddrinfo_t *resolver,
int status,
system.addrinfo *res,
) noexcept with gil:
if resolver.data is NULL:
aio_logger.error(
'AddrInfoRequest callback called with NULL resolver.data')
return
cdef:
AddrInfoRequest request = <AddrInfoRequest> resolver.data
Loop loop = request.loop
object callback = request.callback
AddrInfo ai
try:
if status < 0:
callback(convert_error(status))
else:
ai = AddrInfo()
ai.set_data(res)
callback(ai)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
loop._handle_exception(ex)
finally:
request.on_done()
cdef void __on_nameinfo_resolved(
uv.uv_getnameinfo_t* req,
int status,
const char* hostname,
const char* service,
) noexcept with gil:
cdef:
NameInfoRequest request = <NameInfoRequest> req.data
Loop loop = request.loop
object callback = request.callback
try:
if status < 0:
callback(convert_error(status))
else:
callback(((<bytes>hostname).decode(),
(<bytes>service).decode()))
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
loop._handle_exception(ex)
finally:
request.on_done()

View File

@ -0,0 +1,113 @@
cdef str __strerr(int errno):
return strerror(errno).decode()
cdef __convert_python_error(int uverr):
# XXX Won't work for Windows:
# From libuv docs:
# Implementation detail: on Unix error codes are the
# negated errno (or -errno), while on Windows they
# are defined by libuv to arbitrary negative numbers.
cdef int oserr = -uverr
exc = OSError
if uverr in (uv.UV_EACCES, uv.UV_EPERM):
exc = PermissionError
elif uverr in (uv.UV_EAGAIN, uv.UV_EALREADY):
exc = BlockingIOError
elif uverr in (uv.UV_EPIPE, uv.UV_ESHUTDOWN):
exc = BrokenPipeError
elif uverr == uv.UV_ECONNABORTED:
exc = ConnectionAbortedError
elif uverr == uv.UV_ECONNREFUSED:
exc = ConnectionRefusedError
elif uverr == uv.UV_ECONNRESET:
exc = ConnectionResetError
elif uverr == uv.UV_EEXIST:
exc = FileExistsError
elif uverr == uv.UV_ENOENT:
exc = FileNotFoundError
elif uverr == uv.UV_EINTR:
exc = InterruptedError
elif uverr == uv.UV_EISDIR:
exc = IsADirectoryError
elif uverr == uv.UV_ESRCH:
exc = ProcessLookupError
elif uverr == uv.UV_ETIMEDOUT:
exc = TimeoutError
return exc(oserr, __strerr(oserr))
cdef int __convert_socket_error(int uverr):
cdef int sock_err = 0
if uverr == uv.UV_EAI_ADDRFAMILY:
sock_err = socket_EAI_ADDRFAMILY
elif uverr == uv.UV_EAI_AGAIN:
sock_err = socket_EAI_AGAIN
elif uverr == uv.UV_EAI_BADFLAGS:
sock_err = socket_EAI_BADFLAGS
elif uverr == uv.UV_EAI_BADHINTS:
sock_err = socket_EAI_BADHINTS
elif uverr == uv.UV_EAI_CANCELED:
sock_err = socket_EAI_CANCELED
elif uverr == uv.UV_EAI_FAIL:
sock_err = socket_EAI_FAIL
elif uverr == uv.UV_EAI_FAMILY:
sock_err = socket_EAI_FAMILY
elif uverr == uv.UV_EAI_MEMORY:
sock_err = socket_EAI_MEMORY
elif uverr == uv.UV_EAI_NODATA:
sock_err = socket_EAI_NODATA
elif uverr == uv.UV_EAI_NONAME:
sock_err = socket_EAI_NONAME
elif uverr == uv.UV_EAI_OVERFLOW:
sock_err = socket_EAI_OVERFLOW
elif uverr == uv.UV_EAI_PROTOCOL:
sock_err = socket_EAI_PROTOCOL
elif uverr == uv.UV_EAI_SERVICE:
sock_err = socket_EAI_SERVICE
elif uverr == uv.UV_EAI_SOCKTYPE:
sock_err = socket_EAI_SOCKTYPE
return sock_err
cdef convert_error(int uverr):
cdef int sock_err
if uverr == uv.UV_ECANCELED:
return aio_CancelledError()
sock_err = __convert_socket_error(uverr)
if sock_err:
msg = system.gai_strerror(sock_err).decode('utf-8')
return socket_gaierror(sock_err, msg)
return __convert_python_error(uverr)

View File

@ -0,0 +1,11 @@
cdef class UVAsync(UVHandle):
cdef:
method_t callback
object ctx
cdef _init(self, Loop loop, method_t callback, object ctx)
cdef send(self)
@staticmethod
cdef UVAsync new(Loop loop, method_t callback, object ctx)

View File

@ -0,0 +1,56 @@
@cython.no_gc_clear
cdef class UVAsync(UVHandle):
cdef _init(self, Loop loop, method_t callback, object ctx):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_async_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_async_init(self._loop.uvloop,
<uv.uv_async_t*>self._handle,
__uvasync_callback)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.callback = callback
self.ctx = ctx
cdef send(self):
cdef int err
self._ensure_alive()
err = uv.uv_async_send(<uv.uv_async_t*>self._handle)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
@staticmethod
cdef UVAsync new(Loop loop, method_t callback, object ctx):
cdef UVAsync handle
handle = UVAsync.__new__(UVAsync)
handle._init(loop, callback, ctx)
return handle
cdef void __uvasync_callback(
uv.uv_async_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVAsync callback") == 0:
return
cdef:
UVAsync async_ = <UVAsync> handle.data
method_t cb = async_.callback
try:
cb(async_.ctx)
except BaseException as ex:
async_._error(ex, False)

View File

@ -0,0 +1,54 @@
cdef class UVBaseTransport(UVSocketHandle):
cdef:
readonly bint _closing
bint _protocol_connected
bint _protocol_paused
object _protocol_data_received
size_t _high_water
size_t _low_water
object _protocol
Server _server
object _waiter
dict _extra_info
uint32_t _conn_lost
object __weakref__
# All "inline" methods are final
cdef inline _maybe_pause_protocol(self)
cdef inline _maybe_resume_protocol(self)
cdef inline _schedule_call_connection_made(self)
cdef inline _schedule_call_connection_lost(self, exc)
cdef _wakeup_waiter(self)
cdef _call_connection_made(self)
cdef _call_connection_lost(self, exc)
# Overloads of UVHandle methods:
cdef _fatal_error(self, exc, throw, reason=?)
cdef _close(self)
cdef inline _set_server(self, Server server)
cdef inline _set_waiter(self, object waiter)
cdef _set_protocol(self, object protocol)
cdef _clear_protocol(self)
cdef inline _init_protocol(self)
cdef inline _add_extra_info(self, str name, object obj)
# === overloads ===
cdef _new_socket(self)
cdef size_t _get_write_buffer_size(self)
cdef bint _is_reading(self)
cdef _start_reading(self)
cdef _stop_reading(self)

View File

@ -0,0 +1,293 @@
cdef class UVBaseTransport(UVSocketHandle):
def __cinit__(self):
# Flow control
self._high_water = FLOW_CONTROL_HIGH_WATER * 1024
self._low_water = FLOW_CONTROL_HIGH_WATER // 4
self._protocol = None
self._protocol_connected = 0
self._protocol_paused = 0
self._protocol_data_received = None
self._server = None
self._waiter = None
self._extra_info = None
self._conn_lost = 0
self._closing = 0
cdef size_t _get_write_buffer_size(self):
return 0
cdef inline _schedule_call_connection_made(self):
self._loop._call_soon_handle(
new_MethodHandle(self._loop,
"UVTransport._call_connection_made",
<method_t>self._call_connection_made,
self.context,
self))
cdef inline _schedule_call_connection_lost(self, exc):
self._loop._call_soon_handle(
new_MethodHandle1(self._loop,
"UVTransport._call_connection_lost",
<method1_t>self._call_connection_lost,
self.context,
self, exc))
cdef _fatal_error(self, exc, throw, reason=None):
# Overload UVHandle._fatal_error
self._force_close(exc)
if not isinstance(exc, OSError):
if throw or self._loop is None:
raise exc
msg = f'Fatal error on transport {self.__class__.__name__}'
if reason is not None:
msg = f'{msg} ({reason})'
self._loop.call_exception_handler({
'message': msg,
'exception': exc,
'transport': self,
'protocol': self._protocol,
})
cdef inline _maybe_pause_protocol(self):
cdef:
size_t size = self._get_write_buffer_size()
if size <= self._high_water:
return
if not self._protocol_paused:
self._protocol_paused = 1
try:
# _maybe_pause_protocol() is always triggered from user-calls,
# so we must copy the context to avoid entering context twice
run_in_context(
self.context.copy(), self._protocol.pause_writing,
)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
self._loop.call_exception_handler({
'message': 'protocol.pause_writing() failed',
'exception': exc,
'transport': self,
'protocol': self._protocol,
})
cdef inline _maybe_resume_protocol(self):
cdef:
size_t size = self._get_write_buffer_size()
if self._protocol_paused and size <= self._low_water:
self._protocol_paused = 0
try:
# We're copying the context to avoid entering context twice,
# even though it's not always necessary to copy - it's easier
# to copy here than passing down a copied context.
run_in_context(
self.context.copy(), self._protocol.resume_writing,
)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
self._loop.call_exception_handler({
'message': 'protocol.resume_writing() failed',
'exception': exc,
'transport': self,
'protocol': self._protocol,
})
cdef _wakeup_waiter(self):
if self._waiter is not None:
if not self._waiter.cancelled():
if not self._is_alive():
self._waiter.set_exception(
RuntimeError(
'closed Transport handle and unset waiter'))
else:
self._waiter.set_result(True)
self._waiter = None
cdef _call_connection_made(self):
if self._protocol is None:
raise RuntimeError(
'protocol is not set, cannot call connection_made()')
# We use `_is_alive()` and not `_closing`, because we call
# `transport._close()` in `loop.create_connection()` if an
# exception happens during `await waiter`.
if not self._is_alive():
# A connection waiter can be cancelled between
# 'await loop.create_connection()' and
# `_schedule_call_connection_made` and
# the actual `_call_connection_made`.
self._wakeup_waiter()
return
# Set _protocol_connected to 1 before calling "connection_made":
# if transport is aborted or closed, "connection_lost" will
# still be scheduled.
self._protocol_connected = 1
try:
self._protocol.connection_made(self)
except BaseException:
self._wakeup_waiter()
raise
if not self._is_alive():
# This might happen when "transport.abort()" is called
# from "Protocol.connection_made".
self._wakeup_waiter()
return
self._start_reading()
self._wakeup_waiter()
cdef _call_connection_lost(self, exc):
if self._waiter is not None:
if not self._waiter.done():
self._waiter.set_exception(exc)
self._waiter = None
if self._closed:
# The handle is closed -- likely, _call_connection_lost
# was already called before.
return
try:
if self._protocol_connected:
self._protocol.connection_lost(exc)
finally:
self._clear_protocol()
self._close()
server = self._server
if server is not None:
(<Server>server)._detach()
self._server = None
cdef inline _set_server(self, Server server):
self._server = server
(<Server>server)._attach()
cdef inline _set_waiter(self, object waiter):
if waiter is not None and not isfuture(waiter):
raise TypeError(
f'invalid waiter object {waiter!r}, expected asyncio.Future')
self._waiter = waiter
cdef _set_protocol(self, object protocol):
self._protocol = protocol
# Store a reference to the bound method directly
try:
self._protocol_data_received = protocol.data_received
except AttributeError:
pass
cdef _clear_protocol(self):
self._protocol = None
self._protocol_data_received = None
cdef inline _init_protocol(self):
self._loop._track_transport(self)
if self._protocol is None:
raise RuntimeError('invalid _init_protocol call')
self._schedule_call_connection_made()
cdef inline _add_extra_info(self, str name, object obj):
if self._extra_info is None:
self._extra_info = {}
self._extra_info[name] = obj
cdef bint _is_reading(self):
raise NotImplementedError
cdef _start_reading(self):
raise NotImplementedError
cdef _stop_reading(self):
raise NotImplementedError
# === Public API ===
property _paused:
# Used by SSLProto. Might be removed in the future.
def __get__(self):
return bool(not self._is_reading())
def get_protocol(self):
return self._protocol
def set_protocol(self, protocol):
self._set_protocol(protocol)
if self._is_reading():
self._stop_reading()
self._start_reading()
def _force_close(self, exc):
# Used by SSLProto. Might be removed in the future.
if self._conn_lost or self._closed:
return
if not self._closing:
self._closing = 1
self._stop_reading()
self._conn_lost += 1
self._schedule_call_connection_lost(exc)
def abort(self):
self._force_close(None)
def close(self):
if self._closing or self._closed:
return
self._closing = 1
self._stop_reading()
if not self._get_write_buffer_size():
# The write buffer is empty
self._conn_lost += 1
self._schedule_call_connection_lost(None)
def is_closing(self):
return self._closing
def get_write_buffer_size(self):
return self._get_write_buffer_size()
def set_write_buffer_limits(self, high=None, low=None):
self._ensure_alive()
self._high_water, self._low_water = add_flowcontrol_defaults(
high, low, FLOW_CONTROL_HIGH_WATER)
self._maybe_pause_protocol()
def get_write_buffer_limits(self):
return (self._low_water, self._high_water)
def get_extra_info(self, name, default=None):
if self._extra_info is not None and name in self._extra_info:
return self._extra_info[name]
if name == 'socket':
return self._get_socket()
if name == 'sockname':
return self._get_socket().getsockname()
if name == 'peername':
try:
return self._get_socket().getpeername()
except socket_error:
return default
return default

View File

@ -0,0 +1,14 @@
cdef class UVCheck(UVHandle):
cdef:
Handle h
bint running
# All "inline" methods are final
cdef _init(self, Loop loop, Handle h)
cdef inline stop(self)
cdef inline start(self)
@staticmethod
cdef UVCheck new(Loop loop, Handle h)

View File

@ -0,0 +1,72 @@
@cython.no_gc_clear
cdef class UVCheck(UVHandle):
cdef _init(self, Loop loop, Handle h):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_check_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_check_init(self._loop.uvloop, <uv.uv_check_t*>self._handle)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.h = h
self.running = 0
cdef inline stop(self):
cdef int err
if not self._is_alive():
self.running = 0
return
if self.running == 1:
err = uv.uv_check_stop(<uv.uv_check_t*>self._handle)
self.running = 0
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef inline start(self):
cdef int err
self._ensure_alive()
if self.running == 0:
err = uv.uv_check_start(<uv.uv_check_t*>self._handle,
cb_check_callback)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
self.running = 1
@staticmethod
cdef UVCheck new(Loop loop, Handle h):
cdef UVCheck handle
handle = UVCheck.__new__(UVCheck)
handle._init(loop, h)
return handle
cdef void cb_check_callback(
uv.uv_check_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVCheck callback") == 0:
return
cdef:
UVCheck check = <UVCheck> handle.data
Handle h = check.h
try:
h._run()
except BaseException as ex:
check._error(ex, False)

View File

@ -0,0 +1,12 @@
cdef class UVFSEvent(UVHandle):
cdef:
object callback
bint running
cdef _init(self, Loop loop, object callback, object context)
cdef _close(self)
cdef start(self, char* path, int flags)
cdef stop(self)
@staticmethod
cdef UVFSEvent new(Loop loop, object callback, object context)

View File

@ -0,0 +1,116 @@
import enum
class FileSystemEvent(enum.IntEnum):
RENAME = uv.UV_RENAME
CHANGE = uv.UV_CHANGE
RENAME_CHANGE = RENAME | CHANGE
@cython.no_gc_clear
cdef class UVFSEvent(UVHandle):
cdef _init(self, Loop loop, object callback, object context):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(
sizeof(uv.uv_fs_event_t)
)
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_fs_event_init(
self._loop.uvloop, <uv.uv_fs_event_t*>self._handle
)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.running = 0
self.callback = callback
if context is None:
context = Context_CopyCurrent()
self.context = context
cdef start(self, char* path, int flags):
cdef int err
self._ensure_alive()
if self.running == 0:
err = uv.uv_fs_event_start(
<uv.uv_fs_event_t*>self._handle,
__uvfsevent_callback,
path,
flags,
)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
self.running = 1
cdef stop(self):
cdef int err
if not self._is_alive():
self.running = 0
return
if self.running == 1:
err = uv.uv_fs_event_stop(<uv.uv_fs_event_t*>self._handle)
self.running = 0
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef _close(self):
try:
self.stop()
finally:
UVHandle._close(<UVHandle>self)
def cancel(self):
self._close()
def cancelled(self):
return self.running == 0
@staticmethod
cdef UVFSEvent new(Loop loop, object callback, object context):
cdef UVFSEvent handle
handle = UVFSEvent.__new__(UVFSEvent)
handle._init(loop, callback, context)
return handle
cdef void __uvfsevent_callback(
uv.uv_fs_event_t* handle,
const char *filename,
int events,
int status,
) noexcept with gil:
if __ensure_handle_data(
<uv.uv_handle_t*>handle, "UVFSEvent callback"
) == 0:
return
cdef:
UVFSEvent fs_event = <UVFSEvent> handle.data
Handle h
try:
h = new_Handle(
fs_event._loop,
fs_event.callback,
(filename, FileSystemEvent(events)),
fs_event.context,
)
h._run()
except BaseException as ex:
fs_event._error(ex, False)

View File

@ -0,0 +1,48 @@
cdef class UVHandle:
cdef:
uv.uv_handle_t *_handle
Loop _loop
readonly _source_traceback
bint _closed
bint _inited
object context
# Added to enable current UDPTransport implementation,
# which doesn't use libuv handles.
bint _has_handle
# All "inline" methods are final
cdef inline _start_init(self, Loop loop)
cdef inline _abort_init(self)
cdef inline _finish_init(self)
cdef inline bint _is_alive(self)
cdef inline _ensure_alive(self)
cdef _error(self, exc, throw)
cdef _fatal_error(self, exc, throw, reason=?)
cdef _warn_unclosed(self)
cdef _free(self)
cdef _close(self)
cdef class UVSocketHandle(UVHandle):
cdef:
# Points to a Python file-object that should be closed
# when the transport is closing. Used by pipes. This
# should probably be refactored somehow.
object _fileobj
object __cached_socket
# All "inline" methods are final
cdef _fileno(self)
cdef _new_socket(self)
cdef inline _get_socket(self)
cdef inline _attach_fileobj(self, object file)
cdef _open(self, int sockfd)

View 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()

View File

@ -0,0 +1,14 @@
cdef class UVIdle(UVHandle):
cdef:
Handle h
bint running
# All "inline" methods are final
cdef _init(self, Loop loop, Handle h)
cdef inline stop(self)
cdef inline start(self)
@staticmethod
cdef UVIdle new(Loop loop, Handle h)

View File

@ -0,0 +1,72 @@
@cython.no_gc_clear
cdef class UVIdle(UVHandle):
cdef _init(self, Loop loop, Handle h):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_idle_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_idle_init(self._loop.uvloop, <uv.uv_idle_t*>self._handle)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.h = h
self.running = 0
cdef inline stop(self):
cdef int err
if not self._is_alive():
self.running = 0
return
if self.running == 1:
err = uv.uv_idle_stop(<uv.uv_idle_t*>self._handle)
self.running = 0
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef inline start(self):
cdef int err
self._ensure_alive()
if self.running == 0:
err = uv.uv_idle_start(<uv.uv_idle_t*>self._handle,
cb_idle_callback)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
self.running = 1
@staticmethod
cdef UVIdle new(Loop loop, Handle h):
cdef UVIdle handle
handle = UVIdle.__new__(UVIdle)
handle._init(loop, h)
return handle
cdef void cb_idle_callback(
uv.uv_idle_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVIdle callback") == 0:
return
cdef:
UVIdle idle = <UVIdle> handle.data
Handle h = idle.h
try:
h._run()
except BaseException as ex:
idle._error(ex, False)

View File

@ -0,0 +1,33 @@
cdef class UnixServer(UVStreamServer):
cdef bind(self, str path)
@staticmethod
cdef UnixServer new(Loop loop, object protocol_factory, Server server,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout)
cdef class UnixTransport(UVStream):
@staticmethod
cdef UnixTransport new(Loop loop, object protocol, Server server,
object waiter, object context)
cdef connect(self, char* addr)
cdef class ReadUnixTransport(UVStream):
@staticmethod
cdef ReadUnixTransport new(Loop loop, object protocol, Server server,
object waiter)
cdef class WriteUnixTransport(UVStream):
@staticmethod
cdef WriteUnixTransport new(Loop loop, object protocol, Server server,
object waiter)

View File

@ -0,0 +1,247 @@
cdef __pipe_init_uv_handle(UVStream handle, Loop loop):
cdef int err
handle._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_pipe_t))
if handle._handle is NULL:
handle._abort_init()
raise MemoryError()
# Initialize pipe handle with ipc=0.
# ipc=1 means that libuv will use recvmsg/sendmsg
# instead of recv/send.
err = uv.uv_pipe_init(handle._loop.uvloop,
<uv.uv_pipe_t*>handle._handle,
0)
# UV_HANDLE_READABLE allows calling uv_read_start() on this pipe
# even if it is O_WRONLY, see also #317, libuv/libuv#2058
handle._handle.flags |= uv.UV_INTERNAL_HANDLE_READABLE
if err < 0:
handle._abort_init()
raise convert_error(err)
handle._finish_init()
cdef __pipe_open(UVStream handle, int fd):
cdef int err
err = uv.uv_pipe_open(<uv.uv_pipe_t *>handle._handle,
<uv.uv_os_fd_t>fd)
if err < 0:
exc = convert_error(err)
raise exc
cdef __pipe_get_socket(UVSocketHandle handle):
fileno = handle._fileno()
return PseudoSocket(uv.AF_UNIX, uv.SOCK_STREAM, 0, fileno)
@cython.no_gc_clear
cdef class UnixServer(UVStreamServer):
@staticmethod
cdef UnixServer new(Loop loop, object protocol_factory, Server server,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout):
cdef UnixServer handle
handle = UnixServer.__new__(UnixServer)
handle._init(loop, protocol_factory, server, backlog,
ssl, ssl_handshake_timeout, ssl_shutdown_timeout)
__pipe_init_uv_handle(<UVStream>handle, loop)
return handle
cdef _new_socket(self):
return __pipe_get_socket(<UVSocketHandle>self)
cdef _open(self, int sockfd):
self._ensure_alive()
__pipe_open(<UVStream>self, sockfd)
self._mark_as_open()
cdef bind(self, str path):
cdef int err
self._ensure_alive()
err = uv.uv_pipe_bind(<uv.uv_pipe_t *>self._handle,
path.encode())
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
self._mark_as_open()
cdef UVStream _make_new_transport(self, object protocol, object waiter,
object context):
cdef UnixTransport tr
tr = UnixTransport.new(self._loop, protocol, self._server, waiter,
context)
return <UVStream>tr
cdef _close(self):
sock = self._fileobj
if sock is not None and sock in self._loop._unix_server_sockets:
path = sock.getsockname()
else:
path = None
UVStreamServer._close(self)
if path is not None:
prev_ino = self._loop._unix_server_sockets[sock]
del self._loop._unix_server_sockets[sock]
try:
if os_stat(path).st_ino == prev_ino:
os_unlink(path)
except FileNotFoundError:
pass
except OSError as err:
aio_logger.error('Unable to clean up listening UNIX socket '
'%r: %r', path, err)
@cython.no_gc_clear
cdef class UnixTransport(UVStream):
@staticmethod
cdef UnixTransport new(Loop loop, object protocol, Server server,
object waiter, object context):
cdef UnixTransport handle
handle = UnixTransport.__new__(UnixTransport)
handle._init(loop, protocol, server, waiter, context)
__pipe_init_uv_handle(<UVStream>handle, loop)
return handle
cdef _new_socket(self):
return __pipe_get_socket(<UVSocketHandle>self)
cdef _open(self, int sockfd):
__pipe_open(<UVStream>self, sockfd)
cdef connect(self, char* addr):
cdef _PipeConnectRequest req
req = _PipeConnectRequest(self._loop, self)
req.connect(addr)
@cython.no_gc_clear
cdef class ReadUnixTransport(UVStream):
@staticmethod
cdef ReadUnixTransport new(Loop loop, object protocol, Server server,
object waiter):
cdef ReadUnixTransport handle
handle = ReadUnixTransport.__new__(ReadUnixTransport)
# This is only used in connect_read_pipe() and subprocess_shell/exec()
# directly, we could simply copy the current context.
handle._init(loop, protocol, server, waiter, Context_CopyCurrent())
__pipe_init_uv_handle(<UVStream>handle, loop)
return handle
cdef _new_socket(self):
return __pipe_get_socket(<UVSocketHandle>self)
cdef _open(self, int sockfd):
__pipe_open(<UVStream>self, sockfd)
def get_write_buffer_limits(self):
raise NotImplementedError
def set_write_buffer_limits(self, high=None, low=None):
raise NotImplementedError
def get_write_buffer_size(self):
raise NotImplementedError
def write(self, data):
raise NotImplementedError
def writelines(self, list_of_data):
raise NotImplementedError
def write_eof(self):
raise NotImplementedError
def can_write_eof(self):
raise NotImplementedError
def abort(self):
raise NotImplementedError
@cython.no_gc_clear
cdef class WriteUnixTransport(UVStream):
@staticmethod
cdef WriteUnixTransport new(Loop loop, object protocol, Server server,
object waiter):
cdef WriteUnixTransport handle
handle = WriteUnixTransport.__new__(WriteUnixTransport)
# We listen for read events on write-end of the pipe. When
# the read-end is close, the uv_stream_t.read callback will
# receive an error -- we want to silence that error, and just
# close the transport.
handle._close_on_read_error()
# This is only used in connect_write_pipe() and subprocess_shell/exec()
# directly, we could simply copy the current context.
handle._init(loop, protocol, server, waiter, Context_CopyCurrent())
__pipe_init_uv_handle(<UVStream>handle, loop)
return handle
cdef _new_socket(self):
return __pipe_get_socket(<UVSocketHandle>self)
cdef _open(self, int sockfd):
__pipe_open(<UVStream>self, sockfd)
def pause_reading(self):
raise NotImplementedError
def resume_reading(self):
raise NotImplementedError
cdef class _PipeConnectRequest(UVRequest):
cdef:
UnixTransport transport
uv.uv_connect_t _req_data
def __cinit__(self, loop, transport):
self.request = <uv.uv_req_t*> &self._req_data
self.request.data = <void*>self
self.transport = transport
cdef connect(self, char* addr):
# uv_pipe_connect returns void
uv.uv_pipe_connect(<uv.uv_connect_t*>self.request,
<uv.uv_pipe_t*>self.transport._handle,
addr,
__pipe_connect_callback)
cdef void __pipe_connect_callback(
uv.uv_connect_t* req,
int status,
) noexcept with gil:
cdef:
_PipeConnectRequest wrapper
UnixTransport transport
wrapper = <_PipeConnectRequest> req.data
transport = wrapper.transport
if status < 0:
exc = convert_error(status)
else:
exc = None
try:
transport._on_connect(exc)
except BaseException as ex:
wrapper.transport._fatal_error(ex, False)
finally:
wrapper.on_done()

View File

@ -0,0 +1,25 @@
cdef class UVPoll(UVHandle):
cdef:
int fd
Handle reading_handle
Handle writing_handle
cdef _init(self, Loop loop, int fd)
cdef _close(self)
cdef inline _poll_start(self, int flags)
cdef inline _poll_stop(self)
cdef int is_active(self) noexcept
cdef is_reading(self)
cdef is_writing(self)
cdef start_reading(self, Handle callback)
cdef start_writing(self, Handle callback)
cdef stop_reading(self)
cdef stop_writing(self)
cdef stop(self)
@staticmethod
cdef UVPoll new(Loop loop, int fd)

View File

@ -0,0 +1,233 @@
@cython.no_gc_clear
cdef class UVPoll(UVHandle):
cdef _init(self, Loop loop, int fd):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_poll_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_poll_init(self._loop.uvloop,
<uv.uv_poll_t *>self._handle, fd)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.fd = fd
self.reading_handle = None
self.writing_handle = None
@staticmethod
cdef UVPoll new(Loop loop, int fd):
cdef UVPoll handle
handle = UVPoll.__new__(UVPoll)
handle._init(loop, fd)
return handle
cdef int is_active(self) noexcept:
return (self.reading_handle is not None or
self.writing_handle is not None)
cdef inline _poll_start(self, int flags):
cdef int err
self._ensure_alive()
err = uv.uv_poll_start(
<uv.uv_poll_t*>self._handle,
flags,
__on_uvpoll_event)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef inline _poll_stop(self):
cdef int err
if not self._is_alive():
return
err = uv.uv_poll_stop(<uv.uv_poll_t*>self._handle)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef:
int backend_id
system.epoll_event dummy_event
if system.PLATFORM_IS_LINUX:
# libuv doesn't remove the FD from epoll immediately
# after uv_poll_stop or uv_poll_close, causing hard
# to debug issue with dup-ed file descriptors causing
# CPU burn in epoll/epoll_ctl:
# https://github.com/MagicStack/uvloop/issues/61
#
# It's safe though to manually call epoll_ctl here,
# after calling uv_poll_stop.
backend_id = uv.uv_backend_fd(self._loop.uvloop)
if backend_id != -1:
memset(&dummy_event, 0, sizeof(dummy_event))
system.epoll_ctl(
backend_id,
system.EPOLL_CTL_DEL,
self.fd,
&dummy_event) # ignore errors
cdef is_reading(self):
return self._is_alive() and self.reading_handle is not None
cdef is_writing(self):
return self._is_alive() and self.writing_handle is not None
cdef start_reading(self, Handle callback):
cdef:
int mask = 0
if self.reading_handle is None:
# not reading right now, setup the handle
mask = uv.UV_READABLE
if self.writing_handle is not None:
# are we writing right now?
mask |= uv.UV_WRITABLE
self._poll_start(mask)
else:
self.reading_handle._cancel()
self.reading_handle = callback
cdef start_writing(self, Handle callback):
cdef:
int mask = 0
if self.writing_handle is None:
# not writing right now, setup the handle
mask = uv.UV_WRITABLE
if self.reading_handle is not None:
# are we reading right now?
mask |= uv.UV_READABLE
self._poll_start(mask)
else:
self.writing_handle._cancel()
self.writing_handle = callback
cdef stop_reading(self):
if self.reading_handle is None:
return False
self.reading_handle._cancel()
self.reading_handle = None
if self.writing_handle is None:
self.stop()
else:
self._poll_start(uv.UV_WRITABLE)
return True
cdef stop_writing(self):
if self.writing_handle is None:
return False
self.writing_handle._cancel()
self.writing_handle = None
if self.reading_handle is None:
self.stop()
else:
self._poll_start(uv.UV_READABLE)
return True
cdef stop(self):
if self.reading_handle is not None:
self.reading_handle._cancel()
self.reading_handle = None
if self.writing_handle is not None:
self.writing_handle._cancel()
self.writing_handle = None
self._poll_stop()
cdef _close(self):
if self.is_active():
self.stop()
UVHandle._close(<UVHandle>self)
cdef _fatal_error(self, exc, throw, reason=None):
try:
if self.reading_handle is not None:
try:
self.reading_handle._run()
except BaseException as ex:
self._loop._handle_exception(ex)
self.reading_handle = None
if self.writing_handle is not None:
try:
self.writing_handle._run()
except BaseException as ex:
self._loop._handle_exception(ex)
self.writing_handle = None
finally:
self._close()
cdef void __on_uvpoll_event(
uv.uv_poll_t* handle,
int status,
int events,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVPoll callback") == 0:
return
cdef:
UVPoll poll = <UVPoll> handle.data
if status < 0:
exc = convert_error(status)
poll._fatal_error(exc, False)
return
if ((events & (uv.UV_READABLE | uv.UV_DISCONNECT)) and
poll.reading_handle is not None):
try:
if UVLOOP_DEBUG:
poll._loop._poll_read_events_total += 1
poll.reading_handle._run()
except BaseException as ex:
if UVLOOP_DEBUG:
poll._loop._poll_read_cb_errors_total += 1
poll._error(ex, False)
# continue code execution
if ((events & (uv.UV_WRITABLE | uv.UV_DISCONNECT)) and
poll.writing_handle is not None):
try:
if UVLOOP_DEBUG:
poll._loop._poll_write_events_total += 1
poll.writing_handle._run()
except BaseException as ex:
if UVLOOP_DEBUG:
poll._loop._poll_write_cb_errors_total += 1
poll._error(ex, False)

View File

@ -0,0 +1,80 @@
cdef class UVProcess(UVHandle):
cdef:
object _returncode
object _pid
object _errpipe_read
object _errpipe_write
object _preexec_fn
bint _restore_signals
list _fds_to_close
# Attributes used to compose uv_process_options_t:
uv.uv_process_options_t options
uv.uv_stdio_container_t[3] iocnt
list __env
char **uv_opt_env
list __args
char **uv_opt_args
char *uv_opt_file
bytes __cwd
cdef _close_process_handle(self)
cdef _init(self, Loop loop, list args, dict env, cwd,
start_new_session,
_stdin, _stdout, _stderr, pass_fds,
debug_flags, preexec_fn, restore_signals)
cdef _after_fork(self)
cdef char** __to_cstring_array(self, list arr)
cdef _init_args(self, list args)
cdef _init_env(self, dict env)
cdef _init_files(self, _stdin, _stdout, _stderr)
cdef _init_options(self, list args, dict env, cwd, start_new_session,
_stdin, _stdout, _stderr, bint force_fork)
cdef _close_after_spawn(self, int fd)
cdef _on_exit(self, int64_t exit_status, int term_signal)
cdef _kill(self, int signum)
cdef class UVProcessTransport(UVProcess):
cdef:
list _exit_waiters
list _init_futs
bint _stdio_ready
list _pending_calls
object _protocol
bint _finished
WriteUnixTransport _stdin
ReadUnixTransport _stdout
ReadUnixTransport _stderr
object stdin_proto
object stdout_proto
object stderr_proto
cdef _file_redirect_stdio(self, int fd)
cdef _file_devnull(self)
cdef _file_inpipe(self)
cdef _file_outpipe(self)
cdef _check_proc(self)
cdef _pipe_connection_lost(self, int fd, exc)
cdef _pipe_data_received(self, int fd, data)
cdef _call_connection_made(self, waiter)
cdef _try_finish(self)
@staticmethod
cdef UVProcessTransport new(Loop loop, protocol, args, env, cwd,
start_new_session,
_stdin, _stdout, _stderr, pass_fds,
waiter,
debug_flags,
preexec_fn, restore_signals)

View File

@ -0,0 +1,792 @@
@cython.no_gc_clear
cdef class UVProcess(UVHandle):
"""Abstract class; wrapper over uv_process_t handle."""
def __cinit__(self):
self.uv_opt_env = NULL
self.uv_opt_args = NULL
self._returncode = None
self._pid = None
self._fds_to_close = list()
self._preexec_fn = None
self._restore_signals = True
self.context = Context_CopyCurrent()
cdef _close_process_handle(self):
# XXX: This is a workaround for a libuv bug:
# - https://github.com/libuv/libuv/issues/1933
# - https://github.com/libuv/libuv/pull/551
if self._handle is NULL:
return
self._handle.data = NULL
uv.uv_close(self._handle, __uv_close_process_handle_cb)
self._handle = NULL # close callback will free() the memory
cdef _init(self, Loop loop, list args, dict env,
cwd, start_new_session,
_stdin, _stdout, _stderr, # std* can be defined as macros in C
pass_fds, debug_flags, preexec_fn, restore_signals):
global __forking
global __forking_loop
global __forkHandler
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(
sizeof(uv.uv_process_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
# Too early to call _finish_init, but still a lot of work to do.
# Let's set handle.data to NULL, so in case something goes wrong,
# callbacks have a chance to avoid casting *something* into UVHandle.
self._handle.data = NULL
force_fork = False
if system.PLATFORM_IS_APPLE and not (
preexec_fn is None
and not pass_fds
):
# see _execute_child() in CPython/subprocess.py
force_fork = True
try:
self._init_options(args, env, cwd, start_new_session,
_stdin, _stdout, _stderr, force_fork)
restore_inheritable = set()
if pass_fds:
for fd in pass_fds:
if not os_get_inheritable(fd):
restore_inheritable.add(fd)
os_set_inheritable(fd, True)
except Exception:
self._abort_init()
raise
if __forking or loop.active_process_handler is not None:
# Our pthread_atfork handlers won't work correctly when
# another loop is forking in another thread (even though
# GIL should help us to avoid that.)
self._abort_init()
raise RuntimeError(
'Racing with another loop to spawn a process.')
self._errpipe_read, self._errpipe_write = os_pipe()
fds_to_close = self._fds_to_close
self._fds_to_close = None
fds_to_close.append(self._errpipe_read)
# add the write pipe last so we can close it early
fds_to_close.append(self._errpipe_write)
try:
os_set_inheritable(self._errpipe_write, True)
self._preexec_fn = preexec_fn
self._restore_signals = restore_signals
loop.active_process_handler = self
__forking = 1
__forking_loop = loop
system.setForkHandler(<system.OnForkHandler>&__get_fork_handler)
PyOS_BeforeFork()
err = uv.uv_spawn(loop.uvloop,
<uv.uv_process_t*>self._handle,
&self.options)
__forking = 0
__forking_loop = None
system.resetForkHandler()
loop.active_process_handler = None
PyOS_AfterFork_Parent()
if err < 0:
self._close_process_handle()
self._abort_init()
raise convert_error(err)
self._finish_init()
# close the write pipe early
os_close(fds_to_close.pop())
if preexec_fn is not None:
errpipe_data = bytearray()
while True:
# XXX: This is a blocking code that has to be
# rewritten (using loop.connect_read_pipe() or
# otherwise.)
part = os_read(self._errpipe_read, 50000)
errpipe_data += part
if not part or len(errpipe_data) > 50000:
break
finally:
while fds_to_close:
os_close(fds_to_close.pop())
for fd in restore_inheritable:
os_set_inheritable(fd, False)
# asyncio caches the PID in BaseSubprocessTransport,
# so that the transport knows what the PID was even
# after the process is finished.
self._pid = (<uv.uv_process_t*>self._handle).pid
# Track the process handle (create a strong ref to it)
# to guarantee that __dealloc__ doesn't happen in an
# uncontrolled fashion. We want to wait until the process
# exits and libuv calls __uvprocess_on_exit_callback,
# which will call `UVProcess._close()`, which will, in turn,
# untrack this handle.
self._loop._track_process(self)
if debug_flags & __PROCESS_DEBUG_SLEEP_AFTER_FORK:
time_sleep(1)
if preexec_fn is not None and errpipe_data:
# preexec_fn has raised an exception. The child
# process must be dead now.
try:
exc_name, exc_msg = errpipe_data.split(b':', 1)
exc_name = exc_name.decode()
exc_msg = exc_msg.decode()
except Exception:
self._close()
raise subprocess_SubprocessError(
'Bad exception data from child: {!r}'.format(
errpipe_data))
exc_cls = getattr(__builtins__, exc_name,
subprocess_SubprocessError)
exc = subprocess_SubprocessError(
'Exception occurred in preexec_fn.')
exc.__cause__ = exc_cls(exc_msg)
self._close()
raise exc
cdef _after_fork(self):
# See CPython/_posixsubprocess.c for details
cdef int err
if self._restore_signals:
_Py_RestoreSignals()
PyOS_AfterFork_Child()
err = uv.uv_loop_fork(self._loop.uvloop)
if err < 0:
raise convert_error(err)
if self._preexec_fn is not None:
try:
gc_disable()
self._preexec_fn()
except BaseException as ex:
try:
with open(self._errpipe_write, 'wb') as f:
f.write(str(ex.__class__.__name__).encode())
f.write(b':')
f.write(str(ex.args[0]).encode())
finally:
system._exit(255)
return
else:
os_close(self._errpipe_write)
else:
os_close(self._errpipe_write)
cdef _close_after_spawn(self, int fd):
if self._fds_to_close is None:
raise RuntimeError(
'UVProcess._close_after_spawn called after uv_spawn')
self._fds_to_close.append(fd)
def __dealloc__(self):
if self.uv_opt_env is not NULL:
PyMem_RawFree(self.uv_opt_env)
self.uv_opt_env = NULL
if self.uv_opt_args is not NULL:
PyMem_RawFree(self.uv_opt_args)
self.uv_opt_args = NULL
cdef char** __to_cstring_array(self, list arr):
cdef:
int i
ssize_t arr_len = len(arr)
bytes el
char **ret
ret = <char **>PyMem_RawMalloc((arr_len + 1) * sizeof(char *))
if ret is NULL:
raise MemoryError()
for i in range(arr_len):
el = arr[i]
# NB: PyBytes_AsString doesn't copy the data;
# we have to be careful when the "arr" is GCed,
# and it shouldn't be ever mutated.
ret[i] = PyBytes_AsString(el)
ret[arr_len] = NULL
return ret
cdef _init_options(self, list args, dict env, cwd, start_new_session,
_stdin, _stdout, _stderr, bint force_fork):
memset(&self.options, 0, sizeof(uv.uv_process_options_t))
self._init_env(env)
self.options.env = self.uv_opt_env
self._init_args(args)
self.options.file = self.uv_opt_file
self.options.args = self.uv_opt_args
if start_new_session:
self.options.flags |= uv.UV_PROCESS_DETACHED
if force_fork:
# This is a hack to work around the change in libuv 1.44:
# > macos: use posix_spawn instead of fork
# where Python subprocess options like preexec_fn are
# crippled. CPython only uses posix_spawn under a pretty
# strict list of conditions (see subprocess.py), and falls
# back to using fork() otherwise. We'd like to simulate such
# behavior with libuv, but unfortunately libuv doesn't
# provide explicit API to choose such implementation detail.
# Based on current (libuv 1.46) behavior, setting
# UV_PROCESS_SETUID or UV_PROCESS_SETGID would reliably make
# libuv fallback to use fork, so let's just use it for now.
self.options.flags |= uv.UV_PROCESS_SETUID
self.options.uid = uv.getuid()
if cwd is not None:
cwd = os_fspath(cwd)
if isinstance(cwd, str):
cwd = PyUnicode_EncodeFSDefault(cwd)
if not isinstance(cwd, bytes):
raise ValueError('cwd must be a str or bytes object')
self.__cwd = cwd
self.options.cwd = PyBytes_AsString(self.__cwd)
self.options.exit_cb = &__uvprocess_on_exit_callback
self._init_files(_stdin, _stdout, _stderr)
cdef _init_args(self, list args):
cdef:
bytes path
int an = len(args)
if an < 1:
raise ValueError('cannot spawn a process: args are empty')
self.__args = args.copy()
for i in range(an):
arg = os_fspath(args[i])
if isinstance(arg, str):
self.__args[i] = PyUnicode_EncodeFSDefault(arg)
elif not isinstance(arg, bytes):
raise TypeError('all args must be str or bytes')
path = self.__args[0]
self.uv_opt_file = PyBytes_AsString(path)
self.uv_opt_args = self.__to_cstring_array(self.__args)
cdef _init_env(self, dict env):
if env is not None:
self.__env = list()
for key in env:
val = env[key]
if isinstance(key, str):
key = PyUnicode_EncodeFSDefault(key)
elif not isinstance(key, bytes):
raise TypeError(
'all environment vars must be bytes or str')
if isinstance(val, str):
val = PyUnicode_EncodeFSDefault(val)
elif not isinstance(val, bytes):
raise TypeError(
'all environment values must be bytes or str')
self.__env.append(key + b'=' + val)
self.uv_opt_env = self.__to_cstring_array(self.__env)
else:
self.__env = None
cdef _init_files(self, _stdin, _stdout, _stderr):
self.options.stdio_count = 0
cdef _kill(self, int signum):
cdef int err
self._ensure_alive()
err = uv.uv_process_kill(<uv.uv_process_t*>self._handle, signum)
if err < 0:
raise convert_error(err)
cdef _on_exit(self, int64_t exit_status, int term_signal):
if term_signal:
# From Python docs:
# A negative value -N indicates that the child was
# terminated by signal N (POSIX only).
self._returncode = -term_signal
else:
self._returncode = exit_status
self._close()
cdef _close(self):
try:
if self._loop is not None:
self._loop._untrack_process(self)
finally:
UVHandle._close(self)
DEF _CALL_PIPE_DATA_RECEIVED = 0
DEF _CALL_PIPE_CONNECTION_LOST = 1
DEF _CALL_PROCESS_EXITED = 2
DEF _CALL_CONNECTION_LOST = 3
@cython.no_gc_clear
cdef class UVProcessTransport(UVProcess):
def __cinit__(self):
self._exit_waiters = []
self._protocol = None
self._init_futs = []
self._pending_calls = []
self._stdio_ready = 0
self._stdin = self._stdout = self._stderr = None
self.stdin_proto = self.stdout_proto = self.stderr_proto = None
self._finished = 0
cdef _on_exit(self, int64_t exit_status, int term_signal):
UVProcess._on_exit(self, exit_status, term_signal)
if self._stdio_ready:
self._loop.call_soon(self._protocol.process_exited,
context=self.context)
else:
self._pending_calls.append((_CALL_PROCESS_EXITED, None, None))
self._try_finish()
for waiter in self._exit_waiters:
if not waiter.cancelled():
waiter.set_result(self._returncode)
self._exit_waiters.clear()
self._close()
cdef _check_proc(self):
if not self._is_alive() or self._returncode is not None:
raise ProcessLookupError()
cdef _pipe_connection_lost(self, int fd, exc):
if self._stdio_ready:
self._loop.call_soon(self._protocol.pipe_connection_lost, fd, exc,
context=self.context)
self._try_finish()
else:
self._pending_calls.append((_CALL_PIPE_CONNECTION_LOST, fd, exc))
cdef _pipe_data_received(self, int fd, data):
if self._stdio_ready:
self._loop.call_soon(self._protocol.pipe_data_received, fd, data,
context=self.context)
else:
self._pending_calls.append((_CALL_PIPE_DATA_RECEIVED, fd, data))
cdef _file_redirect_stdio(self, int fd):
fd = os_dup(fd)
os_set_inheritable(fd, True)
self._close_after_spawn(fd)
return fd
cdef _file_devnull(self):
dn = os_open(os_devnull, os_O_RDWR)
os_set_inheritable(dn, True)
self._close_after_spawn(dn)
return dn
cdef _file_outpipe(self):
r, w = __socketpair()
os_set_inheritable(w, True)
self._close_after_spawn(w)
return r, w
cdef _file_inpipe(self):
r, w = __socketpair()
os_set_inheritable(r, True)
self._close_after_spawn(r)
return r, w
cdef _init_files(self, _stdin, _stdout, _stderr):
cdef uv.uv_stdio_container_t *iocnt
UVProcess._init_files(self, _stdin, _stdout, _stderr)
io = [None, None, None]
self.options.stdio_count = 3
self.options.stdio = self.iocnt
if _stdin is not None:
if _stdin == subprocess_PIPE:
r, w = self._file_inpipe()
io[0] = r
self.stdin_proto = WriteSubprocessPipeProto(self, 0)
waiter = self._loop._new_future()
self._stdin = WriteUnixTransport.new(
self._loop, self.stdin_proto, None, waiter)
self._init_futs.append(waiter)
self._stdin._open(w)
self._stdin._init_protocol()
elif _stdin == subprocess_DEVNULL:
io[0] = self._file_devnull()
elif _stdout == subprocess_STDOUT:
raise ValueError(
'subprocess.STDOUT is supported only by stderr parameter')
else:
io[0] = self._file_redirect_stdio(_stdin)
else:
io[0] = self._file_redirect_stdio(0)
if _stdout is not None:
if _stdout == subprocess_PIPE:
# We can't use UV_CREATE_PIPE here, since 'stderr' might be
# set to 'subprocess.STDOUT', and there is no way to
# emulate that functionality with libuv high-level
# streams API. Therefore, we create pipes for stdout and
# stderr manually.
r, w = self._file_outpipe()
io[1] = w
self.stdout_proto = ReadSubprocessPipeProto(self, 1)
waiter = self._loop._new_future()
self._stdout = ReadUnixTransport.new(
self._loop, self.stdout_proto, None, waiter)
self._init_futs.append(waiter)
self._stdout._open(r)
self._stdout._init_protocol()
elif _stdout == subprocess_DEVNULL:
io[1] = self._file_devnull()
elif _stdout == subprocess_STDOUT:
raise ValueError(
'subprocess.STDOUT is supported only by stderr parameter')
else:
io[1] = self._file_redirect_stdio(_stdout)
else:
io[1] = self._file_redirect_stdio(1)
if _stderr is not None:
if _stderr == subprocess_PIPE:
r, w = self._file_outpipe()
io[2] = w
self.stderr_proto = ReadSubprocessPipeProto(self, 2)
waiter = self._loop._new_future()
self._stderr = ReadUnixTransport.new(
self._loop, self.stderr_proto, None, waiter)
self._init_futs.append(waiter)
self._stderr._open(r)
self._stderr._init_protocol()
elif _stderr == subprocess_STDOUT:
if io[1] is None:
# shouldn't ever happen
raise RuntimeError('cannot apply subprocess.STDOUT')
io[2] = self._file_redirect_stdio(io[1])
elif _stderr == subprocess_DEVNULL:
io[2] = self._file_devnull()
else:
io[2] = self._file_redirect_stdio(_stderr)
else:
io[2] = self._file_redirect_stdio(2)
assert len(io) == 3
for idx in range(3):
iocnt = &self.iocnt[idx]
if io[idx] is not None:
iocnt.flags = uv.UV_INHERIT_FD
iocnt.data.fd = io[idx]
else:
iocnt.flags = uv.UV_IGNORE
cdef _call_connection_made(self, waiter):
try:
# we're always called in the right context, so just call the user's
self._protocol.connection_made(self)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
if waiter is not None and not waiter.cancelled():
waiter.set_exception(ex)
else:
raise
else:
if waiter is not None and not waiter.cancelled():
waiter.set_result(True)
self._stdio_ready = 1
if self._pending_calls:
pending_calls = self._pending_calls.copy()
self._pending_calls.clear()
for (type, fd, arg) in pending_calls:
if type == _CALL_PIPE_CONNECTION_LOST:
self._pipe_connection_lost(fd, arg)
elif type == _CALL_PIPE_DATA_RECEIVED:
self._pipe_data_received(fd, arg)
elif type == _CALL_PROCESS_EXITED:
self._loop.call_soon(self._protocol.process_exited)
elif type == _CALL_CONNECTION_LOST:
self._loop.call_soon(self._protocol.connection_lost, None)
cdef _try_finish(self):
if self._returncode is None or self._finished:
return
if ((self.stdin_proto is None or self.stdin_proto.disconnected) and
(self.stdout_proto is None or
self.stdout_proto.disconnected) and
(self.stderr_proto is None or
self.stderr_proto.disconnected)):
self._finished = 1
if self._stdio_ready:
# copy self.context for simplicity
self._loop.call_soon(self._protocol.connection_lost, None,
context=self.context)
else:
self._pending_calls.append((_CALL_CONNECTION_LOST, None, None))
def __stdio_inited(self, waiter, stdio_fut):
exc = stdio_fut.exception()
if exc is not None:
if waiter is None:
raise exc
else:
waiter.set_exception(exc)
else:
self._loop._call_soon_handle(
new_MethodHandle1(self._loop,
"UVProcessTransport._call_connection_made",
<method1_t>self._call_connection_made,
None, # means to copy the current context
self, waiter))
@staticmethod
cdef UVProcessTransport new(Loop loop, protocol, args, env,
cwd, start_new_session,
_stdin, _stdout, _stderr, pass_fds,
waiter,
debug_flags,
preexec_fn,
restore_signals):
cdef UVProcessTransport handle
handle = UVProcessTransport.__new__(UVProcessTransport)
handle._protocol = protocol
handle._init(loop, args, env, cwd, start_new_session,
__process_convert_fileno(_stdin),
__process_convert_fileno(_stdout),
__process_convert_fileno(_stderr),
pass_fds,
debug_flags,
preexec_fn,
restore_signals)
if handle._init_futs:
handle._stdio_ready = 0
init_fut = aio_gather(*handle._init_futs)
# add_done_callback will copy the current context and run the
# callback within the context
init_fut.add_done_callback(
ft_partial(handle.__stdio_inited, waiter))
else:
handle._stdio_ready = 1
loop._call_soon_handle(
new_MethodHandle1(loop,
"UVProcessTransport._call_connection_made",
<method1_t>handle._call_connection_made,
None, # means to copy the current context
handle, waiter))
return handle
def get_protocol(self):
return self._protocol
def set_protocol(self, protocol):
self._protocol = protocol
def get_pid(self):
return self._pid
def get_returncode(self):
return self._returncode
def get_pipe_transport(self, fd):
if fd == 0:
return self._stdin
elif fd == 1:
return self._stdout
elif fd == 2:
return self._stderr
def terminate(self):
self._check_proc()
self._kill(uv.SIGTERM)
def kill(self):
self._check_proc()
self._kill(uv.SIGKILL)
def send_signal(self, int signal):
self._check_proc()
self._kill(signal)
def is_closing(self):
return self._closed
def close(self):
if self._returncode is None:
self._kill(uv.SIGKILL)
if self._stdin is not None:
self._stdin.close()
if self._stdout is not None:
self._stdout.close()
if self._stderr is not None:
self._stderr.close()
if self._returncode is not None:
# The process is dead, just close the UV handle.
#
# (If "self._returncode is None", the process should have been
# killed already and we're just waiting for a SIGCHLD; after
# which the transport will be GC'ed and the uvhandle will be
# closed in UVHandle.__dealloc__.)
self._close()
def get_extra_info(self, name, default=None):
return default
def _wait(self):
fut = self._loop._new_future()
if self._returncode is not None:
fut.set_result(self._returncode)
return fut
self._exit_waiters.append(fut)
return fut
class WriteSubprocessPipeProto(aio_BaseProtocol):
def __init__(self, proc, fd):
if UVLOOP_DEBUG:
if type(proc) is not UVProcessTransport:
raise TypeError
if not isinstance(fd, int):
raise TypeError
self.proc = proc
self.fd = fd
self.pipe = None
self.disconnected = False
def connection_made(self, transport):
self.pipe = transport
def __repr__(self):
return ('<%s fd=%s pipe=%r>'
% (self.__class__.__name__, self.fd, self.pipe))
def connection_lost(self, exc):
self.disconnected = True
(<UVProcessTransport>self.proc)._pipe_connection_lost(self.fd, exc)
self.proc = None
def pause_writing(self):
(<UVProcessTransport>self.proc)._protocol.pause_writing()
def resume_writing(self):
(<UVProcessTransport>self.proc)._protocol.resume_writing()
class ReadSubprocessPipeProto(WriteSubprocessPipeProto,
aio_Protocol):
def data_received(self, data):
(<UVProcessTransport>self.proc)._pipe_data_received(self.fd, data)
cdef __process_convert_fileno(object obj):
if obj is None or isinstance(obj, int):
return obj
fileno = obj.fileno()
if not isinstance(fileno, int):
raise TypeError(
'{!r}.fileno() returned non-integer'.format(obj))
return fileno
cdef void __uvprocess_on_exit_callback(
uv.uv_process_t *handle,
int64_t exit_status,
int term_signal,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle,
"UVProcess exit callback") == 0:
return
cdef UVProcess proc = <UVProcess> handle.data
try:
proc._on_exit(exit_status, term_signal)
except BaseException as ex:
proc._error(ex, False)
cdef __socketpair():
cdef:
int fds[2]
int err
err = system.socketpair(uv.AF_UNIX, uv.SOCK_STREAM, 0, fds)
if err:
exc = convert_error(-err)
raise exc
os_set_inheritable(fds[0], False)
os_set_inheritable(fds[1], False)
return fds[0], fds[1]
cdef void __uv_close_process_handle_cb(
uv.uv_handle_t* handle
) noexcept with gil:
PyMem_RawFree(handle)

View File

@ -0,0 +1,50 @@
cdef class UVStream(UVBaseTransport):
cdef:
uv.uv_shutdown_t _shutdown_req
bint __shutting_down
bint __reading
bint __read_error_close
bint __buffered
object _protocol_get_buffer
object _protocol_buffer_updated
bint _eof
list _buffer
size_t _buffer_size
Py_buffer _read_pybuf
bint _read_pybuf_acquired
# All "inline" methods are final
cdef inline _init(self, Loop loop, object protocol, Server server,
object waiter, object context)
cdef inline _shutdown(self)
cdef inline _accept(self, UVStream server)
cdef inline _close_on_read_error(self)
cdef inline __reading_started(self)
cdef inline __reading_stopped(self)
# The user API write() and writelines() firstly call _buffer_write() to
# buffer up user data chunks, potentially multiple times in writelines(),
# and then call _initiate_write() to start writing either immediately or in
# the next iteration (loop._queue_write()).
cdef inline _buffer_write(self, object data)
cdef inline _initiate_write(self)
# _exec_write() is the method that does the actual send, and _try_write()
# is a fast-path used in _exec_write() to send a single chunk.
cdef inline _exec_write(self)
cdef inline _try_write(self, object data)
cdef _close(self)
cdef inline _on_accept(self)
cdef inline _on_eof(self)
cdef inline _on_write(self)
cdef inline _on_connect(self, object exc)

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,26 @@
cdef class UVStreamServer(UVSocketHandle):
cdef:
int backlog
object ssl
object ssl_handshake_timeout
object ssl_shutdown_timeout
object protocol_factory
bint opened
Server _server
# All "inline" methods are final
cdef inline _init(self, Loop loop, object protocol_factory,
Server server,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout)
cdef inline _mark_as_open(self)
cdef inline listen(self)
cdef inline _on_listen(self)
cdef UVStream _make_new_transport(self, object protocol, object waiter,
object context)

View File

@ -0,0 +1,150 @@
@cython.no_gc_clear
cdef class UVStreamServer(UVSocketHandle):
def __cinit__(self):
self.opened = 0
self._server = None
self.ssl = None
self.ssl_handshake_timeout = None
self.ssl_shutdown_timeout = None
self.protocol_factory = None
cdef inline _init(self, Loop loop, object protocol_factory,
Server server,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout):
if not isinstance(backlog, int):
# Don't allow floats
raise TypeError('integer argument expected, got {}'.format(
type(backlog).__name__))
if ssl is not None:
if not isinstance(ssl, ssl_SSLContext):
raise TypeError(
'ssl is expected to be None or an instance of '
'ssl.SSLContext, got {!r}'.format(ssl))
else:
if ssl_handshake_timeout is not None:
raise ValueError(
'ssl_handshake_timeout is only meaningful with ssl')
if ssl_shutdown_timeout is not None:
raise ValueError(
'ssl_shutdown_timeout is only meaningful with ssl')
self.backlog = backlog
self.ssl = ssl
self.ssl_handshake_timeout = ssl_handshake_timeout
self.ssl_shutdown_timeout = ssl_shutdown_timeout
self._start_init(loop)
self.protocol_factory = protocol_factory
self._server = server
cdef inline listen(self):
cdef int err
self._ensure_alive()
if self.protocol_factory is None:
raise RuntimeError('unable to listen(); no protocol_factory')
if self.opened != 1:
raise RuntimeError('unopened TCPServer')
self.context = Context_CopyCurrent()
err = uv.uv_listen(<uv.uv_stream_t*> self._handle,
self.backlog,
__uv_streamserver_on_listen)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef inline _on_listen(self):
cdef UVStream client
protocol = run_in_context(self.context, self.protocol_factory)
if self.ssl is None:
client = self._make_new_transport(protocol, None, self.context)
else:
waiter = self._loop._new_future()
ssl_protocol = SSLProtocol(
self._loop, protocol, self.ssl,
waiter,
server_side=True,
server_hostname=None,
ssl_handshake_timeout=self.ssl_handshake_timeout,
ssl_shutdown_timeout=self.ssl_shutdown_timeout)
client = self._make_new_transport(ssl_protocol, None, self.context)
waiter.add_done_callback(
ft_partial(self.__on_ssl_connected, client))
client._accept(<UVStream>self)
cdef _fatal_error(self, exc, throw, reason=None):
# Overload UVHandle._fatal_error
self._close()
if not isinstance(exc, OSError):
if throw or self._loop is None:
raise exc
msg = f'Fatal error on server {self.__class__.__name__}'
if reason is not None:
msg = f'{msg} ({reason})'
self._loop.call_exception_handler({
'message': msg,
'exception': exc,
})
cdef inline _mark_as_open(self):
self.opened = 1
cdef UVStream _make_new_transport(self, object protocol, object waiter,
object context):
raise NotImplementedError
def __on_ssl_connected(self, transport, fut):
exc = fut.exception()
if exc is not None:
transport._force_close(exc)
cdef void __uv_streamserver_on_listen(
uv.uv_stream_t* handle,
int status,
) noexcept with gil:
# callback for uv_listen
if __ensure_handle_data(<uv.uv_handle_t*>handle,
"UVStream listen callback") == 0:
return
cdef:
UVStreamServer stream = <UVStreamServer> handle.data
if status < 0:
if UVLOOP_DEBUG:
stream._loop._debug_stream_listen_errors_total += 1
exc = convert_error(status)
stream._fatal_error(
exc, False, "error status in uv_stream_t.listen callback")
return
try:
stream._on_listen()
except BaseException as exc:
stream._error(exc, False)

View File

@ -0,0 +1,26 @@
cdef class TCPServer(UVStreamServer):
cdef bind(self, system.sockaddr* addr, unsigned int flags=*)
@staticmethod
cdef TCPServer new(Loop loop, object protocol_factory, Server server,
unsigned int flags,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout)
cdef class TCPTransport(UVStream):
cdef:
bint __peername_set
bint __sockname_set
system.sockaddr_storage __peername
system.sockaddr_storage __sockname
cdef bind(self, system.sockaddr* addr, unsigned int flags=*)
cdef connect(self, system.sockaddr* addr)
cdef _set_nodelay(self)
@staticmethod
cdef TCPTransport new(Loop loop, object protocol, Server server,
object waiter, object context)

View File

@ -0,0 +1,228 @@
cdef __tcp_init_uv_handle(UVStream handle, Loop loop, unsigned int flags):
cdef int err
handle._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_tcp_t))
if handle._handle is NULL:
handle._abort_init()
raise MemoryError()
err = uv.uv_tcp_init_ex(handle._loop.uvloop,
<uv.uv_tcp_t*>handle._handle,
flags)
if err < 0:
handle._abort_init()
raise convert_error(err)
handle._finish_init()
cdef __tcp_bind(UVStream handle, system.sockaddr* addr, unsigned int flags):
cdef int err
err = uv.uv_tcp_bind(<uv.uv_tcp_t *>handle._handle,
addr, flags)
if err < 0:
exc = convert_error(err)
raise exc
cdef __tcp_open(UVStream handle, int sockfd):
cdef int err
err = uv.uv_tcp_open(<uv.uv_tcp_t *>handle._handle,
<uv.uv_os_sock_t>sockfd)
if err < 0:
exc = convert_error(err)
raise exc
cdef __tcp_get_socket(UVSocketHandle handle):
cdef:
int buf_len = sizeof(system.sockaddr_storage)
int fileno
int err
system.sockaddr_storage buf
fileno = handle._fileno()
err = uv.uv_tcp_getsockname(<uv.uv_tcp_t*>handle._handle,
<system.sockaddr*>&buf,
&buf_len)
if err < 0:
raise convert_error(err)
return PseudoSocket(buf.ss_family, uv.SOCK_STREAM, 0, fileno)
@cython.no_gc_clear
cdef class TCPServer(UVStreamServer):
@staticmethod
cdef TCPServer new(Loop loop, object protocol_factory, Server server,
unsigned int flags,
object backlog,
object ssl,
object ssl_handshake_timeout,
object ssl_shutdown_timeout):
cdef TCPServer handle
handle = TCPServer.__new__(TCPServer)
handle._init(loop, protocol_factory, server, backlog,
ssl, ssl_handshake_timeout, ssl_shutdown_timeout)
__tcp_init_uv_handle(<UVStream>handle, loop, flags)
return handle
cdef _new_socket(self):
return __tcp_get_socket(<UVSocketHandle>self)
cdef _open(self, int sockfd):
self._ensure_alive()
try:
__tcp_open(<UVStream>self, sockfd)
except Exception as exc:
self._fatal_error(exc, True)
else:
self._mark_as_open()
cdef bind(self, system.sockaddr* addr, unsigned int flags=0):
self._ensure_alive()
try:
__tcp_bind(<UVStream>self, addr, flags)
except Exception as exc:
self._fatal_error(exc, True)
else:
self._mark_as_open()
cdef UVStream _make_new_transport(self, object protocol, object waiter,
object context):
cdef TCPTransport tr
tr = TCPTransport.new(self._loop, protocol, self._server, waiter,
context)
return <UVStream>tr
@cython.no_gc_clear
cdef class TCPTransport(UVStream):
@staticmethod
cdef TCPTransport new(Loop loop, object protocol, Server server,
object waiter, object context):
cdef TCPTransport handle
handle = TCPTransport.__new__(TCPTransport)
handle._init(loop, protocol, server, waiter, context)
__tcp_init_uv_handle(<UVStream>handle, loop, uv.AF_UNSPEC)
handle.__peername_set = 0
handle.__sockname_set = 0
handle._set_nodelay()
return handle
cdef _set_nodelay(self):
cdef int err
self._ensure_alive()
err = uv.uv_tcp_nodelay(<uv.uv_tcp_t*>self._handle, 1)
if err < 0:
raise convert_error(err)
cdef _call_connection_made(self):
# asyncio saves peername & sockname when transports are instantiated,
# so that they're accessible even after the transport is closed.
# We are doing the same thing here, except that we create Python
# objects lazily, on request in get_extra_info()
cdef:
int err
int buf_len
buf_len = sizeof(system.sockaddr_storage)
err = uv.uv_tcp_getsockname(<uv.uv_tcp_t*>self._handle,
<system.sockaddr*>&self.__sockname,
&buf_len)
if err >= 0:
# Ignore errors, this is an optional thing.
# If something serious is going on, the transport
# will crash later (in roughly the same way how
# an asyncio transport would.)
self.__sockname_set = 1
buf_len = sizeof(system.sockaddr_storage)
err = uv.uv_tcp_getpeername(<uv.uv_tcp_t*>self._handle,
<system.sockaddr*>&self.__peername,
&buf_len)
if err >= 0:
# Same as few lines above -- we don't really care
# about error case here.
self.__peername_set = 1
UVBaseTransport._call_connection_made(self)
def get_extra_info(self, name, default=None):
if name == 'sockname':
if self.__sockname_set:
return __convert_sockaddr_to_pyaddr(
<system.sockaddr*>&self.__sockname)
elif name == 'peername':
if self.__peername_set:
return __convert_sockaddr_to_pyaddr(
<system.sockaddr*>&self.__peername)
return super().get_extra_info(name, default)
cdef _new_socket(self):
return __tcp_get_socket(<UVSocketHandle>self)
cdef bind(self, system.sockaddr* addr, unsigned int flags=0):
self._ensure_alive()
__tcp_bind(<UVStream>self, addr, flags)
cdef _open(self, int sockfd):
self._ensure_alive()
__tcp_open(<UVStream>self, sockfd)
cdef connect(self, system.sockaddr* addr):
cdef _TCPConnectRequest req
req = _TCPConnectRequest(self._loop, self)
req.connect(addr)
cdef class _TCPConnectRequest(UVRequest):
cdef:
TCPTransport transport
uv.uv_connect_t _req_data
def __cinit__(self, loop, transport):
self.request = <uv.uv_req_t*>&self._req_data
self.request.data = <void*>self
self.transport = transport
cdef connect(self, system.sockaddr* addr):
cdef int err
err = uv.uv_tcp_connect(<uv.uv_connect_t*>self.request,
<uv.uv_tcp_t*>self.transport._handle,
addr,
__tcp_connect_callback)
if err < 0:
exc = convert_error(err)
self.on_done()
raise exc
cdef void __tcp_connect_callback(
uv.uv_connect_t* req,
int status,
) noexcept with gil:
cdef:
_TCPConnectRequest wrapper
TCPTransport transport
wrapper = <_TCPConnectRequest> req.data
transport = wrapper.transport
if status < 0:
exc = convert_error(status)
else:
exc = None
try:
transport._on_connect(exc)
except BaseException as ex:
wrapper.transport._fatal_error(ex, False)
finally:
wrapper.on_done()

View File

@ -0,0 +1,18 @@
cdef class UVTimer(UVHandle):
cdef:
method_t callback
object ctx
bint running
uint64_t timeout
uint64_t start_t
cdef _init(self, Loop loop, method_t callback, object ctx,
uint64_t timeout)
cdef stop(self)
cdef start(self)
cdef get_when(self)
@staticmethod
cdef UVTimer new(Loop loop, method_t callback, object ctx,
uint64_t timeout)

View File

@ -0,0 +1,89 @@
@cython.no_gc_clear
cdef class UVTimer(UVHandle):
cdef _init(self, Loop loop, method_t callback, object ctx,
uint64_t timeout):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*> PyMem_RawMalloc(sizeof(uv.uv_timer_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_timer_init(self._loop.uvloop, <uv.uv_timer_t*>self._handle)
if err < 0:
self._abort_init()
raise convert_error(err)
self._finish_init()
self.callback = callback
self.ctx = ctx
self.running = 0
self.timeout = timeout
self.start_t = 0
cdef stop(self):
cdef int err
if not self._is_alive():
self.running = 0
return
if self.running == 1:
err = uv.uv_timer_stop(<uv.uv_timer_t*>self._handle)
self.running = 0
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
cdef start(self):
cdef int err
self._ensure_alive()
if self.running == 0:
# Update libuv internal time.
uv.uv_update_time(self._loop.uvloop) # void
self.start_t = uv.uv_now(self._loop.uvloop)
err = uv.uv_timer_start(<uv.uv_timer_t*>self._handle,
__uvtimer_callback,
self.timeout, 0)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
self.running = 1
cdef get_when(self):
return self.start_t + self.timeout
@staticmethod
cdef UVTimer new(Loop loop, method_t callback, object ctx,
uint64_t timeout):
cdef UVTimer handle
handle = UVTimer.__new__(UVTimer)
handle._init(loop, callback, ctx, timeout)
return handle
cdef void __uvtimer_callback(
uv.uv_timer_t* handle,
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle, "UVTimer callback") == 0:
return
cdef:
UVTimer timer = <UVTimer> handle.data
method_t cb = timer.callback
timer.running = 0
try:
cb(timer.ctx)
except BaseException as ex:
timer._error(ex, False)

View File

@ -0,0 +1,22 @@
cdef class UDPTransport(UVBaseTransport):
cdef:
bint __receiving
int _family
object _address
cdef _init(self, Loop loop, unsigned int family)
cdef _set_address(self, system.addrinfo *addr)
cdef _connect(self, system.sockaddr* addr, size_t addr_len)
cdef _bind(self, system.sockaddr* addr)
cdef open(self, int family, int sockfd)
cdef _set_broadcast(self, bint on)
cdef inline __receiving_started(self)
cdef inline __receiving_stopped(self)
cdef _send(self, object data, object addr)
cdef _on_receive(self, bytes data, object exc, object addr)
cdef _on_sent(self, object exc, object context=*)

View File

@ -0,0 +1,408 @@
@cython.no_gc_clear
@cython.freelist(DEFAULT_FREELIST_SIZE)
cdef class _UDPSendContext:
# used to hold additional write request information for uv_write
cdef:
uv.uv_udp_send_t req
uv.uv_buf_t uv_buf
Py_buffer py_buf
UDPTransport udp
bint closed
cdef close(self):
if self.closed:
return
self.closed = 1
PyBuffer_Release(&self.py_buf) # void
self.req.data = NULL
self.uv_buf.base = NULL
Py_DECREF(self)
self.udp = None
@staticmethod
cdef _UDPSendContext new(UDPTransport udp, object data):
cdef _UDPSendContext ctx
ctx = _UDPSendContext.__new__(_UDPSendContext)
ctx.udp = None
ctx.closed = 1
ctx.req.data = <void*> ctx
Py_INCREF(ctx)
PyObject_GetBuffer(data, &ctx.py_buf, PyBUF_SIMPLE)
ctx.uv_buf.base = <char*>ctx.py_buf.buf
ctx.uv_buf.len = ctx.py_buf.len
ctx.udp = udp
ctx.closed = 0
return ctx
def __dealloc__(self):
if UVLOOP_DEBUG:
if not self.closed:
raise RuntimeError(
'open _UDPSendContext is being deallocated')
self.udp = None
@cython.no_gc_clear
cdef class UDPTransport(UVBaseTransport):
def __cinit__(self):
self._family = uv.AF_UNSPEC
self.__receiving = 0
self._address = None
self.context = Context_CopyCurrent()
cdef _init(self, Loop loop, unsigned int family):
cdef int err
self._start_init(loop)
self._handle = <uv.uv_handle_t*>PyMem_RawMalloc(sizeof(uv.uv_udp_t))
if self._handle is NULL:
self._abort_init()
raise MemoryError()
err = uv.uv_udp_init_ex(loop.uvloop,
<uv.uv_udp_t*>self._handle,
family)
if err < 0:
self._abort_init()
raise convert_error(err)
if family in (uv.AF_INET, uv.AF_INET6):
self._family = family
self._finish_init()
cdef _set_address(self, system.addrinfo *addr):
self._address = __convert_sockaddr_to_pyaddr(addr.ai_addr)
cdef _connect(self, system.sockaddr* addr, size_t addr_len):
cdef int err
err = uv.uv_udp_connect(<uv.uv_udp_t*>self._handle, addr)
if err < 0:
exc = convert_error(err)
raise exc
cdef open(self, int family, int sockfd):
if family in (uv.AF_INET, uv.AF_INET6, uv.AF_UNIX):
self._family = family
else:
raise ValueError(
'cannot open a UDP handle, invalid family {}'.format(family))
cdef int err
err = uv.uv_udp_open(<uv.uv_udp_t*>self._handle,
<uv.uv_os_sock_t>sockfd)
if err < 0:
exc = convert_error(err)
raise exc
cdef _bind(self, system.sockaddr* addr):
cdef:
int err
int flags = 0
self._ensure_alive()
err = uv.uv_udp_bind(<uv.uv_udp_t*>self._handle, addr, flags)
if err < 0:
exc = convert_error(err)
raise exc
cdef _set_broadcast(self, bint on):
cdef int err
self._ensure_alive()
err = uv.uv_udp_set_broadcast(<uv.uv_udp_t*>self._handle, on)
if err < 0:
exc = convert_error(err)
raise exc
cdef size_t _get_write_buffer_size(self):
if self._handle is NULL:
return 0
return (<uv.uv_udp_t*>self._handle).send_queue_size
cdef bint _is_reading(self):
return self.__receiving
cdef _start_reading(self):
cdef int err
if self.__receiving:
return
self._ensure_alive()
err = uv.uv_udp_recv_start(<uv.uv_udp_t*>self._handle,
__loop_alloc_buffer,
__uv_udp_on_receive)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
else:
# UDPTransport must live until the read callback is called
self.__receiving_started()
cdef _stop_reading(self):
cdef int err
if not self.__receiving:
return
self._ensure_alive()
err = uv.uv_udp_recv_stop(<uv.uv_udp_t*>self._handle)
if err < 0:
exc = convert_error(err)
self._fatal_error(exc, True)
return
else:
self.__receiving_stopped()
cdef inline __receiving_started(self):
if self.__receiving:
return
self.__receiving = 1
Py_INCREF(self)
cdef inline __receiving_stopped(self):
if not self.__receiving:
return
self.__receiving = 0
Py_DECREF(self)
cdef _new_socket(self):
if self._family not in (uv.AF_INET, uv.AF_INET6, uv.AF_UNIX):
raise RuntimeError(
'UDPTransport.family is undefined; '
'cannot create python socket')
fileno = self._fileno()
return PseudoSocket(self._family, uv.SOCK_DGRAM, 0, fileno)
cdef _send(self, object data, object addr):
cdef:
_UDPSendContext ctx
system.sockaddr_storage saddr_st
system.sockaddr *saddr
Py_buffer try_pybuf
uv.uv_buf_t try_uvbuf
self._ensure_alive()
if self._family not in (uv.AF_INET, uv.AF_INET6, uv.AF_UNIX):
raise RuntimeError('UDPTransport.family is undefined; cannot send')
if addr is None:
saddr = NULL
else:
try:
__convert_pyaddr_to_sockaddr(self._family, addr,
<system.sockaddr*>&saddr_st)
except (ValueError, TypeError):
raise
except Exception:
raise ValueError(
f'{addr!r}: socket family mismatch or '
f'a DNS lookup is required')
saddr = <system.sockaddr*>(&saddr_st)
if self._get_write_buffer_size() == 0:
PyObject_GetBuffer(data, &try_pybuf, PyBUF_SIMPLE)
try_uvbuf.base = <char*>try_pybuf.buf
try_uvbuf.len = try_pybuf.len
err = uv.uv_udp_try_send(<uv.uv_udp_t*>self._handle,
&try_uvbuf,
1,
saddr)
PyBuffer_Release(&try_pybuf)
else:
err = uv.UV_EAGAIN
if err == uv.UV_EAGAIN:
ctx = _UDPSendContext.new(self, data)
err = uv.uv_udp_send(&ctx.req,
<uv.uv_udp_t*>self._handle,
&ctx.uv_buf,
1,
saddr,
__uv_udp_on_send)
if err < 0:
ctx.close()
exc = convert_error(err)
if isinstance(exc, OSError):
run_in_context1(self.context.copy(), self._protocol.error_received, exc)
else:
self._fatal_error(exc, True)
else:
self._maybe_pause_protocol()
else:
self._on_sent(convert_error(err) if err < 0 else None, self.context.copy())
cdef _on_receive(self, bytes data, object exc, object addr):
if exc is None:
run_in_context2(
self.context, self._protocol.datagram_received, data, addr,
)
else:
run_in_context1(self.context, self._protocol.error_received, exc)
cdef _on_sent(self, object exc, object context=None):
if exc is not None:
if isinstance(exc, OSError):
if context is None:
context = self.context
run_in_context1(context, self._protocol.error_received, exc)
else:
self._fatal_error(
exc, False, 'Fatal write error on datagram transport')
self._maybe_resume_protocol()
if not self._get_write_buffer_size():
if self._closing:
self._schedule_call_connection_lost(None)
# === Public API ===
def sendto(self, data, addr=None):
if not data:
# Replicating asyncio logic here.
return
if self._address:
if addr not in (None, self._address):
# Replicating asyncio logic here.
raise ValueError(
'Invalid address: must be None or %s' % (self._address,))
# Instead of setting addr to self._address below like what asyncio
# does, we depend on previous uv_udp_connect() to set the address
addr = None
if self._conn_lost:
# Replicating asyncio logic here.
if self._conn_lost >= LOG_THRESHOLD_FOR_CONNLOST_WRITES:
aio_logger.warning('socket.send() raised exception.')
self._conn_lost += 1
return
self._send(data, addr)
cdef void __uv_udp_on_receive(
uv.uv_udp_t* handle,
ssize_t nread,
const uv.uv_buf_t* buf,
const system.sockaddr* addr,
unsigned flags
) noexcept with gil:
if __ensure_handle_data(<uv.uv_handle_t*>handle,
"UDPTransport receive callback") == 0:
return
cdef:
UDPTransport udp = <UDPTransport>handle.data
Loop loop = udp._loop
bytes data
object pyaddr
# 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 udp._closed:
# The handle was closed, there is no reason to
# do any work now.
udp.__receiving_stopped() # Just in case.
return
if addr is NULL and nread == 0:
# From libuv docs:
# addr: struct sockaddr* containing the address
# of the sender. Can be NULL. Valid for the duration
# of the callback only.
# [...]
# The receive callback will be called with
# nread == 0 and addr == NULL when there is
# nothing to read, and with nread == 0 and
# addr != NULL when an empty UDP packet is
# received.
return
if addr is NULL:
pyaddr = None
elif addr.sa_family == uv.AF_UNSPEC:
# https://github.com/MagicStack/uvloop/issues/304
if system.PLATFORM_IS_LINUX:
pyaddr = None
else:
pyaddr = ''
else:
try:
pyaddr = __convert_sockaddr_to_pyaddr(addr)
except BaseException as exc:
udp._error(exc, False)
return
if nread < 0:
exc = convert_error(nread)
udp._on_receive(None, exc, pyaddr)
return
if nread == 0:
data = b''
else:
data = loop._recv_buffer[:nread]
try:
udp._on_receive(data, None, pyaddr)
except BaseException as exc:
udp._error(exc, False)
cdef void __uv_udp_on_send(
uv.uv_udp_send_t* req,
int status,
) noexcept with gil:
if req.data is NULL:
# Shouldn't happen as:
# - _UDPSendContext does an extra INCREF in its 'init()'
# - _UDPSendContext holds a ref to the relevant UDPTransport
aio_logger.error(
'UVStream.write callback called with NULL req.data, status=%r',
status)
return
cdef:
_UDPSendContext ctx = <_UDPSendContext> req.data
UDPTransport udp = <UDPTransport>ctx.udp
ctx.close()
if status < 0:
exc = convert_error(status)
print(exc)
else:
exc = None
try:
udp._on_sent(exc)
except BaseException as exc:
udp._error(exc, False)

View File

@ -0,0 +1,23 @@
# flake8: noqa
# These have to be synced with the stdlib.pxi
import asyncio
import collections
import concurrent.futures
import errno
import functools
import gc
import inspect
import itertools
import os
import signal
import socket
import subprocess
import ssl
import stat
import sys
import threading
import traceback
import time
import warnings
import weakref

View File

@ -0,0 +1,33 @@
cdef enum:
UV_STREAM_RECV_BUF_SIZE = 256000 # 250kb
FLOW_CONTROL_HIGH_WATER = 64 # KiB
FLOW_CONTROL_HIGH_WATER_SSL_READ = 256 # KiB
FLOW_CONTROL_HIGH_WATER_SSL_WRITE = 512 # KiB
DEFAULT_FREELIST_SIZE = 250
DNS_PYADDR_TO_SOCKADDR_CACHE_SIZE = 2048
DEBUG_STACK_DEPTH = 10
__PROCESS_DEBUG_SLEEP_AFTER_FORK = 1
LOG_THRESHOLD_FOR_CONNLOST_WRITES = 5
SSL_READ_MAX_SIZE = 256 * 1024
cdef extern from *:
'''
// Number of seconds to wait for SSL handshake to complete
// The default timeout matches that of Nginx.
#define SSL_HANDSHAKE_TIMEOUT 60.0
// Number of seconds to wait for SSL shutdown to complete
// The default timeout mimics lingering_time
#define SSL_SHUTDOWN_TIMEOUT 30.0
'''
const float SSL_HANDSHAKE_TIMEOUT
const float SSL_SHUTDOWN_TIMEOUT

View File

@ -0,0 +1,3 @@
cdef extern from "includes/debug.h":
cdef int UVLOOP_DEBUG

View File

@ -0,0 +1,23 @@
# flake8: noqa
cdef inline add_flowcontrol_defaults(high, low, int kb):
cdef int h, l
if high is None:
if low is None:
h = kb * 1024
else:
l = low
h = 4 * l
else:
h = high
if low is None:
l = h // 4
else:
l = low
if not h >= l >= 0:
raise ValueError('high (%r) must be >= low (%r) must be >= 0' %
(h, l))
return h, l

View File

@ -0,0 +1,31 @@
cdef extern from "Python.h":
int PY_VERSION_HEX
unicode PyUnicode_FromString(const char *)
void* PyMem_RawMalloc(size_t n) nogil
void* PyMem_RawRealloc(void *p, size_t n) nogil
void* PyMem_RawCalloc(size_t nelem, size_t elsize) nogil
void PyMem_RawFree(void *p) nogil
object PyUnicode_EncodeFSDefault(object)
void PyErr_SetInterrupt() nogil
object PyMemoryView_FromMemory(char *mem, ssize_t size, int flags)
object PyMemoryView_FromObject(object obj)
int PyMemoryView_Check(object obj)
cdef enum:
PyBUF_WRITE
cdef extern from "includes/compat.h":
object Context_CopyCurrent()
int Context_Enter(object) except -1
int Context_Exit(object) except -1
void PyOS_BeforeFork()
void PyOS_AfterFork_Parent()
void PyOS_AfterFork_Child()
void _Py_RestoreSignals()

View File

@ -0,0 +1,176 @@
# flake8: noqa
import asyncio, asyncio.log, asyncio.base_events, \
asyncio.sslproto, asyncio.coroutines, \
asyncio.futures, asyncio.transports
import collections.abc
import concurrent.futures
import errno
import functools
import gc
import inspect
import itertools
import os
import signal
import socket
import subprocess
import ssl
import stat
import sys
import threading
import traceback
import time
import warnings
import weakref
cdef aio_get_event_loop = asyncio.get_event_loop
cdef aio_CancelledError = asyncio.CancelledError
cdef aio_InvalidStateError = asyncio.InvalidStateError
cdef aio_TimeoutError = asyncio.TimeoutError
cdef aio_Future = asyncio.Future
cdef aio_Task = asyncio.Task
cdef aio_ensure_future = asyncio.ensure_future
cdef aio_gather = asyncio.gather
cdef aio_wait = asyncio.wait
cdef aio_wrap_future = asyncio.wrap_future
cdef aio_logger = asyncio.log.logger
cdef aio_iscoroutine = asyncio.iscoroutine
cdef aio_iscoroutinefunction = asyncio.iscoroutinefunction
cdef aio_BaseProtocol = asyncio.BaseProtocol
cdef aio_Protocol = asyncio.Protocol
cdef aio_isfuture = getattr(asyncio, 'isfuture', None)
cdef aio_get_running_loop = getattr(asyncio, '_get_running_loop', None)
cdef aio_set_running_loop = getattr(asyncio, '_set_running_loop', None)
cdef aio_debug_wrapper = getattr(asyncio.coroutines, 'debug_wrapper', None)
cdef aio_AbstractChildWatcher = asyncio.AbstractChildWatcher
cdef aio_Transport = asyncio.Transport
cdef aio_FlowControlMixin = asyncio.transports._FlowControlMixin
cdef col_deque = collections.deque
cdef col_Iterable = collections.abc.Iterable
cdef col_Counter = collections.Counter
cdef col_OrderedDict = collections.OrderedDict
cdef cc_ThreadPoolExecutor = concurrent.futures.ThreadPoolExecutor
cdef cc_Future = concurrent.futures.Future
cdef errno_EBADF = errno.EBADF
cdef errno_EINVAL = errno.EINVAL
cdef ft_partial = functools.partial
cdef gc_disable = gc.disable
cdef iter_chain = itertools.chain
cdef inspect_isgenerator = inspect.isgenerator
cdef int has_IPV6_V6ONLY = hasattr(socket, 'IPV6_V6ONLY')
cdef int IPV6_V6ONLY = getattr(socket, 'IPV6_V6ONLY', -1)
cdef int has_SO_REUSEPORT = hasattr(socket, 'SO_REUSEPORT')
cdef int SO_REUSEPORT = getattr(socket, 'SO_REUSEPORT', 0)
cdef int SO_BROADCAST = getattr(socket, 'SO_BROADCAST')
cdef int SOCK_NONBLOCK = getattr(socket, 'SOCK_NONBLOCK', -1)
cdef int socket_AI_CANONNAME = getattr(socket, 'AI_CANONNAME')
cdef socket_gaierror = socket.gaierror
cdef socket_error = socket.error
cdef socket_timeout = socket.timeout
cdef socket_socket = socket.socket
cdef socket_socketpair = socket.socketpair
cdef socket_getservbyname = socket.getservbyname
cdef socket_AddressFamily = socket.AddressFamily
cdef socket_SocketKind = socket.SocketKind
cdef int socket_EAI_ADDRFAMILY = getattr(socket, 'EAI_ADDRFAMILY', -1)
cdef int socket_EAI_AGAIN = getattr(socket, 'EAI_AGAIN', -1)
cdef int socket_EAI_BADFLAGS = getattr(socket, 'EAI_BADFLAGS', -1)
cdef int socket_EAI_BADHINTS = getattr(socket, 'EAI_BADHINTS', -1)
cdef int socket_EAI_CANCELED = getattr(socket, 'EAI_CANCELED', -1)
cdef int socket_EAI_FAIL = getattr(socket, 'EAI_FAIL', -1)
cdef int socket_EAI_FAMILY = getattr(socket, 'EAI_FAMILY', -1)
cdef int socket_EAI_MEMORY = getattr(socket, 'EAI_MEMORY', -1)
cdef int socket_EAI_NODATA = getattr(socket, 'EAI_NODATA', -1)
cdef int socket_EAI_NONAME = getattr(socket, 'EAI_NONAME', -1)
cdef int socket_EAI_OVERFLOW = getattr(socket, 'EAI_OVERFLOW', -1)
cdef int socket_EAI_PROTOCOL = getattr(socket, 'EAI_PROTOCOL', -1)
cdef int socket_EAI_SERVICE = getattr(socket, 'EAI_SERVICE', -1)
cdef int socket_EAI_SOCKTYPE = getattr(socket, 'EAI_SOCKTYPE', -1)
cdef str os_name = os.name
cdef os_environ = os.environ
cdef os_dup = os.dup
cdef os_set_inheritable = os.set_inheritable
cdef os_get_inheritable = os.get_inheritable
cdef os_close = os.close
cdef os_open = os.open
cdef os_devnull = os.devnull
cdef os_O_RDWR = os.O_RDWR
cdef os_pipe = os.pipe
cdef os_read = os.read
cdef os_remove = os.remove
cdef os_stat = os.stat
cdef os_unlink = os.unlink
cdef os_fspath = os.fspath
cdef stat_S_ISSOCK = stat.S_ISSOCK
cdef sys_ignore_environment = sys.flags.ignore_environment
cdef sys_dev_mode = sys.flags.dev_mode
cdef sys_exc_info = sys.exc_info
cdef sys_set_coroutine_wrapper = getattr(sys, 'set_coroutine_wrapper', None)
cdef sys_get_coroutine_wrapper = getattr(sys, 'get_coroutine_wrapper', None)
cdef sys_getframe = sys._getframe
cdef sys_version_info = sys.version_info
cdef sys_getfilesystemencoding = sys.getfilesystemencoding
cdef str sys_platform = sys.platform
cdef ssl_SSLContext = ssl.SSLContext
cdef ssl_MemoryBIO = ssl.MemoryBIO
cdef ssl_create_default_context = ssl.create_default_context
cdef ssl_SSLError = ssl.SSLError
cdef ssl_SSLAgainErrors = (ssl.SSLWantReadError, ssl.SSLSyscallError)
cdef ssl_SSLZeroReturnError = ssl.SSLZeroReturnError
cdef ssl_CertificateError = ssl.CertificateError
cdef int ssl_SSL_ERROR_WANT_READ = ssl.SSL_ERROR_WANT_READ
cdef int ssl_SSL_ERROR_WANT_WRITE = ssl.SSL_ERROR_WANT_WRITE
cdef int ssl_SSL_ERROR_SYSCALL = ssl.SSL_ERROR_SYSCALL
cdef threading_Thread = threading.Thread
cdef threading_main_thread = threading.main_thread
cdef int subprocess_PIPE = subprocess.PIPE
cdef int subprocess_STDOUT = subprocess.STDOUT
cdef int subprocess_DEVNULL = subprocess.DEVNULL
cdef subprocess_SubprocessError = subprocess.SubprocessError
cdef int signal_NSIG = signal.NSIG
cdef signal_signal = signal.signal
cdef signal_siginterrupt = signal.siginterrupt
cdef signal_set_wakeup_fd = signal.set_wakeup_fd
cdef signal_default_int_handler = signal.default_int_handler
cdef signal_SIG_DFL = signal.SIG_DFL
cdef time_sleep = time.sleep
cdef time_monotonic = time.monotonic
cdef tb_StackSummary = traceback.StackSummary
cdef tb_walk_stack = traceback.walk_stack
cdef tb_format_list = traceback.format_list
cdef warnings_warn = warnings.warn
cdef weakref_WeakValueDictionary = weakref.WeakValueDictionary
cdef weakref_WeakSet = weakref.WeakSet
cdef py_inf = float('inf')
# Cython doesn't clean-up imported objects properly in Py3 mode,
# so we delete refs to all modules manually (except sys)
del asyncio, concurrent, collections, errno
del functools, inspect, itertools, socket, os, threading
del signal, subprocess, ssl
del time, traceback, warnings, weakref

View File

@ -0,0 +1,96 @@
from libc.stdint cimport int8_t, uint64_t
cdef extern from "arpa/inet.h" nogil:
int ntohl(int)
int htonl(int)
int ntohs(int)
cdef extern from "sys/socket.h" nogil:
struct sockaddr:
unsigned short sa_family
char sa_data[14]
struct addrinfo:
int ai_flags
int ai_family
int ai_socktype
int ai_protocol
size_t ai_addrlen
sockaddr* ai_addr
char* ai_canonname
addrinfo* ai_next
struct sockaddr_in:
unsigned short sin_family
unsigned short sin_port
# ...
struct sockaddr_in6:
unsigned short sin6_family
unsigned short sin6_port
unsigned long sin6_flowinfo
# ...
unsigned long sin6_scope_id
struct sockaddr_storage:
unsigned short ss_family
# ...
const char *gai_strerror(int errcode)
int socketpair(int domain, int type, int protocol, int socket_vector[2])
int setsockopt(int socket, int level, int option_name,
const void *option_value, int option_len)
cdef extern from "sys/un.h" nogil:
struct sockaddr_un:
unsigned short sun_family
char* sun_path
# ...
cdef extern from "unistd.h" nogil:
ssize_t write(int fd, const void *buf, size_t count)
void _exit(int status)
cdef extern from "pthread.h":
int pthread_atfork(
void (*prepare)(),
void (*parent)(),
void (*child)())
cdef extern from "includes/compat.h" nogil:
cdef int EWOULDBLOCK
cdef int PLATFORM_IS_APPLE
cdef int PLATFORM_IS_LINUX
struct epoll_event:
# We don't use the fields
pass
int EPOLL_CTL_DEL
int epoll_ctl(int epfd, int op, int fd, epoll_event *event)
object MakeUnixSockPyAddr(sockaddr_un *addr)
cdef extern from "includes/fork_handler.h":
uint64_t MAIN_THREAD_ID
int8_t MAIN_THREAD_ID_SET
ctypedef void (*OnForkHandler)()
void handleAtFork()
void setForkHandler(OnForkHandler handler)
void resetForkHandler()
void setMainThreadID(uint64_t id)

View File

@ -0,0 +1,506 @@
from libc.stdint cimport uint16_t, uint32_t, uint64_t, int64_t
from posix.types cimport gid_t, uid_t
from posix.unistd cimport getuid
from . cimport system
# This is an internal enum UV_HANDLE_READABLE from uv-common.h, used only by
# handles/pipe.pyx to temporarily workaround a libuv issue libuv/libuv#2058,
# before there is a proper fix in libuv. In short, libuv disallowed feeding a
# write-only pipe to uv_read_start(), which was needed by uvloop to detect a
# broken pipe without having to send anything on the write-only end. We're
# setting UV_HANDLE_READABLE on pipe_t to workaround this limitation
# temporarily, please see also #317.
cdef enum:
UV_INTERNAL_HANDLE_READABLE = 0x00004000
cdef extern from "uv.h" nogil:
cdef int UV_TCP_IPV6ONLY
cdef int UV_EACCES
cdef int UV_EAGAIN
cdef int UV_EALREADY
cdef int UV_EBUSY
cdef int UV_ECONNABORTED
cdef int UV_ECONNREFUSED
cdef int UV_ECONNRESET
cdef int UV_ECANCELED
cdef int UV_EEXIST
cdef int UV_EINTR
cdef int UV_EINVAL
cdef int UV_EISDIR
cdef int UV_ENOENT
cdef int UV_EOF
cdef int UV_EPERM
cdef int UV_EPIPE
cdef int UV_ESHUTDOWN
cdef int UV_ESRCH
cdef int UV_ETIMEDOUT
cdef int UV_EBADF
cdef int UV_ENOBUFS
cdef int UV_EAI_ADDRFAMILY
cdef int UV_EAI_AGAIN
cdef int UV_EAI_BADFLAGS
cdef int UV_EAI_BADHINTS
cdef int UV_EAI_CANCELED
cdef int UV_EAI_FAIL
cdef int UV_EAI_FAMILY
cdef int UV_EAI_MEMORY
cdef int UV_EAI_NODATA
cdef int UV_EAI_NONAME
cdef int UV_EAI_OVERFLOW
cdef int UV_EAI_PROTOCOL
cdef int UV_EAI_SERVICE
cdef int UV_EAI_SOCKTYPE
cdef int SOL_SOCKET
cdef int SO_ERROR
cdef int SO_REUSEADDR
# use has_SO_REUSEPORT and SO_REUSEPORT in stdlib.pxi instead
cdef int AF_INET
cdef int AF_INET6
cdef int AF_UNIX
cdef int AF_UNSPEC
cdef int AI_PASSIVE
cdef int AI_NUMERICHOST
cdef int INET6_ADDRSTRLEN
cdef int IPPROTO_IPV6
cdef int SOCK_STREAM
cdef int SOCK_DGRAM
cdef int IPPROTO_TCP
cdef int IPPROTO_UDP
cdef int SIGINT
cdef int SIGHUP
cdef int SIGCHLD
cdef int SIGKILL
cdef int SIGTERM
ctypedef int uv_os_sock_t
ctypedef int uv_file
ctypedef int uv_os_fd_t
ctypedef struct uv_buf_t:
char* base
size_t len
ctypedef struct uv_loop_t:
void* data
# ...
ctypedef struct uv_handle_t:
void* data
uv_loop_t* loop
unsigned int flags
# ...
ctypedef struct uv_idle_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_check_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_signal_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_async_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_timer_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_stream_t:
void* data
size_t write_queue_size
uv_loop_t* loop
# ...
ctypedef struct uv_tcp_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_pipe_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_udp_t:
void* data
uv_loop_t* loop
size_t send_queue_size
size_t send_queue_count
# ...
ctypedef struct uv_udp_send_t:
void* data
uv_udp_t* handle
ctypedef struct uv_poll_t:
void* data
uv_loop_t* loop
# ...
ctypedef struct uv_req_t:
# Only cancellation of uv_fs_t, uv_getaddrinfo_t,
# uv_getnameinfo_t and uv_work_t requests is
# currently supported.
void* data
uv_req_type type
# ...
ctypedef struct uv_connect_t:
void* data
ctypedef struct uv_getaddrinfo_t:
void* data
# ...
ctypedef struct uv_getnameinfo_t:
void* data
# ...
ctypedef struct uv_write_t:
void* data
# ...
ctypedef struct uv_shutdown_t:
void* data
# ...
ctypedef struct uv_process_t:
void* data
int pid
# ...
ctypedef struct uv_fs_event_t:
void* data
# ...
ctypedef enum uv_req_type:
UV_UNKNOWN_REQ = 0,
UV_REQ,
UV_CONNECT,
UV_WRITE,
UV_SHUTDOWN,
UV_UDP_SEND,
UV_FS,
UV_WORK,
UV_GETADDRINFO,
UV_GETNAMEINFO,
UV_REQ_TYPE_PRIVATE,
UV_REQ_TYPE_MAX
ctypedef enum uv_run_mode:
UV_RUN_DEFAULT = 0,
UV_RUN_ONCE,
UV_RUN_NOWAIT
ctypedef enum uv_poll_event:
UV_READABLE = 1,
UV_WRITABLE = 2,
UV_DISCONNECT = 4
ctypedef enum uv_udp_flags:
UV_UDP_IPV6ONLY = 1,
UV_UDP_PARTIAL = 2
ctypedef enum uv_membership:
UV_LEAVE_GROUP = 0,
UV_JOIN_GROUP
cdef enum uv_fs_event:
UV_RENAME = 1,
UV_CHANGE = 2
const char* uv_strerror(int err)
const char* uv_err_name(int err)
ctypedef void (*uv_walk_cb)(uv_handle_t* handle, void* arg) with gil
ctypedef void (*uv_close_cb)(uv_handle_t* handle) with gil
ctypedef void (*uv_idle_cb)(uv_idle_t* handle) with gil
ctypedef void (*uv_check_cb)(uv_check_t* handle) with gil
ctypedef void (*uv_signal_cb)(uv_signal_t* handle, int signum) with gil
ctypedef void (*uv_async_cb)(uv_async_t* handle) with gil
ctypedef void (*uv_timer_cb)(uv_timer_t* handle) with gil
ctypedef void (*uv_connection_cb)(uv_stream_t* server, int status) with gil
ctypedef void (*uv_alloc_cb)(uv_handle_t* handle,
size_t suggested_size,
uv_buf_t* buf) with gil
ctypedef void (*uv_read_cb)(uv_stream_t* stream,
ssize_t nread,
const uv_buf_t* buf) with gil
ctypedef void (*uv_write_cb)(uv_write_t* req, int status) with gil
ctypedef void (*uv_getaddrinfo_cb)(uv_getaddrinfo_t* req,
int status,
system.addrinfo* res) with gil
ctypedef void (*uv_getnameinfo_cb)(uv_getnameinfo_t* req,
int status,
const char* hostname,
const char* service) with gil
ctypedef void (*uv_shutdown_cb)(uv_shutdown_t* req, int status) with gil
ctypedef void (*uv_poll_cb)(uv_poll_t* handle,
int status, int events) with gil
ctypedef void (*uv_connect_cb)(uv_connect_t* req, int status) with gil
ctypedef void (*uv_udp_send_cb)(uv_udp_send_t* req, int status) with gil
ctypedef void (*uv_udp_recv_cb)(uv_udp_t* handle,
ssize_t nread,
const uv_buf_t* buf,
const system.sockaddr* addr,
unsigned flags) with gil
ctypedef void (*uv_fs_event_cb)(uv_fs_event_t* handle,
const char *filename,
int events,
int status) with gil
# Generic request functions
int uv_cancel(uv_req_t* req)
# Generic handler functions
int uv_is_active(const uv_handle_t* handle)
void uv_close(uv_handle_t* handle, uv_close_cb close_cb)
int uv_is_closing(const uv_handle_t* handle)
int uv_fileno(const uv_handle_t* handle, uv_os_fd_t* fd)
void uv_walk(uv_loop_t* loop, uv_walk_cb walk_cb, void* arg)
# Loop functions
int uv_loop_init(uv_loop_t* loop)
int uv_loop_close(uv_loop_t* loop)
int uv_loop_alive(uv_loop_t* loop)
int uv_loop_fork(uv_loop_t* loop)
uv_os_fd_t uv_backend_fd(uv_loop_t* loop)
void uv_update_time(uv_loop_t* loop)
uint64_t uv_now(const uv_loop_t*)
int uv_run(uv_loop_t*, uv_run_mode mode) nogil
void uv_stop(uv_loop_t*)
# Idle handler
int uv_idle_init(uv_loop_t*, uv_idle_t* idle)
int uv_idle_start(uv_idle_t* idle, uv_idle_cb cb)
int uv_idle_stop(uv_idle_t* idle)
# Check handler
int uv_check_init(uv_loop_t*, uv_check_t* idle)
int uv_check_start(uv_check_t* check, uv_check_cb cb)
int uv_check_stop(uv_check_t* check)
# Signal handler
int uv_signal_init(uv_loop_t* loop, uv_signal_t* handle)
int uv_signal_start(uv_signal_t* handle,
uv_signal_cb signal_cb,
int signum)
int uv_signal_stop(uv_signal_t* handle)
# Async handler
int uv_async_init(uv_loop_t*,
uv_async_t* async_,
uv_async_cb async_cb)
int uv_async_send(uv_async_t* async_)
# Timer handler
int uv_timer_init(uv_loop_t*, uv_timer_t* handle)
int uv_timer_start(uv_timer_t* handle,
uv_timer_cb cb,
uint64_t timeout,
uint64_t repeat)
int uv_timer_stop(uv_timer_t* handle)
# DNS
int uv_getaddrinfo(uv_loop_t* loop,
uv_getaddrinfo_t* req,
uv_getaddrinfo_cb getaddrinfo_cb,
const char* node,
const char* service,
const system.addrinfo* hints)
void uv_freeaddrinfo(system.addrinfo* ai)
int uv_getnameinfo(uv_loop_t* loop,
uv_getnameinfo_t* req,
uv_getnameinfo_cb getnameinfo_cb,
const system.sockaddr* addr,
int flags)
int uv_ip4_name(const system.sockaddr_in* src, char* dst, size_t size)
int uv_ip6_name(const system.sockaddr_in6* src, char* dst, size_t size)
# Streams
int uv_listen(uv_stream_t* stream, int backlog, uv_connection_cb cb)
int uv_accept(uv_stream_t* server, uv_stream_t* client)
int uv_read_start(uv_stream_t* stream,
uv_alloc_cb alloc_cb,
uv_read_cb read_cb)
int uv_read_stop(uv_stream_t*)
int uv_write(uv_write_t* req, uv_stream_t* handle,
uv_buf_t bufs[], unsigned int nbufs, uv_write_cb cb)
int uv_try_write(uv_stream_t* handle, uv_buf_t bufs[], unsigned int nbufs)
int uv_shutdown(uv_shutdown_t* req, uv_stream_t* handle, uv_shutdown_cb cb)
int uv_is_readable(const uv_stream_t* handle)
int uv_is_writable(const uv_stream_t* handle)
# TCP
int uv_tcp_init_ex(uv_loop_t*, uv_tcp_t* handle, unsigned int flags)
int uv_tcp_nodelay(uv_tcp_t* handle, int enable)
int uv_tcp_keepalive(uv_tcp_t* handle, int enable, unsigned int delay)
int uv_tcp_open(uv_tcp_t* handle, uv_os_sock_t sock)
int uv_tcp_bind(uv_tcp_t* handle, system.sockaddr* addr,
unsigned int flags)
int uv_tcp_getsockname(const uv_tcp_t* handle, system.sockaddr* name,
int* namelen)
int uv_tcp_getpeername(const uv_tcp_t* handle, system.sockaddr* name,
int* namelen)
int uv_tcp_connect(uv_connect_t* req, uv_tcp_t* handle,
const system.sockaddr* addr, uv_connect_cb cb)
# Pipes
int uv_pipe_init(uv_loop_t* loop, uv_pipe_t* handle, int ipc)
int uv_pipe_open(uv_pipe_t* handle, uv_os_fd_t file)
int uv_pipe_bind(uv_pipe_t* handle, const char* name)
void uv_pipe_connect(uv_connect_t* req, uv_pipe_t* handle,
const char* name, uv_connect_cb cb)
# UDP
int uv_udp_init_ex(uv_loop_t* loop, uv_udp_t* handle, unsigned int flags)
int uv_udp_connect(uv_udp_t* handle, const system.sockaddr* addr)
int uv_udp_open(uv_udp_t* handle, uv_os_sock_t sock)
int uv_udp_bind(uv_udp_t* handle, const system.sockaddr* addr,
unsigned int flags)
int uv_udp_send(uv_udp_send_t* req, uv_udp_t* handle,
const uv_buf_t bufs[], unsigned int nbufs,
const system.sockaddr* addr, uv_udp_send_cb send_cb)
int uv_udp_try_send(uv_udp_t* handle,
const uv_buf_t bufs[], unsigned int nbufs,
const system.sockaddr* addr)
int uv_udp_recv_start(uv_udp_t* handle, uv_alloc_cb alloc_cb,
uv_udp_recv_cb recv_cb)
int uv_udp_recv_stop(uv_udp_t* handle)
int uv_udp_set_broadcast(uv_udp_t* handle, int on)
# Polling
int uv_poll_init(uv_loop_t* loop, uv_poll_t* handle, int fd)
int uv_poll_init_socket(uv_loop_t* loop, uv_poll_t* handle,
uv_os_sock_t socket)
int uv_poll_start(uv_poll_t* handle, int events, uv_poll_cb cb)
int uv_poll_stop(uv_poll_t* poll)
# FS Event
int uv_fs_event_init(uv_loop_t *loop, uv_fs_event_t *handle)
int uv_fs_event_start(uv_fs_event_t *handle, uv_fs_event_cb cb,
const char *path, unsigned int flags)
int uv_fs_event_stop(uv_fs_event_t *handle)
# Misc
ctypedef struct uv_timeval_t:
long tv_sec
long tv_usec
ctypedef struct uv_rusage_t:
uv_timeval_t ru_utime # user CPU time used
uv_timeval_t ru_stime # system CPU time used
uint64_t ru_maxrss # maximum resident set size
uint64_t ru_ixrss # integral shared memory size
uint64_t ru_idrss # integral unshared data size
uint64_t ru_isrss # integral unshared stack size
uint64_t ru_minflt # page reclaims (soft page faults)
uint64_t ru_majflt # page faults (hard page faults)
uint64_t ru_nswap # swaps
uint64_t ru_inblock # block input operations
uint64_t ru_oublock # block output operations
uint64_t ru_msgsnd # IPC messages sent
uint64_t ru_msgrcv # IPC messages received
uint64_t ru_nsignals # signals received
uint64_t ru_nvcsw # voluntary context switches
uint64_t ru_nivcsw # involuntary context switches
int uv_getrusage(uv_rusage_t* rusage)
int uv_ip4_addr(const char* ip, int port, system.sockaddr_in* addr)
int uv_ip6_addr(const char* ip, int port, system.sockaddr_in6* addr)
# Memory Allocation
ctypedef void* (*uv_malloc_func)(size_t size)
ctypedef void* (*uv_realloc_func)(void* ptr, size_t size)
ctypedef void* (*uv_calloc_func)(size_t count, size_t size)
ctypedef void (*uv_free_func)(void* ptr)
int uv_replace_allocator(uv_malloc_func malloc_func,
uv_realloc_func realloc_func,
uv_calloc_func calloc_func,
uv_free_func free_func)
# Process
ctypedef void (*uv_exit_cb)(uv_process_t*, int64_t exit_status,
int term_signal) with gil
ctypedef enum uv_process_flags:
UV_PROCESS_SETUID = 1,
UV_PROCESS_SETGID = 2,
UV_PROCESS_WINDOWS_VERBATIM_ARGUMENTS = 4,
UV_PROCESS_DETACHED = 8,
UV_PROCESS_WINDOWS_HIDE = 16
ctypedef enum uv_stdio_flags:
UV_IGNORE = 0x00,
UV_CREATE_PIPE = 0x01,
UV_INHERIT_FD = 0x02,
UV_INHERIT_STREAM = 0x04,
UV_READABLE_PIPE = 0x10,
UV_WRITABLE_PIPE = 0x20
ctypedef union uv_stdio_container_data_u:
uv_stream_t* stream
int fd
ctypedef struct uv_stdio_container_t:
uv_stdio_flags flags
uv_stdio_container_data_u data
ctypedef struct uv_process_options_t:
uv_exit_cb exit_cb
char* file
char** args
char** env
char* cwd
unsigned int flags
int stdio_count
uv_stdio_container_t* stdio
uid_t uid
gid_t gid
int uv_spawn(uv_loop_t* loop, uv_process_t* handle,
const uv_process_options_t* options)
int uv_process_kill(uv_process_t* handle, int signum)
unsigned int uv_version()

Binary file not shown.

View File

@ -0,0 +1,230 @@
# cython: language_level=3
from .includes cimport uv
from .includes cimport system
from libc.stdint cimport uint64_t, uint32_t, int64_t
include "includes/consts.pxi"
cdef extern from *:
ctypedef int vint "volatile int"
cdef class UVHandle
cdef class UVSocketHandle(UVHandle)
cdef class UVAsync(UVHandle)
cdef class UVTimer(UVHandle)
cdef class UVIdle(UVHandle)
cdef class UVBaseTransport(UVSocketHandle)
ctypedef object (*method_t)(object)
ctypedef object (*method1_t)(object, object)
ctypedef object (*method2_t)(object, object, object)
ctypedef object (*method3_t)(object, object, object, object)
cdef class Loop:
cdef:
uv.uv_loop_t *uvloop
bint _coroutine_debug_set
int _coroutine_origin_tracking_saved_depth
public slow_callback_duration
readonly bint _closed
bint _debug
bint _running
bint _stopping
uint64_t _thread_id
object _task_factory
object _exception_handler
object _default_executor
object _ready
set _queued_streams, _executing_streams
Py_ssize_t _ready_len
set _servers
object _transports
set _processes
dict _fd_to_reader_fileobj
dict _fd_to_writer_fileobj
dict _unix_server_sockets
set _signals
dict _signal_handlers
object _ssock
object _csock
bint _listening_signals
int _old_signal_wakeup_id
set _timers
dict _polls
UVProcess active_process_handler
UVAsync handler_async
UVIdle handler_idle
UVCheck handler_check__exec_writes
object _last_error
cdef object __weakref__
object _asyncgens
bint _asyncgens_shutdown_called
bint _executor_shutdown_called
char _recv_buffer[UV_STREAM_RECV_BUF_SIZE]
bint _recv_buffer_in_use
# DEBUG fields
# True when compiled with DEBUG.
# Used only in unittests.
readonly bint _debug_cc
readonly object _debug_handles_total
readonly object _debug_handles_closed
readonly object _debug_handles_current
readonly uint64_t _debug_uv_handles_total
readonly uint64_t _debug_uv_handles_freed
readonly uint64_t _debug_cb_handles_total
readonly uint64_t _debug_cb_handles_count
readonly uint64_t _debug_cb_timer_handles_total
readonly uint64_t _debug_cb_timer_handles_count
readonly uint64_t _debug_stream_shutdown_errors_total
readonly uint64_t _debug_stream_listen_errors_total
readonly uint64_t _debug_stream_read_cb_total
readonly uint64_t _debug_stream_read_cb_errors_total
readonly uint64_t _debug_stream_read_eof_total
readonly uint64_t _debug_stream_read_eof_cb_errors_total
readonly uint64_t _debug_stream_read_errors_total
readonly uint64_t _debug_stream_write_tries
readonly uint64_t _debug_stream_write_errors_total
readonly uint64_t _debug_stream_write_ctx_total
readonly uint64_t _debug_stream_write_ctx_cnt
readonly uint64_t _debug_stream_write_cb_errors_total
readonly uint64_t _poll_read_events_total
readonly uint64_t _poll_read_cb_errors_total
readonly uint64_t _poll_write_events_total
readonly uint64_t _poll_write_cb_errors_total
readonly uint64_t _sock_try_write_total
readonly uint64_t _debug_exception_handler_cnt
cdef _init_debug_fields(self)
cdef _on_wake(self)
cdef _on_idle(self)
cdef __run(self, uv.uv_run_mode)
cdef _run(self, uv.uv_run_mode)
cdef _close(self)
cdef _stop(self, exc)
cdef uint64_t _time(self)
cdef inline _queue_write(self, UVStream stream)
cdef _exec_queued_writes(self)
cdef inline _call_soon(self, object callback, object args, object context)
cdef inline _append_ready_handle(self, Handle handle)
cdef inline _call_soon_handle(self, Handle handle)
cdef _call_later(self, uint64_t delay, object callback, object args,
object context)
cdef void _handle_exception(self, object ex)
cdef inline _is_main_thread(self)
cdef inline _new_future(self)
cdef inline _check_signal(self, sig)
cdef inline _check_closed(self)
cdef inline _check_thread(self)
cdef _getaddrinfo(self, object host, object port,
int family, int type,
int proto, int flags,
int unpack)
cdef _getnameinfo(self, system.sockaddr *addr, int flags)
cdef _track_transport(self, UVBaseTransport transport)
cdef _fileobj_to_fd(self, fileobj)
cdef _ensure_fd_no_transport(self, fd)
cdef _track_process(self, UVProcess proc)
cdef _untrack_process(self, UVProcess proc)
cdef _add_reader(self, fd, Handle handle)
cdef _has_reader(self, fd)
cdef _remove_reader(self, fd)
cdef _add_writer(self, fd, Handle handle)
cdef _has_writer(self, fd)
cdef _remove_writer(self, fd)
cdef _sock_recv(self, fut, sock, n)
cdef _sock_recv_into(self, fut, sock, buf)
cdef _sock_sendall(self, fut, sock, data)
cdef _sock_accept(self, fut, sock)
cdef _sock_connect(self, sock, address)
cdef _sock_connect_cb(self, fut, sock, address)
cdef _sock_set_reuseport(self, int fd)
cdef _setup_or_resume_signals(self)
cdef _shutdown_signals(self)
cdef _pause_signals(self)
cdef _handle_signal(self, sig)
cdef _read_from_self(self)
cdef inline _ceval_process_signals(self)
cdef _invoke_signals(self, bytes data)
cdef _set_coroutine_debug(self, bint enabled)
cdef _print_debug_info(self)
include "cbhandles.pxd"
include "handles/handle.pxd"
include "handles/async_.pxd"
include "handles/idle.pxd"
include "handles/check.pxd"
include "handles/timer.pxd"
include "handles/poll.pxd"
include "handles/basetransport.pxd"
include "handles/stream.pxd"
include "handles/streamserver.pxd"
include "handles/tcp.pxd"
include "handles/pipe.pxd"
include "handles/process.pxd"
include "handles/fsevent.pxd"
include "request.pxd"
include "sslproto.pxd"
include "handles/udp.pxd"
include "server.pxd"

View File

@ -0,0 +1,297 @@
import asyncio
import ssl
import sys
from socket import AddressFamily, SocketKind, _Address, _RetAddress, socket
from typing import (
IO,
Any,
Awaitable,
Callable,
Dict,
Generator,
List,
Optional,
Sequence,
Tuple,
TypeVar,
Union,
overload,
)
_T = TypeVar('_T')
_Context = Dict[str, Any]
_ExceptionHandler = Callable[[asyncio.AbstractEventLoop, _Context], Any]
_SSLContext = Union[bool, None, ssl.SSLContext]
_ProtocolT = TypeVar("_ProtocolT", bound=asyncio.BaseProtocol)
class Loop:
def call_soon(
self, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
) -> asyncio.Handle: ...
def call_soon_threadsafe(
self, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
) -> asyncio.Handle: ...
def call_later(
self, delay: float, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
) -> asyncio.TimerHandle: ...
def call_at(
self, when: float, callback: Callable[..., Any], *args: Any, context: Optional[Any] = ...
) -> asyncio.TimerHandle: ...
def time(self) -> float: ...
def stop(self) -> None: ...
def run_forever(self) -> None: ...
def close(self) -> None: ...
def get_debug(self) -> bool: ...
def set_debug(self, enabled: bool) -> None: ...
def is_running(self) -> bool: ...
def is_closed(self) -> bool: ...
def create_future(self) -> asyncio.Future[Any]: ...
def create_task(
self,
coro: Union[Awaitable[_T], Generator[Any, None, _T]],
*,
name: Optional[str] = ...,
) -> asyncio.Task[_T]: ...
def set_task_factory(
self,
factory: Optional[
Callable[[asyncio.AbstractEventLoop, Generator[Any, None, _T]], asyncio.Future[_T]]
],
) -> None: ...
def get_task_factory(
self,
) -> Optional[
Callable[[asyncio.AbstractEventLoop, Generator[Any, None, _T]], asyncio.Future[_T]]
]: ...
@overload
def run_until_complete(self, future: Generator[Any, None, _T]) -> _T: ...
@overload
def run_until_complete(self, future: Awaitable[_T]) -> _T: ...
async def getaddrinfo(
self,
host: Optional[Union[str, bytes]],
port: Optional[Union[str, bytes, int]],
*,
family: int = ...,
type: int = ...,
proto: int = ...,
flags: int = ...,
) -> List[
Tuple[
AddressFamily,
SocketKind,
int,
str,
Union[Tuple[str, int], Tuple[str, int, int, int]],
]
]: ...
async def getnameinfo(
self,
sockaddr: Union[
Tuple[str, int],
Tuple[str, int, int],
Tuple[str, int, int, int]
],
flags: int = ...,
) -> Tuple[str, str]: ...
async def start_tls(
self,
transport: asyncio.BaseTransport,
protocol: asyncio.BaseProtocol,
sslcontext: ssl.SSLContext,
*,
server_side: bool = ...,
server_hostname: Optional[str] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
) -> asyncio.BaseTransport: ...
@overload
async def create_server(
self,
protocol_factory: asyncio.events._ProtocolFactory,
host: Optional[Union[str, Sequence[str]]] = ...,
port: int = ...,
*,
family: int = ...,
flags: int = ...,
sock: None = ...,
backlog: int = ...,
ssl: _SSLContext = ...,
reuse_address: Optional[bool] = ...,
reuse_port: Optional[bool] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
start_serving: bool = ...,
) -> asyncio.AbstractServer: ...
@overload
async def create_server(
self,
protocol_factory: asyncio.events._ProtocolFactory,
host: None = ...,
port: None = ...,
*,
family: int = ...,
flags: int = ...,
sock: socket = ...,
backlog: int = ...,
ssl: _SSLContext = ...,
reuse_address: Optional[bool] = ...,
reuse_port: Optional[bool] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
start_serving: bool = ...,
) -> asyncio.AbstractServer: ...
@overload
async def create_connection(
self,
protocol_factory: Callable[[], _ProtocolT],
host: str = ...,
port: int = ...,
*,
ssl: _SSLContext = ...,
family: int = ...,
proto: int = ...,
flags: int = ...,
sock: None = ...,
local_addr: Optional[Tuple[str, int]] = ...,
server_hostname: Optional[str] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
@overload
async def create_connection(
self,
protocol_factory: Callable[[], _ProtocolT],
host: None = ...,
port: None = ...,
*,
ssl: _SSLContext = ...,
family: int = ...,
proto: int = ...,
flags: int = ...,
sock: socket,
local_addr: None = ...,
server_hostname: Optional[str] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def create_unix_server(
self,
protocol_factory: asyncio.events._ProtocolFactory,
path: Optional[str] = ...,
*,
backlog: int = ...,
sock: Optional[socket] = ...,
ssl: _SSLContext = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
start_serving: bool = ...,
) -> asyncio.AbstractServer: ...
async def create_unix_connection(
self,
protocol_factory: Callable[[], _ProtocolT],
path: Optional[str] = ...,
*,
ssl: _SSLContext = ...,
sock: Optional[socket] = ...,
server_hostname: Optional[str] = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
def default_exception_handler(self, context: _Context) -> None: ...
def get_exception_handler(self) -> Optional[_ExceptionHandler]: ...
def set_exception_handler(self, handler: Optional[_ExceptionHandler]) -> None: ...
def call_exception_handler(self, context: _Context) -> None: ...
def add_reader(self, fd: Any, callback: Callable[..., Any], *args: Any) -> None: ...
def remove_reader(self, fd: Any) -> None: ...
def add_writer(self, fd: Any, callback: Callable[..., Any], *args: Any) -> None: ...
def remove_writer(self, fd: Any) -> None: ...
async def sock_recv(self, sock: socket, nbytes: int) -> bytes: ...
async def sock_recv_into(self, sock: socket, buf: bytearray) -> int: ...
async def sock_sendall(self, sock: socket, data: bytes) -> None: ...
async def sock_accept(self, sock: socket) -> Tuple[socket, _RetAddress]: ...
async def sock_connect(self, sock: socket, address: _Address) -> None: ...
async def sock_recvfrom(self, sock: socket, bufsize: int) -> bytes: ...
async def sock_recvfrom_into(self, sock: socket, buf: bytearray, nbytes: int = ...) -> int: ...
async def sock_sendto(self, sock: socket, data: bytes, address: _Address) -> None: ...
async def connect_accepted_socket(
self,
protocol_factory: Callable[[], _ProtocolT],
sock: socket,
*,
ssl: _SSLContext = ...,
ssl_handshake_timeout: Optional[float] = ...,
ssl_shutdown_timeout: Optional[float] = ...,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def run_in_executor(
self, executor: Any, func: Callable[..., _T], *args: Any
) -> _T: ...
def set_default_executor(self, executor: Any) -> None: ...
async def subprocess_shell(
self,
protocol_factory: Callable[[], _ProtocolT],
cmd: Union[bytes, str],
*,
stdin: Any = ...,
stdout: Any = ...,
stderr: Any = ...,
**kwargs: Any,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def subprocess_exec(
self,
protocol_factory: Callable[[], _ProtocolT],
*args: Any,
stdin: Any = ...,
stdout: Any = ...,
stderr: Any = ...,
**kwargs: Any,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def connect_read_pipe(
self, protocol_factory: Callable[[], _ProtocolT], pipe: Any
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def connect_write_pipe(
self, protocol_factory: Callable[[], _ProtocolT], pipe: Any
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
def add_signal_handler(
self, sig: int, callback: Callable[..., Any], *args: Any
) -> None: ...
def remove_signal_handler(self, sig: int) -> bool: ...
async def create_datagram_endpoint(
self,
protocol_factory: Callable[[], _ProtocolT],
local_addr: Optional[Tuple[str, int]] = ...,
remote_addr: Optional[Tuple[str, int]] = ...,
*,
family: int = ...,
proto: int = ...,
flags: int = ...,
reuse_address: Optional[bool] = ...,
reuse_port: Optional[bool] = ...,
allow_broadcast: Optional[bool] = ...,
sock: Optional[socket] = ...,
) -> tuple[asyncio.BaseProtocol, _ProtocolT]: ...
async def shutdown_asyncgens(self) -> None: ...
async def shutdown_default_executor(
self,
timeout: Optional[float] = ...,
) -> None: ...
# Loop doesn't implement these, but since they are marked as abstract in typeshed,
# we have to put them in so mypy thinks the base methods are overridden
async def sendfile(
self,
transport: asyncio.BaseTransport,
file: IO[bytes],
offset: int = ...,
count: Optional[int] = ...,
*,
fallback: bool = ...,
) -> int: ...
async def sock_sendfile(
self,
sock: socket,
file: IO[bytes],
offset: int = ...,
count: Optional[int] = ...,
*,
fallback: bool = ...
) -> int: ...

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,79 @@
cdef object _LRU_MARKER = object()
@cython.final
cdef class LruCache:
cdef:
object _dict
int _maxsize
object _dict_move_to_end
object _dict_get
# We use an OrderedDict for LRU implementation. Operations:
#
# * We use a simple `__setitem__` to push a new entry:
# `entries[key] = new_entry`
# That will push `new_entry` to the *end* of the entries dict.
#
# * When we have a cache hit, we call
# `entries.move_to_end(key, last=True)`
# to move the entry to the *end* of the entries dict.
#
# * When we need to remove entries to maintain `max_size`, we call
# `entries.popitem(last=False)`
# to remove an entry from the *beginning* of the entries dict.
#
# So new entries and hits are always promoted to the end of the
# entries dict, whereas the unused one will group in the
# beginning of it.
def __init__(self, *, maxsize):
if maxsize <= 0:
raise ValueError(
f'maxsize is expected to be greater than 0, got {maxsize}')
self._dict = col_OrderedDict()
self._dict_move_to_end = self._dict.move_to_end
self._dict_get = self._dict.get
self._maxsize = maxsize
cdef get(self, key, default):
o = self._dict_get(key, _LRU_MARKER)
if o is _LRU_MARKER:
return default
self._dict_move_to_end(key) # last=True
return o
cdef inline needs_cleanup(self):
return len(self._dict) > self._maxsize
cdef inline cleanup_one(self):
k, _ = self._dict.popitem(last=False)
return k
def __getitem__(self, key):
o = self._dict[key]
self._dict_move_to_end(key) # last=True
return o
def __setitem__(self, key, o):
if key in self._dict:
self._dict[key] = o
self._dict_move_to_end(key) # last=True
else:
self._dict[key] = o
while self.needs_cleanup():
self.cleanup_one()
def __delitem__(self, key):
del self._dict[key]
def __contains__(self, key):
return key in self._dict
def __len__(self):
return len(self._dict)
def __iter__(self):
return iter(self._dict)

View File

@ -0,0 +1,209 @@
cdef class PseudoSocket:
cdef:
int _family
int _type
int _proto
int _fd
object _peername
object _sockname
def __init__(self, int family, int type, int proto, int fd):
self._family = family
self._type = type
self._proto = proto
self._fd = fd
self._peername = None
self._sockname = None
cdef _na(self, what):
raise TypeError('transport sockets do not support {}'.format(what))
cdef _make_sock(self):
return socket_socket(self._family, self._type, self._proto, self._fd)
property family:
def __get__(self):
try:
return socket_AddressFamily(self._family)
except ValueError:
return self._family
property type:
def __get__(self):
try:
return socket_SocketKind(self._type)
except ValueError:
return self._type
property proto:
def __get__(self):
return self._proto
def __repr__(self):
s = ("<uvloop.PseudoSocket fd={}, family={!s}, "
"type={!s}, proto={}").format(self.fileno(), self.family.name,
self.type.name, self.proto)
if self._fd != -1:
try:
laddr = self.getsockname()
if laddr:
s += ", laddr=%s" % str(laddr)
except socket_error:
pass
try:
raddr = self.getpeername()
if raddr:
s += ", raddr=%s" % str(raddr)
except socket_error:
pass
s += '>'
return s
def __getstate__(self):
raise TypeError("Cannot serialize socket object")
def fileno(self):
return self._fd
def dup(self):
fd = os_dup(self._fd)
sock = socket_socket(self._family, self._type, self._proto, fileno=fd)
sock.settimeout(0)
return sock
def get_inheritable(self):
return os_get_inheritable(self._fd)
def set_inheritable(self):
os_set_inheritable(self._fd)
def ioctl(self, *args, **kwargs):
pass
def getsockopt(self, *args, **kwargs):
sock = self._make_sock()
try:
return sock.getsockopt(*args, **kwargs)
finally:
sock.detach()
def setsockopt(self, *args, **kwargs):
sock = self._make_sock()
try:
return sock.setsockopt(*args, **kwargs)
finally:
sock.detach()
def getpeername(self):
if self._peername is not None:
return self._peername
sock = self._make_sock()
try:
self._peername = sock.getpeername()
return self._peername
finally:
sock.detach()
def getsockname(self):
if self._sockname is not None:
return self._sockname
sock = self._make_sock()
try:
self._sockname = sock.getsockname()
return self._sockname
finally:
sock.detach()
def share(self, process_id):
sock = self._make_sock()
try:
return sock.share(process_id)
finally:
sock.detach()
def accept(self):
self._na('accept() method')
def connect(self, *args):
self._na('connect() method')
def connect_ex(self, *args):
self._na('connect_ex() method')
def bind(self, *args):
self._na('bind() method')
def listen(self, *args, **kwargs):
self._na('listen() method')
def makefile(self):
self._na('makefile() method')
def sendfile(self, *args, **kwargs):
self._na('sendfile() method')
def close(self):
self._na('close() method')
def detach(self):
self._na('detach() method')
def shutdown(self, *args):
self._na('shutdown() method')
def sendmsg_afalg(self, *args, **kwargs):
self._na('sendmsg_afalg() method')
def sendmsg(self):
self._na('sendmsg() method')
def sendto(self, *args, **kwargs):
self._na('sendto() method')
def send(self, *args, **kwargs):
self._na('send() method')
def sendall(self, *args, **kwargs):
self._na('sendall() method')
def recv_into(self, *args, **kwargs):
self._na('recv_into() method')
def recvfrom_into(self, *args, **kwargs):
self._na('recvfrom_into() method')
def recvmsg_into(self, *args, **kwargs):
self._na('recvmsg_into() method')
def recvmsg(self, *args, **kwargs):
self._na('recvmsg() method')
def recvfrom(self, *args, **kwargs):
self._na('recvfrom() method')
def recv(self, *args, **kwargs):
self._na('recv() method')
def settimeout(self, value):
if value == 0:
return
raise ValueError(
'settimeout(): only 0 timeout is allowed on transport sockets')
def gettimeout(self):
return 0
def setblocking(self, flag):
if not flag:
return
raise ValueError(
'setblocking(): transport sockets cannot be blocking')
def __enter__(self):
self._na('context manager protocol')
def __exit__(self, *err):
self._na('context manager protocol')

View File

View File

@ -0,0 +1,8 @@
cdef class UVRequest:
cdef:
uv.uv_req_t *request
bint done
Loop loop
cdef on_done(self)
cdef cancel(self)

View File

@ -0,0 +1,65 @@
cdef class UVRequest:
"""A base class for all libuv requests (uv_getaddrinfo_t, etc).
Important: it's a responsibility of the subclass to call the
"on_done" method in the request's callback.
If "on_done" isn't called, the request object will never die.
"""
def __cinit__(self, Loop loop, *_):
self.request = NULL
self.loop = loop
self.done = 0
Py_INCREF(self)
cdef on_done(self):
self.done = 1
Py_DECREF(self)
cdef cancel(self):
# Most requests are implemented using a threadpool. It's only
# possible to cancel a request when it's still in a threadpool's
# queue. Once it's started to execute, we have to wait until
# it finishes and calls its callback (and callback *must* call
# UVRequest.on_done).
cdef int err
if self.done == 1:
return
if UVLOOP_DEBUG:
if self.request is NULL:
raise RuntimeError(
'{}.cancel: .request is NULL'.format(
self.__class__.__name__))
if self.request.data is NULL:
raise RuntimeError(
'{}.cancel: .request.data is NULL'.format(
self.__class__.__name__))
if <UVRequest>self.request.data is not self:
raise RuntimeError(
'{}.cancel: .request.data is not UVRequest'.format(
self.__class__.__name__))
# We only can cancel pending requests. Let's try.
err = uv.uv_cancel(self.request)
if err < 0:
if err == uv.UV_EBUSY:
# Can't close the request -- it's executing (see the first
# comment). Loop will have to wait until the callback
# fires.
pass
elif err == uv.UV_EINVAL:
# From libuv docs:
#
# Only cancellation of uv_fs_t, uv_getaddrinfo_t,
# uv_getnameinfo_t and uv_work_t requests is currently
# supported.
return
else:
ex = convert_error(err)
self.loop._handle_exception(ex)

View File

@ -0,0 +1,19 @@
cdef class Server:
cdef:
list _servers
list _waiters
int _active_count
Loop _loop
bint _serving
object _serving_forever_fut
object __weakref__
cdef _add_server(self, UVStreamServer srv)
cdef _start_serving(self)
cdef _wakeup(self)
cdef _attach(self)
cdef _detach(self)
cdef _ref(self)
cdef _unref(self)

View File

@ -0,0 +1,136 @@
import asyncio
cdef class Server:
def __cinit__(self, Loop loop):
self._loop = loop
self._servers = []
self._waiters = []
self._active_count = 0
self._serving_forever_fut = None
cdef _add_server(self, UVStreamServer srv):
self._servers.append(srv)
cdef _start_serving(self):
if self._serving:
return
self._serving = 1
for server in self._servers:
(<UVStreamServer>server).listen()
cdef _wakeup(self):
cdef list waiters
waiters = self._waiters
self._waiters = None
for waiter in waiters:
if not waiter.done():
waiter.set_result(waiter)
cdef _attach(self):
assert self._servers is not None
self._active_count += 1
cdef _detach(self):
assert self._active_count > 0
self._active_count -= 1
if self._active_count == 0 and self._servers is None:
self._wakeup()
cdef _ref(self):
# Keep the server object alive while it's not explicitly closed.
self._loop._servers.add(self)
cdef _unref(self):
self._loop._servers.discard(self)
# Public API
@cython.iterable_coroutine
async def __aenter__(self):
return self
@cython.iterable_coroutine
async def __aexit__(self, *exc):
self.close()
await self.wait_closed()
def __repr__(self):
return '<%s sockets=%r>' % (self.__class__.__name__, self.sockets)
def get_loop(self):
return self._loop
@cython.iterable_coroutine
async def wait_closed(self):
# Do not remove `self._servers is None` below
# because close() method only closes server sockets
# and existing client connections are left open.
if self._servers is None or self._waiters is None:
return
waiter = self._loop._new_future()
self._waiters.append(waiter)
await waiter
def close(self):
cdef list servers
if self._servers is None:
return
try:
servers = self._servers
self._servers = None
self._serving = 0
for server in servers:
(<UVStreamServer>server)._close()
if self._active_count == 0:
self._wakeup()
finally:
self._unref()
def is_serving(self):
return self._serving
@cython.iterable_coroutine
async def start_serving(self):
self._start_serving()
@cython.iterable_coroutine
async def serve_forever(self):
if self._serving_forever_fut is not None:
raise RuntimeError(
f'server {self!r} is already being awaited on serve_forever()')
if self._servers is None:
raise RuntimeError(f'server {self!r} is closed')
self._start_serving()
self._serving_forever_fut = self._loop.create_future()
try:
await self._serving_forever_fut
except asyncio.CancelledError:
try:
self.close()
await self.wait_closed()
finally:
raise
finally:
self._serving_forever_fut = None
property sockets:
def __get__(self):
cdef list sockets = []
# Guard against `self._servers is None`
if self._servers:
for server in self._servers:
sockets.append(
(<UVStreamServer>server)._get_socket()
)
return sockets

View File

@ -0,0 +1,138 @@
cdef enum SSLProtocolState:
UNWRAPPED = 0
DO_HANDSHAKE = 1
WRAPPED = 2
FLUSHING = 3
SHUTDOWN = 4
cdef enum AppProtocolState:
# This tracks the state of app protocol (https://git.io/fj59P):
#
# INIT -cm-> CON_MADE [-dr*->] [-er-> EOF?] -cl-> CON_LOST
#
# * cm: connection_made()
# * dr: data_received()
# * er: eof_received()
# * cl: connection_lost()
STATE_INIT = 0
STATE_CON_MADE = 1
STATE_EOF = 2
STATE_CON_LOST = 3
cdef class _SSLProtocolTransport:
cdef:
Loop _loop
SSLProtocol _ssl_protocol
bint _closed
object context
cdef class SSLProtocol:
cdef:
bint _server_side
str _server_hostname
object _sslcontext
object _extra
object _write_backlog
size_t _write_buffer_size
object _waiter
Loop _loop
_SSLProtocolTransport _app_transport
bint _app_transport_created
object _transport
object _ssl_handshake_timeout
object _ssl_shutdown_timeout
object _sslobj
object _sslobj_read
object _sslobj_write
object _incoming
object _incoming_write
object _outgoing
object _outgoing_read
char* _ssl_buffer
size_t _ssl_buffer_len
object _ssl_buffer_view
SSLProtocolState _state
size_t _conn_lost
AppProtocolState _app_state
bint _ssl_writing_paused
bint _app_reading_paused
size_t _incoming_high_water
size_t _incoming_low_water
bint _ssl_reading_paused
bint _app_writing_paused
size_t _outgoing_high_water
size_t _outgoing_low_water
object _app_protocol
bint _app_protocol_is_buffer
object _app_protocol_get_buffer
object _app_protocol_buffer_updated
object _handshake_start_time
object _handshake_timeout_handle
object _shutdown_timeout_handle
cdef _set_app_protocol(self, app_protocol)
cdef _wakeup_waiter(self, exc=*)
cdef _get_extra_info(self, name, default=*)
cdef _set_state(self, SSLProtocolState new_state)
# Handshake flow
cdef _start_handshake(self)
cdef _check_handshake_timeout(self)
cdef _do_handshake(self)
cdef _on_handshake_complete(self, handshake_exc)
# Shutdown flow
cdef _start_shutdown(self, object context=*)
cdef _check_shutdown_timeout(self)
cdef _do_read_into_void(self, object context)
cdef _do_flush(self, object context=*)
cdef _do_shutdown(self, object context=*)
cdef _on_shutdown_complete(self, shutdown_exc)
cdef _abort(self, exc)
# Outgoing flow
cdef _write_appdata(self, list_of_data, object context)
cdef _do_write(self)
cdef _process_outgoing(self)
# Incoming flow
cdef _do_read(self)
cdef _do_read__buffered(self)
cdef _do_read__copied(self)
cdef _call_eof_received(self, object context=*)
# Flow control for writes from APP socket
cdef _control_app_writing(self, object context=*)
cdef size_t _get_write_buffer_size(self)
cdef _set_write_buffer_limits(self, high=*, low=*)
# Flow control for reads to APP socket
cdef _pause_reading(self)
cdef _resume_reading(self, object context)
# Flow control for reads from SSL socket
cdef _control_ssl_reading(self)
cdef _set_read_buffer_limits(self, high=*, low=*)
cdef size_t _get_read_buffer_size(self)
cdef _fatal_error(self, exc, message=*)

View File

@ -0,0 +1,950 @@
cdef _create_transport_context(server_side, server_hostname):
if server_side:
raise ValueError('Server side SSL needs a valid SSLContext')
# Client side may pass ssl=True to use a default
# context; in that case the sslcontext passed is None.
# The default is secure for client connections.
# Python 3.4+: use up-to-date strong settings.
sslcontext = ssl_create_default_context()
if not server_hostname:
sslcontext.check_hostname = False
return sslcontext
cdef class _SSLProtocolTransport:
# TODO:
# _sendfile_compatible = constants._SendfileMode.FALLBACK
def __cinit__(self, Loop loop, ssl_protocol, context):
self._loop = loop
# SSLProtocol instance
self._ssl_protocol = ssl_protocol
self._closed = False
if context is None:
context = Context_CopyCurrent()
self.context = context
def get_extra_info(self, name, default=None):
"""Get optional transport information."""
return self._ssl_protocol._get_extra_info(name, default)
def set_protocol(self, protocol):
self._ssl_protocol._set_app_protocol(protocol)
def get_protocol(self):
return self._ssl_protocol._app_protocol
def is_closing(self):
return self._closed
def close(self):
"""Close the transport.
Buffered data will be flushed asynchronously. No more data
will be received. After all buffered data is flushed, the
protocol's connection_lost() method will (eventually) called
with None as its argument.
"""
self._closed = True
self._ssl_protocol._start_shutdown(self.context.copy())
def __dealloc__(self):
if not self._closed:
self._closed = True
warnings_warn(
"unclosed transport <uvloop.loop._SSLProtocolTransport "
"object>", ResourceWarning)
def is_reading(self):
return not self._ssl_protocol._app_reading_paused
def pause_reading(self):
"""Pause the receiving end.
No data will be passed to the protocol's data_received()
method until resume_reading() is called.
"""
self._ssl_protocol._pause_reading()
def resume_reading(self):
"""Resume the receiving end.
Data received will once again be passed to the protocol's
data_received() method.
"""
self._ssl_protocol._resume_reading(self.context.copy())
def set_write_buffer_limits(self, high=None, low=None):
"""Set the high- and low-water limits for write flow control.
These two values control when to call the protocol's
pause_writing() and resume_writing() methods. If specified,
the low-water limit must be less than or equal to the
high-water limit. Neither value can be negative.
The defaults are implementation-specific. If only the
high-water limit is given, the low-water limit defaults to an
implementation-specific value less than or equal to the
high-water limit. Setting high to zero forces low to zero as
well, and causes pause_writing() to be called whenever the
buffer becomes non-empty. Setting low to zero causes
resume_writing() to be called only once the buffer is empty.
Use of zero for either limit is generally sub-optimal as it
reduces opportunities for doing I/O and computation
concurrently.
"""
self._ssl_protocol._set_write_buffer_limits(high, low)
self._ssl_protocol._control_app_writing(self.context.copy())
def get_write_buffer_limits(self):
return (self._ssl_protocol._outgoing_low_water,
self._ssl_protocol._outgoing_high_water)
def get_write_buffer_size(self):
"""Return the current size of the write buffers."""
return self._ssl_protocol._get_write_buffer_size()
def set_read_buffer_limits(self, high=None, low=None):
"""Set the high- and low-water limits for read flow control.
These two values control when to call the upstream transport's
pause_reading() and resume_reading() methods. If specified,
the low-water limit must be less than or equal to the
high-water limit. Neither value can be negative.
The defaults are implementation-specific. If only the
high-water limit is given, the low-water limit defaults to an
implementation-specific value less than or equal to the
high-water limit. Setting high to zero forces low to zero as
well, and causes pause_reading() to be called whenever the
buffer becomes non-empty. Setting low to zero causes
resume_reading() to be called only once the buffer is empty.
Use of zero for either limit is generally sub-optimal as it
reduces opportunities for doing I/O and computation
concurrently.
"""
self._ssl_protocol._set_read_buffer_limits(high, low)
self._ssl_protocol._control_ssl_reading()
def get_read_buffer_limits(self):
return (self._ssl_protocol._incoming_low_water,
self._ssl_protocol._incoming_high_water)
def get_read_buffer_size(self):
"""Return the current size of the read buffer."""
return self._ssl_protocol._get_read_buffer_size()
@property
def _protocol_paused(self):
# Required for sendfile fallback pause_writing/resume_writing logic
return self._ssl_protocol._app_writing_paused
def write(self, data):
"""Write some data bytes to the transport.
This does not block; it buffers the data and arranges for it
to be sent out asynchronously.
"""
if not isinstance(data, (bytes, bytearray, memoryview)):
raise TypeError(f"data: expecting a bytes-like instance, "
f"got {type(data).__name__}")
if not data:
return
self._ssl_protocol._write_appdata((data,), self.context.copy())
def writelines(self, list_of_data):
"""Write a list (or any iterable) of data bytes to the transport.
The default implementation concatenates the arguments and
calls write() on the result.
"""
self._ssl_protocol._write_appdata(list_of_data, self.context.copy())
def write_eof(self):
"""Close the write end after flushing buffered data.
This raises :exc:`NotImplementedError` right now.
"""
raise NotImplementedError
def can_write_eof(self):
"""Return True if this transport supports write_eof(), False if not."""
return False
def abort(self):
"""Close the transport immediately.
Buffered data will be lost. No more data will be received.
The protocol's connection_lost() method will (eventually) be
called with None as its argument.
"""
self._force_close(None)
def _force_close(self, exc):
self._closed = True
self._ssl_protocol._abort(exc)
def _test__append_write_backlog(self, data):
# for test only
self._ssl_protocol._write_backlog.append(data)
self._ssl_protocol._write_buffer_size += len(data)
cdef class SSLProtocol:
"""SSL protocol.
Implementation of SSL on top of a socket using incoming and outgoing
buffers which are ssl.MemoryBIO objects.
"""
def __cinit__(self, *args, **kwargs):
self._ssl_buffer_len = SSL_READ_MAX_SIZE
self._ssl_buffer = <char*>PyMem_RawMalloc(self._ssl_buffer_len)
if not self._ssl_buffer:
raise MemoryError()
self._ssl_buffer_view = PyMemoryView_FromMemory(
self._ssl_buffer, self._ssl_buffer_len, PyBUF_WRITE)
def __dealloc__(self):
self._ssl_buffer_view = None
PyMem_RawFree(self._ssl_buffer)
self._ssl_buffer = NULL
self._ssl_buffer_len = 0
def __init__(self, loop, app_protocol, sslcontext, waiter,
server_side=False, server_hostname=None,
call_connection_made=True,
ssl_handshake_timeout=None,
ssl_shutdown_timeout=None):
if ssl_handshake_timeout is None:
ssl_handshake_timeout = SSL_HANDSHAKE_TIMEOUT
elif ssl_handshake_timeout <= 0:
raise ValueError(
f"ssl_handshake_timeout should be a positive number, "
f"got {ssl_handshake_timeout}")
if ssl_shutdown_timeout is None:
ssl_shutdown_timeout = SSL_SHUTDOWN_TIMEOUT
elif ssl_shutdown_timeout <= 0:
raise ValueError(
f"ssl_shutdown_timeout should be a positive number, "
f"got {ssl_shutdown_timeout}")
if not sslcontext:
sslcontext = _create_transport_context(
server_side, server_hostname)
self._server_side = server_side
if server_hostname and not server_side:
self._server_hostname = server_hostname
else:
self._server_hostname = None
self._sslcontext = sslcontext
# SSL-specific extra info. More info are set when the handshake
# completes.
self._extra = dict(sslcontext=sslcontext)
# App data write buffering
self._write_backlog = col_deque()
self._write_buffer_size = 0
self._waiter = waiter
self._loop = loop
self._set_app_protocol(app_protocol)
self._app_transport = None
self._app_transport_created = False
# transport, ex: SelectorSocketTransport
self._transport = None
self._ssl_handshake_timeout = ssl_handshake_timeout
self._ssl_shutdown_timeout = ssl_shutdown_timeout
# SSL and state machine
self._sslobj = None
self._incoming = ssl_MemoryBIO()
self._incoming_write = self._incoming.write
self._outgoing = ssl_MemoryBIO()
self._outgoing_read = self._outgoing.read
self._state = UNWRAPPED
self._conn_lost = 0 # Set when connection_lost called
if call_connection_made:
self._app_state = STATE_INIT
else:
self._app_state = STATE_CON_MADE
# Flow Control
self._ssl_writing_paused = False
self._app_reading_paused = False
self._ssl_reading_paused = False
self._incoming_high_water = 0
self._incoming_low_water = 0
self._set_read_buffer_limits()
self._app_writing_paused = False
self._outgoing_high_water = 0
self._outgoing_low_water = 0
self._set_write_buffer_limits()
cdef _set_app_protocol(self, app_protocol):
self._app_protocol = app_protocol
if (hasattr(app_protocol, 'get_buffer') and
not isinstance(app_protocol, aio_Protocol)):
self._app_protocol_get_buffer = app_protocol.get_buffer
self._app_protocol_buffer_updated = app_protocol.buffer_updated
self._app_protocol_is_buffer = True
else:
self._app_protocol_is_buffer = False
cdef _wakeup_waiter(self, exc=None):
if self._waiter is None:
return
if not self._waiter.cancelled():
if exc is not None:
self._waiter.set_exception(exc)
else:
self._waiter.set_result(None)
self._waiter = None
def _get_app_transport(self, context=None):
if self._app_transport is None:
if self._app_transport_created:
raise RuntimeError('Creating _SSLProtocolTransport twice')
self._app_transport = _SSLProtocolTransport(self._loop, self,
context)
self._app_transport_created = True
return self._app_transport
def connection_made(self, transport):
"""Called when the low-level connection is made.
Start the SSL handshake.
"""
self._transport = transport
self._start_handshake()
def connection_lost(self, exc):
"""Called when the low-level connection is lost or closed.
The argument is an exception object or None (the latter
meaning a regular EOF is received or the connection was
aborted or closed).
"""
self._write_backlog.clear()
self._outgoing_read()
self._conn_lost += 1
# Just mark the app transport as closed so that its __dealloc__
# doesn't complain.
if self._app_transport is not None:
self._app_transport._closed = True
if self._state != DO_HANDSHAKE:
if self._app_state == STATE_CON_MADE or \
self._app_state == STATE_EOF:
self._app_state = STATE_CON_LOST
self._loop.call_soon(self._app_protocol.connection_lost, exc)
self._set_state(UNWRAPPED)
self._transport = None
self._app_transport = None
self._app_protocol = None
self._wakeup_waiter(exc)
if self._shutdown_timeout_handle:
self._shutdown_timeout_handle.cancel()
self._shutdown_timeout_handle = None
if self._handshake_timeout_handle:
self._handshake_timeout_handle.cancel()
self._handshake_timeout_handle = None
def get_buffer(self, n):
cdef size_t want = n
if want > SSL_READ_MAX_SIZE:
want = SSL_READ_MAX_SIZE
if self._ssl_buffer_len < want:
self._ssl_buffer = <char*>PyMem_RawRealloc(self._ssl_buffer, want)
if not self._ssl_buffer:
raise MemoryError()
self._ssl_buffer_len = want
self._ssl_buffer_view = PyMemoryView_FromMemory(
self._ssl_buffer, want, PyBUF_WRITE)
return self._ssl_buffer_view
def buffer_updated(self, nbytes):
self._incoming_write(PyMemoryView_FromMemory(
self._ssl_buffer, nbytes, PyBUF_WRITE))
if self._state == DO_HANDSHAKE:
self._do_handshake()
elif self._state == WRAPPED:
self._do_read()
elif self._state == FLUSHING:
self._do_flush()
elif self._state == SHUTDOWN:
self._do_shutdown()
def eof_received(self):
"""Called when the other end of the low-level stream
is half-closed.
If this returns a false value (including None), the transport
will close itself. If it returns a true value, closing the
transport is up to the protocol.
"""
try:
if self._loop.get_debug():
aio_logger.debug("%r received EOF", self)
if self._state == DO_HANDSHAKE:
self._on_handshake_complete(ConnectionResetError)
elif self._state == WRAPPED or self._state == FLUSHING:
# We treat a low-level EOF as a critical situation similar to a
# broken connection - just send whatever is in the buffer and
# close. No application level eof_received() is called -
# because we don't want the user to think that this is a
# graceful shutdown triggered by SSL "close_notify".
self._set_state(SHUTDOWN)
self._on_shutdown_complete(None)
elif self._state == SHUTDOWN:
self._on_shutdown_complete(None)
except Exception:
self._transport.close()
raise
cdef _get_extra_info(self, name, default=None):
if name == 'uvloop.sslproto':
return self
elif name in self._extra:
return self._extra[name]
elif self._transport is not None:
return self._transport.get_extra_info(name, default)
else:
return default
cdef _set_state(self, SSLProtocolState new_state):
cdef bint allowed = False
if new_state == UNWRAPPED:
allowed = True
elif self._state == UNWRAPPED and new_state == DO_HANDSHAKE:
allowed = True
elif self._state == DO_HANDSHAKE and new_state == WRAPPED:
allowed = True
elif self._state == WRAPPED and new_state == FLUSHING:
allowed = True
elif self._state == WRAPPED and new_state == SHUTDOWN:
allowed = True
elif self._state == FLUSHING and new_state == SHUTDOWN:
allowed = True
if allowed:
self._state = new_state
else:
raise RuntimeError(
'cannot switch state from {} to {}'.format(
self._state, new_state))
# Handshake flow
cdef _start_handshake(self):
if self._loop.get_debug():
aio_logger.debug("%r starts SSL handshake", self)
self._handshake_start_time = self._loop.time()
else:
self._handshake_start_time = None
self._set_state(DO_HANDSHAKE)
# start handshake timeout count down
self._handshake_timeout_handle = \
self._loop.call_later(self._ssl_handshake_timeout,
lambda: self._check_handshake_timeout())
try:
self._sslobj = self._sslcontext.wrap_bio(
self._incoming, self._outgoing,
server_side=self._server_side,
server_hostname=self._server_hostname)
self._sslobj_read = self._sslobj.read
self._sslobj_write = self._sslobj.write
except Exception as ex:
self._on_handshake_complete(ex)
else:
self._do_handshake()
cdef _check_handshake_timeout(self):
if self._state == DO_HANDSHAKE:
msg = (
f"SSL handshake is taking longer than "
f"{self._ssl_handshake_timeout} seconds: "
f"aborting the connection"
)
self._fatal_error(ConnectionAbortedError(msg))
cdef _do_handshake(self):
try:
self._sslobj.do_handshake()
except ssl_SSLAgainErrors as exc:
self._process_outgoing()
except ssl_SSLError as exc:
self._on_handshake_complete(exc)
else:
self._on_handshake_complete(None)
cdef _on_handshake_complete(self, handshake_exc):
if self._handshake_timeout_handle is not None:
self._handshake_timeout_handle.cancel()
self._handshake_timeout_handle = None
sslobj = self._sslobj
try:
if handshake_exc is None:
self._set_state(WRAPPED)
else:
raise handshake_exc
peercert = sslobj.getpeercert()
except Exception as exc:
self._set_state(UNWRAPPED)
if isinstance(exc, ssl_CertificateError):
msg = 'SSL handshake failed on verifying the certificate'
else:
msg = 'SSL handshake failed'
self._fatal_error(exc, msg)
self._wakeup_waiter(exc)
return
if self._loop.get_debug():
dt = self._loop.time() - self._handshake_start_time
aio_logger.debug("%r: SSL handshake took %.1f ms", self, dt * 1e3)
# Add extra info that becomes available after handshake.
self._extra.update(peercert=peercert,
cipher=sslobj.cipher(),
compression=sslobj.compression(),
ssl_object=sslobj)
if self._app_state == STATE_INIT:
self._app_state = STATE_CON_MADE
self._app_protocol.connection_made(self._get_app_transport())
self._wakeup_waiter()
# We should wakeup user code before sending the first data below. In
# case of `start_tls()`, the user can only get the SSLTransport in the
# wakeup callback, because `connection_made()` is not called again.
# We should schedule the first data later than the wakeup callback so
# that the user get a chance to e.g. check ALPN with the transport
# before having to handle the first data.
self._loop._call_soon_handle(
new_MethodHandle(self._loop,
"SSLProtocol._do_read",
<method_t> self._do_read,
None, # current context is good
self))
# Shutdown flow
cdef _start_shutdown(self, object context=None):
if self._state in (FLUSHING, SHUTDOWN, UNWRAPPED):
return
# we don't need the context for _abort or the timeout, because
# TCP transport._force_close() should be able to call
# connection_lost() in the right context
if self._app_transport is not None:
self._app_transport._closed = True
if self._state == DO_HANDSHAKE:
self._abort(None)
else:
self._set_state(FLUSHING)
self._shutdown_timeout_handle = \
self._loop.call_later(self._ssl_shutdown_timeout,
lambda: self._check_shutdown_timeout())
self._do_flush(context)
cdef _check_shutdown_timeout(self):
if self._state in (FLUSHING, SHUTDOWN):
self._transport._force_close(
aio_TimeoutError('SSL shutdown timed out'))
cdef _do_read_into_void(self, object context):
"""Consume and discard incoming application data.
If close_notify is received for the first time, call eof_received.
"""
cdef:
bint close_notify = False
try:
while True:
if not self._sslobj_read(SSL_READ_MAX_SIZE):
close_notify = True
break
except ssl_SSLAgainErrors as exc:
pass
except ssl_SSLZeroReturnError:
close_notify = True
if close_notify:
self._call_eof_received(context)
cdef _do_flush(self, object context=None):
"""Flush the write backlog, discarding new data received.
We don't send close_notify in FLUSHING because we still want to send
the remaining data over SSL, even if we received a close_notify. Also,
no application-level resume_writing() or pause_writing() will be called
in FLUSHING, as we could fully manage the flow control internally.
"""
try:
self._do_read_into_void(context)
self._do_write()
self._process_outgoing()
self._control_ssl_reading()
except Exception as ex:
self._on_shutdown_complete(ex)
else:
if not self._get_write_buffer_size():
self._set_state(SHUTDOWN)
self._do_shutdown(context)
cdef _do_shutdown(self, object context=None):
"""Send close_notify and wait for the same from the peer."""
try:
# we must skip all application data (if any) before unwrap
self._do_read_into_void(context)
try:
self._sslobj.unwrap()
except ssl_SSLAgainErrors as exc:
self._process_outgoing()
else:
self._process_outgoing()
if not self._get_write_buffer_size():
self._on_shutdown_complete(None)
except Exception as ex:
self._on_shutdown_complete(ex)
cdef _on_shutdown_complete(self, shutdown_exc):
if self._shutdown_timeout_handle is not None:
self._shutdown_timeout_handle.cancel()
self._shutdown_timeout_handle = None
# we don't need the context here because TCP transport.close() should
# be able to call connection_made() in the right context
if shutdown_exc:
self._fatal_error(shutdown_exc, 'Error occurred during shutdown')
else:
self._transport.close()
cdef _abort(self, exc):
self._set_state(UNWRAPPED)
if self._transport is not None:
self._transport._force_close(exc)
# Outgoing flow
cdef _write_appdata(self, list_of_data, object context):
if self._state in (FLUSHING, SHUTDOWN, UNWRAPPED):
if self._conn_lost >= LOG_THRESHOLD_FOR_CONNLOST_WRITES:
aio_logger.warning('SSL connection is closed')
self._conn_lost += 1
return
for data in list_of_data:
self._write_backlog.append(data)
self._write_buffer_size += len(data)
try:
if self._state == WRAPPED:
self._do_write()
self._process_outgoing()
self._control_app_writing(context)
except Exception as ex:
self._fatal_error(ex, 'Fatal error on SSL protocol')
cdef _do_write(self):
"""Do SSL write, consumes write backlog and fills outgoing BIO."""
cdef size_t data_len, count
try:
while self._write_backlog:
data = self._write_backlog[0]
count = self._sslobj_write(data)
data_len = len(data)
if count < data_len:
if not PyMemoryView_Check(data):
data = PyMemoryView_FromObject(data)
self._write_backlog[0] = data[count:]
self._write_buffer_size -= count
else:
del self._write_backlog[0]
self._write_buffer_size -= data_len
except ssl_SSLAgainErrors as exc:
pass
cdef _process_outgoing(self):
"""Send bytes from the outgoing BIO."""
if not self._ssl_writing_paused:
data = self._outgoing_read()
if len(data):
self._transport.write(data)
# Incoming flow
cdef _do_read(self):
if self._state != WRAPPED:
return
try:
if not self._app_reading_paused:
if self._app_protocol_is_buffer:
self._do_read__buffered()
else:
self._do_read__copied()
if self._write_backlog:
self._do_write()
self._process_outgoing()
self._control_app_writing()
self._control_ssl_reading()
except Exception as ex:
self._fatal_error(ex, 'Fatal error on SSL protocol')
cdef _do_read__buffered(self):
cdef:
Py_buffer pybuf
bint pybuf_inited = False
size_t wants, offset = 0
int count = 1
object buf
buf = self._app_protocol_get_buffer(self._get_read_buffer_size())
wants = len(buf)
try:
count = self._sslobj_read(wants, buf)
if count > 0:
offset = count
if offset < wants:
PyObject_GetBuffer(buf, &pybuf, PyBUF_WRITABLE)
pybuf_inited = True
while offset < wants:
buf = PyMemoryView_FromMemory(
(<char*>pybuf.buf) + offset,
wants - offset,
PyBUF_WRITE)
count = self._sslobj_read(wants - offset, buf)
if count > 0:
offset += count
else:
break
else:
self._loop._call_soon_handle(
new_MethodHandle(self._loop,
"SSLProtocol._do_read",
<method_t>self._do_read,
None, # current context is good
self))
except ssl_SSLAgainErrors as exc:
pass
finally:
if pybuf_inited:
PyBuffer_Release(&pybuf)
if offset > 0:
self._app_protocol_buffer_updated(offset)
if not count:
# close_notify
self._call_eof_received()
self._start_shutdown()
cdef _do_read__copied(self):
cdef:
list data
bytes first, chunk = b'1'
bint zero = True, one = False
try:
while True:
chunk = self._sslobj_read(SSL_READ_MAX_SIZE)
if not chunk:
break
if zero:
zero = False
one = True
first = chunk
elif one:
one = False
data = [first, chunk]
else:
data.append(chunk)
except ssl_SSLAgainErrors as exc:
pass
if one:
self._app_protocol.data_received(first)
elif not zero:
self._app_protocol.data_received(b''.join(data))
if not chunk:
# close_notify
self._call_eof_received()
self._start_shutdown()
cdef _call_eof_received(self, object context=None):
if self._app_state == STATE_CON_MADE:
self._app_state = STATE_EOF
try:
if context is None:
# If the caller didn't provide a context, we assume the
# caller is already in the right context, which is usually
# inside the upstream callbacks like buffer_updated()
keep_open = self._app_protocol.eof_received()
else:
keep_open = run_in_context(
context, self._app_protocol.eof_received,
)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as ex:
self._fatal_error(ex, 'Error calling eof_received()')
else:
if keep_open:
aio_logger.warning('returning true from eof_received() '
'has no effect when using ssl')
# Flow control for writes from APP socket
cdef _control_app_writing(self, object context=None):
cdef size_t size = self._get_write_buffer_size()
if size >= self._outgoing_high_water and not self._app_writing_paused:
self._app_writing_paused = True
try:
if context is None:
# If the caller didn't provide a context, we assume the
# caller is already in the right context, which is usually
# inside the upstream callbacks like buffer_updated()
self._app_protocol.pause_writing()
else:
run_in_context(context, self._app_protocol.pause_writing)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
self._loop.call_exception_handler({
'message': 'protocol.pause_writing() failed',
'exception': exc,
'transport': self._app_transport,
'protocol': self,
})
elif size <= self._outgoing_low_water and self._app_writing_paused:
self._app_writing_paused = False
try:
if context is None:
# If the caller didn't provide a context, we assume the
# caller is already in the right context, which is usually
# inside the upstream callbacks like resume_writing()
self._app_protocol.resume_writing()
else:
run_in_context(context, self._app_protocol.resume_writing)
except (KeyboardInterrupt, SystemExit):
raise
except BaseException as exc:
self._loop.call_exception_handler({
'message': 'protocol.resume_writing() failed',
'exception': exc,
'transport': self._app_transport,
'protocol': self,
})
cdef size_t _get_write_buffer_size(self):
return self._outgoing.pending + self._write_buffer_size
cdef _set_write_buffer_limits(self, high=None, low=None):
high, low = add_flowcontrol_defaults(
high, low, FLOW_CONTROL_HIGH_WATER_SSL_WRITE)
self._outgoing_high_water = high
self._outgoing_low_water = low
# Flow control for reads to APP socket
cdef _pause_reading(self):
self._app_reading_paused = True
cdef _resume_reading(self, object context):
if self._app_reading_paused:
self._app_reading_paused = False
if self._state == WRAPPED:
self._loop._call_soon_handle(
new_MethodHandle(self._loop,
"SSLProtocol._do_read",
<method_t>self._do_read,
context,
self))
# Flow control for reads from SSL socket
cdef _control_ssl_reading(self):
cdef size_t size = self._get_read_buffer_size()
if size >= self._incoming_high_water and not self._ssl_reading_paused:
self._ssl_reading_paused = True
self._transport.pause_reading()
elif size <= self._incoming_low_water and self._ssl_reading_paused:
self._ssl_reading_paused = False
self._transport.resume_reading()
cdef _set_read_buffer_limits(self, high=None, low=None):
high, low = add_flowcontrol_defaults(
high, low, FLOW_CONTROL_HIGH_WATER_SSL_READ)
self._incoming_high_water = high
self._incoming_low_water = low
cdef size_t _get_read_buffer_size(self):
return self._incoming.pending
# Flow control for writes to SSL socket
def pause_writing(self):
"""Called when the low-level transport's buffer goes over
the high-water mark.
"""
assert not self._ssl_writing_paused
self._ssl_writing_paused = True
def resume_writing(self):
"""Called when the low-level transport's buffer drains below
the low-water mark.
"""
assert self._ssl_writing_paused
self._ssl_writing_paused = False
if self._state == WRAPPED:
self._process_outgoing()
self._control_app_writing()
elif self._state == FLUSHING:
self._do_flush()
elif self._state == SHUTDOWN:
self._do_shutdown()
cdef _fatal_error(self, exc, message='Fatal error on transport'):
if self._app_transport:
self._app_transport._force_close(exc)
elif self._transport:
self._transport._force_close(exc)
if isinstance(exc, OSError):
if self._loop.get_debug():
aio_logger.debug("%r: %s", self, message, exc_info=True)
elif not isinstance(exc, aio_CancelledError):
self._loop.call_exception_handler({
'message': message,
'exception': exc,
'transport': self._transport,
'protocol': self,
})