# cython: language_level=3


include "__debug.pxi" # Generated by "make"


cimport cython

from .includes cimport uv
from .includes cimport system
from .includes.python cimport PyMem_Malloc, PyMem_Free, \
                              PyMem_Calloc, PyMem_Realloc, \
                              PyUnicode_EncodeFSDefault, \
                              PyErr_SetInterrupt

from libc.stdint cimport uint64_t
from libc.string cimport memset, strerror
from libc cimport errno

from cpython cimport PyObject
from cpython cimport PyErr_CheckSignals, PyErr_Occurred
from cpython cimport PyThread_get_thread_ident
from cpython cimport Py_INCREF, Py_DECREF, Py_XDECREF, Py_XINCREF
from cpython cimport PyObject_GetBuffer, PyBuffer_Release, PyBUF_SIMPLE, \
                     Py_buffer, PyBytes_AsString
from cpython cimport PyErr_CheckSignals


include "includes/consts.pxi"
include "includes/stdlib.pxi"

include "errors.pyx"


cdef Loop __main_loop__ = None


@cython.no_gc_clear
cdef class Loop:
    def __cinit__(self):
        cdef int err

        # Install PyMem* memory allocators if they aren't installed yet.
        __install_pymem()

        self.uvloop = <uv.uv_loop_t*> \
                            PyMem_Malloc(sizeof(uv.uv_loop_t))
        if self.uvloop is NULL:
            raise MemoryError()

        self._closed = 0
        self._debug = 0
        self._thread_is_main = 0
        self._thread_id = 0
        self._running = 0
        self._stopping = 0

        self._custom_sigint = 0
        self._sigint_check = 0
        self._signal_handlers = dict()

        self._timers = set()
        self._polls = dict()
        self._polls_gc = dict()

        if MAIN_THREAD_ID == PyThread_get_thread_ident():  # XXX
            self.py_signals = SignalsStack()
            self.uv_signals = SignalsStack()

            self.py_signals.save()
        else:
            self.py_signals = None
            self.uv_signals = None

        self._executing_py_code = 0

        self._recv_buffer_in_use = 0

        err = uv.uv_loop_init(self.uvloop)
        if err < 0:
            raise convert_error(err)
        self.uvloop.data = <void*> self

        IF DEBUG:
            self._debug_cc = True

            self._debug_handles_current = col_Counter()
            self._debug_handles_closed = col_Counter()
            self._debug_handles_total = col_Counter()

            self._debug_stream_read_cb_total = 0
            self._debug_stream_read_eof_total = 0
            self._debug_stream_read_errors_total = 0
            self._debug_stream_read_cb_errors_total = 0
            self._debug_stream_read_eof_cb_errors_total = 0

            self._debug_stream_shutdown_errors_total = 0
            self._debug_stream_listen_errors_total = 0

            self._debug_stream_write_tries = 0
            self._debug_stream_write_errors_total = 0
            self._debug_stream_write_ctx_total = 0
            self._debug_stream_write_ctx_cnt = 0
            self._debug_stream_write_cb_errors_total = 0

            self._debug_cb_handles_total = 0
            self._debug_cb_handles_count = 0

            self._debug_cb_timer_handles_total = 0
            self._debug_cb_timer_handles_count = 0

            self._poll_read_events_total = 0
            self._poll_read_cb_errors_total = 0
            self._poll_write_events_total = 0
            self._poll_write_cb_errors_total = 0

            self._sock_try_read_total = 0
            self._sock_try_write_total = 0

            self._debug_exception_handler_cnt = 0

        self._last_error = None

        self._task_factory = None
        self._exception_handler = None
        self._default_executor = None

        self._ready = col_deque()
        self._ready_len = 0

        self.handler_async = UVAsync.new(
            self, <method_t*>&self._on_wake, self)

        self.handler_idle = UVIdle.new(
            self,
            new_MethodHandle(
                self, "loop._on_idle", <method_t*>&self._on_idle, self))

        self.handler_sigint = UVSignal.new(
            self,
            new_MethodHandle(
                self, "loop._on_sigint", <method_t*>&self._on_sigint, self),
            uv.SIGINT)

        self.handler_sighup = UVSignal.new(
            self,
            new_MethodHandle(
                self, "loop._on_sighup", <method_t*>&self._on_sighup, self),
            uv.SIGHUP)

        uv.uv_disable_stdio_inheritance()

        self._coroutine_wrapper_set = False

    def __init__(self):
        self.set_debug((not sys_ignore_environment
                        and bool(os_environ.get('PYTHONASYNCIODEBUG'))))

    def __dealloc__(self):
        if self._running == 1:
            raise RuntimeError('deallocating a running event loop!')
        if self._closed == 0:
            aio_logger.error("deallocating an open event loop")
            return
        PyMem_Free(self.uvloop)
        self.uvloop = NULL

    cdef _on_wake(self):
        if (self._ready_len > 0 or self._stopping) \
                            and not self.handler_idle.running:
            self.handler_idle.start()

    cdef _on_sigint(self):
        try:
            uvs = self._signal_handlers[uv.SIGINT]
        except KeyError:
            pass
        else:
            (<UVSignal>uvs).h._run()
            return

        try:
            PyErr_CheckSignals()
        except KeyboardInterrupt as ex:
            self._stop(ex)
        else:
            self._stop(KeyboardInterrupt())

    cdef _on_sighup(self):
        try:
            uvs = self._signal_handlers[uv.SIGHUP]
        except KeyError:
            pass
        else:
            (<UVSignal>uvs).h._run()
            return

        self._stop(SystemExit())

    cdef _check_sigint(self):
        self.uv_signals.save()
        __signal_set_sigint()

    cdef _on_idle(self):
        cdef:
            int i, ntodo
            object popleft = self._ready.popleft
            Handle handler

        if self._sigint_check == 0 and self._thread_is_main == 1:
            self._sigint_check = 1
            self._check_sigint()

        ntodo = len(self._ready)
        for i from 0 <= i < ntodo:
            handler = <Handle> popleft()
            if handler.cancelled == 0:
                try:
                    handler._run()
                except BaseException as ex:
                    self._stop(ex)
                    return

        if len(self._polls_gc):
            for fd in tuple(self._polls_gc):
                poll = <UVPoll> self._polls_gc[fd]
                if not poll.is_active():
                    poll._close()
                    self._polls.pop(fd)
                self._polls_gc.pop(fd)

        self._ready_len = len(self._ready)
        if self._ready_len == 0 and self.handler_idle.running:
            self.handler_idle.stop()

        if self._stopping:
            uv.uv_stop(self.uvloop)  # void

    cdef _stop(self, exc):
        if exc is not None:
            self._last_error = exc
        if self._stopping == 1:
            return
        self._stopping = 1
        if not self.handler_idle.running:
            self.handler_idle.start()

    cdef __run(self, uv.uv_run_mode mode):
        global __main_loop__

        if self.py_signals is not None:
            # self.py_signals is not None only for the main thread
            __main_loop__ = self

        self._executing_py_code = 0
        # Although every UVHandle holds a reference to the loop,
        # we want to do everything to ensure that the loop will
        # never deallocate during the run -- so we do some
        # manual refs management.
        Py_INCREF(self)
        with nogil:
            err = uv.uv_run(self.uvloop, mode)
        Py_DECREF(self)
        self._executing_py_code = 1

        if self.py_signals is not None:
            # self.py_signals is not None only for the main thread
            self.py_signals.restore()
            __main_loop__ = None

        if err < 0:
            raise convert_error(err)

    cdef _run(self, uv.uv_run_mode mode):
        cdef int err

        if self._closed == 1:
            raise RuntimeError('unable to start the loop; it was closed')

        if self._running == 1:
            raise RuntimeError('Event loop is running.')

        # reset _last_error
        self._last_error = None

        self._thread_id = PyThread_get_thread_ident()
        self._thread_is_main = MAIN_THREAD_ID == self._thread_id
        self._sigint_check = 0
        self._running = 1

        self.handler_idle.start()
        self.handler_sigint.start()
        self.handler_sighup.start()

        self.__run(mode)

        self.handler_idle.stop()
        self.handler_sigint.stop()
        self.handler_sighup.stop()

        self._sigint_check = 0
        self._thread_is_main = 0
        self._thread_id = 0
        self._running = 0
        self._stopping = 0

        if self._last_error is not None:
            # The loop was stopped with an error with 'loop._stop(error)' call
            raise self._last_error

    cdef _close(self):
        cdef int err

        if self._running == 1:
            raise RuntimeError("Cannot close a running event loop")

        if self._closed == 1:
            return

        self._closed = 1

        for cb_handle in self._ready:
            cb_handle.cancel()
        self._ready.clear()
        self._ready_len = 0

        if self._polls:
            for poll_handle in self._polls.values():
                (<UVHandle>poll_handle)._close()

            self._polls.clear()
            self._polls_gc.clear()

        if self._timers:
            for timer_cbhandle in tuple(self._timers):
                timer_cbhandle.cancel()

        for sig_handle in self._signal_handlers.values():
            (<UVSignal>sig_handle)._close()
        self._signal_handlers.clear()

        # Close all remaining handles
        self.handler_async._close()
        self.handler_idle._close()
        self.handler_sigint._close()
        self.handler_sighup._close()
        __close_all_handles(self)
        # During this run there should be no open handles,
        # so it should finish right away
        self.__run(uv.UV_RUN_DEFAULT)

        if self._timers:
            raise RuntimeError(
                "new timers were queued during loop closing: {}"
                    .format(self._timers))

        if self._polls:
            raise RuntimeError(
                "new poll handles were queued during loop closing: {}"
                    .format(self._polls))

        if self._ready:
            raise RuntimeError(
                "new callbacks were queued during loop closing: {}"
                    .format(self._ready))

        err = uv.uv_loop_close(self.uvloop)
        if err < 0:
            raise convert_error(err)

        self.handler_async = None
        self.handler_idle = None
        self.handler_sigint = None
        self.handler_sighup = None

        executor = self._default_executor
        if executor is not None:
            self._default_executor = None
            executor.shutdown(wait=False)

    cdef uint64_t _time(self):
        return uv.uv_now(self.uvloop)

    cdef inline _call_soon(self, object callback, object args):
        cdef Handle handle
        handle = new_Handle(self, callback, args)
        self._call_soon_handle(handle)
        return handle

    cdef inline _call_soon_handle(self, Handle handle):
        self._check_closed()
        self._ready.append(handle)
        self._ready_len += 1;
        if not self.handler_idle.running:
            self.handler_idle.start()

    cdef _call_later(self, uint64_t delay, object callback, object args):
        return TimerHandle(self, callback, args, delay)

    cdef void _handle_exception(self, object ex):
        if isinstance(ex, Exception):
            self.call_exception_handler({'exception': ex})
        else:
            # BaseException
            self._last_error = ex
            # Exit ASAP
            self._stop(None)

    cdef inline _check_signal(self, sig):
        if not isinstance(sig, int):
            raise TypeError('sig must be an int, not {!r}'.format(sig))

        if not (1 <= sig < signal_NSIG):
            raise ValueError(
                'sig {} out of range(1, {})'.format(sig, signal_NSIG))

    cdef inline _check_closed(self):
        if self._closed == 1:
            raise RuntimeError('Event loop is closed')

    cdef inline _check_thread(self):
        if self._thread_id == 0:
            return
        cdef long thread_id = PyThread_get_thread_ident()
        if thread_id != self._thread_id:
            raise RuntimeError(
                "Non-thread-safe operation invoked on an event loop other "
                "than the current one")

    cdef inline _new_future(self):
        return aio_Future(loop=self)

    cdef inline _add_reader(self, fd, Handle handle):
        cdef:
            UVPoll poll

        self._check_closed()

        try:
            poll = <UVPoll>(self._polls[fd])
        except KeyError:
            poll = UVPoll.new(self, fd)
            self._polls[fd] = poll

        poll.start_reading(handle)

    cdef inline _remove_reader(self, fd):
        cdef:
            UVPoll poll

        if self._closed == 1:
            return False

        try:
            poll = <UVPoll>(self._polls[fd])
        except KeyError:
            return False

        result = poll.stop_reading()
        if not poll.is_active():
            self._polls_gc[fd] = poll
        return result

    cdef inline _add_writer(self, fd, Handle handle):
        cdef:
            UVPoll poll

        self._check_closed()

        try:
            poll = <UVPoll>(self._polls[fd])
        except KeyError:
            poll = UVPoll.new(self, fd)
            self._polls[fd] = poll

        poll.start_writing(handle)

    cdef inline _remove_writer(self, fd):
        cdef:
            UVPoll poll

        if self._closed == 1:
            return False

        try:
            poll = <UVPoll>(self._polls[fd])
        except KeyError:
            return False

        result = poll.stop_writing()
        if not poll.is_active():
            self._polls_gc[fd] = poll
        return result

    cdef _getaddrinfo(self, object host, object port,
                      int family, int type,
                      int proto, int flags,
                      int unpack):

        if isinstance(port, str):
            port = port.encode()
        elif isinstance(port, int):
            port = str(port).encode()
        if port is not None and not isinstance(port, bytes):
            raise TypeError('port must be a str, bytes or int')

        if isinstance(host, str):
            host = host.encode('idna')
        if host is not None:
            if not isinstance(host, bytes):
                raise TypeError('host must be a str or bytes')

        fut = self._new_future()

        def callback(result):
            if AddrInfo.isinstance(result):
                try:
                    if unpack == 0:
                        data = result
                    else:
                        data = (<AddrInfo>result).unpack()
                except Exception as ex:
                    if not fut.cancelled():
                        fut.set_exception(ex)
                else:
                    if not fut.cancelled():
                        fut.set_result(data)
            else:
                fut.set_exception(result)

        AddrInfoRequest(self, host, port, family, type, proto, flags, callback)
        return fut

    cdef _getnameinfo(self, system.sockaddr *addr, int flags):
        cdef NameInfoRequest nr
        fut = self._new_future()

        def callback(result):
            if isinstance(result, tuple):
                fut.set_result(result)
            else:
                fut.set_exception(result)

        nr = NameInfoRequest(self, callback)
        nr.query(addr, flags)
        return fut

    cdef _sock_recv(self, fut, int registered, sock, n):
        # _sock_recv() can add itself as an I/O callback if the operation can't
        # be done immediately. Don't use it directly, call sock_recv().
        cdef:
            Handle handle

        fd = sock.fileno()
        if registered:
            # Remove the callback early.  It should be rare that the
            # selector says the fd is ready but the call still returns
            # EAGAIN, and I am willing to take a hit in that case in
            # order to simplify the common case.
            self._remove_reader(fd)
        if fut.cancelled():
            return
        try:
            data = sock.recv(n)
        except (BlockingIOError, InterruptedError):
            handle = new_MethodHandle4(
                self,
                "Loop._sock_recv",
                <method4_t*>&self._sock_recv,
                self,
                fut, 1, sock, n)

            self._add_reader(fd, handle)
        except Exception as exc:
            fut.set_exception(exc)
        else:
            IF DEBUG:
                if not registered:
                    self._sock_try_read_total += 1
            fut.set_result(data)

    cdef _sock_sendall(self, fut, int registered, sock, data):
        cdef:
            Handle handle

        fd = sock.fileno()

        if registered:
            self._remove_writer(fd)
        if fut.cancelled():
            return

        try:
            n = sock.send(data)
        except (BlockingIOError, InterruptedError):
            n = 0
        except Exception as exc:
            fut.set_exception(exc)
            return
        else:
            IF DEBUG:
                if not registered:
                    # This can be a partial success, i.e. only part
                    # of the data was sent
                    self._sock_try_write_total += 1

        if n == len(data):
            fut.set_result(None)
        else:
            if n:
                data = data[n:]

            handle = new_MethodHandle4(
                self,
                "Loop._sock_sendall",
                <method4_t*>&self._sock_sendall,
                self,
                fut, 1, sock, data)

            self._add_writer(fd, handle)

    cdef _sock_accept(self, fut, int registered, sock):
        cdef:
            Handle handle

        fd = sock.fileno()
        if registered:
            self._remove_reader(fd)
        if fut.cancelled():
            return
        try:
            conn, address = sock.accept()
            conn.setblocking(False)
        except (BlockingIOError, InterruptedError):
            handle = new_MethodHandle3(
                self,
                "Loop._sock_accept",
                <method3_t*>&self._sock_accept,
                self,
                fut, 1, sock)

            self._add_reader(fd, handle)
        except Exception as exc:
            fut.set_exception(exc)
        else:
            fut.set_result((conn, address))

    cdef _sock_connect(self, fut, sock, address):
        cdef:
            Handle handle

        fd = sock.fileno()
        try:
            sock.connect(address)
        except (BlockingIOError, InterruptedError):
            # Issue #23618: When the C function connect() fails with EINTR, the
            # connection runs in background. We have to wait until the socket
            # becomes writable to be notified when the connection succeed or
            # fails.
            fut.add_done_callback(lambda fut: self._remove_writer(fd))

            handle = new_MethodHandle3(
                self,
                "Loop._sock_connect",
                <method3_t*>&self._sock_connect_cb,
                self,
                fut, sock, address)

            self._add_writer(fd, handle)
        except Exception as exc:
            fut.set_exception(exc)
        else:
            fut.set_result(None)

    cdef _sock_connect_cb(self, fut, sock, address):
        if fut.cancelled():
            return

        try:
            err = sock.getsockopt(uv.SOL_SOCKET, uv.SO_ERROR)
            if err != 0:
                # Jump to any except clause below.
                raise OSError(err, 'Connect call failed %s' % (address,))
        except (BlockingIOError, InterruptedError):
            # socket is still registered, the callback will be retried later
            pass
        except Exception as exc:
            fut.set_exception(exc)
        else:
            fut.set_result(None)

    cdef _sock_set_reuseport(self, int fd):
        cdef:
            int err
            int reuseport_flag = 1

        err = system.setsockopt(
            fd,
            uv.SOL_SOCKET,
            SO_REUSEPORT,
            <char*>&reuseport_flag,
            sizeof(reuseport_flag))

        if err < 0:
            raise convert_error(-errno.errno)

    cdef _set_coroutine_wrapper(self, bint enabled):
        enabled = bool(enabled)
        if self._coroutine_wrapper_set == enabled:
            return

        wrapper = aio_debug_wrapper
        current_wrapper = sys_get_coroutine_wrapper()

        if enabled:
            if current_wrapper not in (None, wrapper):
                warnings.warn(
                    "loop.set_debug(True): cannot set debug coroutine "
                    "wrapper; another wrapper is already set %r" %
                    current_wrapper, RuntimeWarning)
            else:
                sys_set_coroutine_wrapper(wrapper)
                self._coroutine_wrapper_set = True
        else:
            if current_wrapper not in (None, wrapper):
                warnings.warn(
                    "loop.set_debug(False): cannot unset debug coroutine "
                    "wrapper; another wrapper was set %r" %
                    current_wrapper, RuntimeWarning)
            else:
                sys_set_coroutine_wrapper(None)
                self._coroutine_wrapper_set = False

    IF DEBUG:
        def print_debug_info(self):
            cdef:
                int err
                uv.uv_rusage_t rusage
            err = uv.uv_getrusage(&rusage)
            if err < 0:
                raise convert_error(err)

            ################### OS

            print('---- Process info: -----')
            print('Process memory:            {}'.format(rusage.ru_maxrss))
            print('Number of signals:         {}'.format(rusage.ru_nsignals))
            print('')

            ################### Loop

            print('--- Loop debug info: ---')
            print('Loop time:                 {}'.format(self.time()))
            print('Errors logged:             {}'.format(
                self._debug_exception_handler_cnt))
            print()
            print('Callback handles:          {: <8} | {}'.format(
                self._debug_cb_handles_count,
                self._debug_cb_handles_total))
            print('Timer handles:             {: <8} | {}'.format(
                self._debug_cb_timer_handles_count,
                self._debug_cb_timer_handles_total))
            print()

            print('                        alive  | closed  |')
            print('UVHandles               python | libuv   | total')
            print('                        objs   | handles |')
            print('-------------------------------+---------+---------')
            for name in sorted(self._debug_handles_total):
                print('    {: <18} {: >7} | {: >7} | {: >7}'.format(
                    name,
                    self._debug_handles_current[name],
                    self._debug_handles_closed[name],
                    self._debug_handles_total[name]))
            print()

            print('--- Streams debug info: ---')
            print('Write errors:              {}'.format(
                self._debug_stream_write_errors_total))
            print('Write without poll:        {}'.format(
                self._debug_stream_write_tries))
            print('Write contexts:            {: <8} | {}'.format(
                self._debug_stream_write_ctx_cnt,
                self._debug_stream_write_ctx_total))
            print('Write failed callbacks:    {}'.format(
                self._debug_stream_write_cb_errors_total))
            print()
            print('Read errors:               {}'.format(
                self._debug_stream_read_errors_total))
            print('Read callbacks:            {}'.format(
                self._debug_stream_read_cb_total))
            print('Read failed callbacks:     {}'.format(
                self._debug_stream_read_cb_errors_total))
            print('Read EOFs:                 {}'.format(
                self._debug_stream_read_eof_total))
            print('Read EOF failed callbacks: {}'.format(
                self._debug_stream_read_eof_cb_errors_total))
            print()
            print('Listen errors:             {}'.format(
                self._debug_stream_listen_errors_total))
            print('Shutdown errors            {}'.format(
                self._debug_stream_shutdown_errors_total))
            print()

            print('--- Polls debug info: ---')
            print('Read events:               {}'.format(
                self._poll_read_events_total))
            print('Read callbacks failed:     {}'.format(
                self._poll_read_cb_errors_total))
            print('Write events:              {}'.format(
                self._poll_write_events_total))
            print('Write callbacks failed:    {}'.format(
                self._poll_write_cb_errors_total))
            print()

            print('--- Sock ops successfull on 1st try: ---')
            print('Socket try-reads:          {}'.format(
                self._sock_try_read_total))
            print('Socket try-writes:         {}'.format(
                self._sock_try_write_total))

            print(flush=True)

    # Public API

    def __repr__(self):
        return '<{}.{} running={} closed={} debug={}>'.format(
                    self.__class__.__module__,
                    self.__class__.__name__,
                    self.is_running(),
                    self.is_closed(),
                    self.get_debug())

    def call_soon(self, callback, *args):
        if self._debug == 1:
            self._check_thread()
        if args:
            return self._call_soon(callback, args)
        else:
            return self._call_soon(callback, None)

    def call_soon_threadsafe(self, callback, *args):
        if not args:
            args = None
        handle = self._call_soon(callback, args)
        self.handler_async.send()
        return handle

    def call_later(self, delay, callback, *args):
        self._check_closed()
        if self._debug == 1:
            self._check_thread()
        if delay < 0:
            delay = 0
        cdef uint64_t when = <uint64_t>(delay * 1000)
        if not args:
            args = None
        if when == 0:
            return self._call_soon(callback, args)
        else:
            return self._call_later(when, callback, args)

    def call_at(self, when, callback, *args):
        return self.call_later(when - self.time(), callback, *args)

    def time(self):
        return self._time() / 1000

    def stop(self):
        self._call_soon_handle(
            new_MethodHandle1(
                self,
                "Loop._stop",
                <method1_t*>&self._stop,
                self,
                None))

    def run_forever(self):
        self._check_closed()
        mode = uv.UV_RUN_DEFAULT
        if self._stopping:
            # loop.stop() was called right before loop.run_forever().
            # This is how asyncio loop behaves.
            mode = uv.UV_RUN_NOWAIT
        self._set_coroutine_wrapper(self._debug)
        try:
            self._run(mode)
        finally:
            self._set_coroutine_wrapper(0)

    def close(self):
        self._close()

    def get_debug(self):
        return bool(self._debug)

    def set_debug(self, enabled):
        self._debug = bool(enabled)
        if self.is_running():
            self._set_coroutine_wrapper(self._debug)

    def is_running(self):
        return bool(self._running)

    def is_closed(self):
        return bool(self._closed)

    def create_task(self, coro):
        self._check_closed()
        if self._task_factory is None:
            task = aio_Task(coro, loop=self)
        else:
            task = self._task_factory(self, coro)
        return task

    def set_task_factory(self, factory):
        if factory is not None and not callable(factory):
            raise TypeError('task factory must be a callable or None')
        self._task_factory = factory

    def get_task_factory(self):
        return self._task_factory

    def run_until_complete(self, future):
        self._check_closed()

        new_task = not isinstance(future, aio_Future)
        future = aio_ensure_future(future, loop=self)
        if new_task:
            # An exception is raised if the future didn't complete, so there
            # is no need to log the "destroy pending task" message
            future._log_destroy_pending = False

        done_cb = lambda fut: self.stop()

        future.add_done_callback(done_cb)
        try:
            self.run_forever()
        except:
            if new_task and future.done() and not future.cancelled():
                # The coroutine raised a BaseException. Consume the exception
                # to not log a warning, the caller doesn't have access to the
                # local task.
                future.exception()
            raise
        future.remove_done_callback(done_cb)
        if not future.done():
            raise RuntimeError('Event loop stopped before Future completed.')

        return future.result()

    def getaddrinfo(self, object host, object port, *,
                    int family=0, int type=0, int proto=0, int flags=0):

        return self._getaddrinfo(host, port, family, type, proto, flags, 1)

    @aio_coroutine
    async def getnameinfo(self, sockaddr, int flags=0):
        cdef:
            AddrInfo ai_cnt
            system.addrinfo *ai
            system.sockaddr_in6 *sin6

        if not isinstance(sockaddr, tuple):
            raise TypeError('getnameinfo() argument 1 must be a tuple')

        sl = len(sockaddr)

        if sl < 2 or sl > 4:
            raise ValueError('sockaddr must be a tuple of 2, 3 or 4 values')

        if sl > 2:
            flowinfo = sockaddr[2]
            if flowinfo < 0 or flowinfo > 0xfffff:
                raise OverflowError(
                    'getsockaddrarg: flowinfo must be 0-1048575.')
        else:
            flowinfo = 0

        if sl > 3:
            scope_id = sockaddr[3]
            if scope_id < 0 or scope_id > 2 ** 32:
                raise OverflowError(
                    'getsockaddrarg: scope_id must be unsigned 32 bit integer')
        else:
            scope_id = 0

        ai_cnt = await self._getaddrinfo(
            sockaddr[0], sockaddr[1],
            uv.AF_UNSPEC,         # family
            uv.SOCK_DGRAM,        # type
            0,                    # proto
            uv.AI_NUMERICHOST,    # flags
            0)                    # unpack

        ai = ai_cnt.data

        if ai.ai_next:
            raise OSError("sockaddr resolved to multiple addresses")

        if ai.ai_family == uv.AF_INET:
            if sl > 2:
                raise OSError("IPv4 sockaddr must be 2 tuple")
        elif ai.ai_family == uv.AF_INET6:
            # Modify some fields in `ai`
            sin6 = <system.sockaddr_in6*> ai.ai_addr
            sin6.sin6_flowinfo = system.htonl(flowinfo)
            sin6.sin6_scope_id = scope_id

        return await self._getnameinfo(ai.ai_addr, flags)

    @aio_coroutine
    async def create_server(self, protocol_factory, host=None, port=None,
                            *,
                            int family=uv.AF_UNSPEC,
                            int flags=uv.AI_PASSIVE,
                            sock=None,
                            int backlog=100,
                            ssl=None,
                            reuse_address=None,  # ignored, libuv sets it
                            reuse_port=None):

        cdef:
            TCPServer tcp
            system.addrinfo *addrinfo
            Server server = Server(self)

        if ssl is not None and not isinstance(ssl, ssl_SSLContext):
            raise TypeError('ssl argument must be an SSLContext or None')

        if host is not None or port is not None:
            if sock is not None:
                raise ValueError(
                    'host/port and sock can not be specified at the same time')

            reuse_port = bool(reuse_port)
            if reuse_port and not has_SO_REUSEPORT:
                raise ValueError(
                    'reuse_port not supported by socket module')

            if host == '':
                hosts = [None]
            elif (isinstance(host, str) or not isinstance(host, col_Iterable)):
                hosts = [host]
            else:
                hosts = host

            fs = [self._getaddrinfo(host, port, family,
                                    uv.SOCK_STREAM, 0, flags,
                                    0) for host in hosts]

            infos = await aio_gather(*fs, loop=self)

            completed = False
            try:
                for info in infos:
                    addrinfo = (<AddrInfo>info).data
                    while addrinfo != NULL:
                        if addrinfo.ai_family == uv.AF_UNSPEC:
                            raise RuntimeError('AF_UNSPEC in DNS results')

                        tcp = TCPServer.new(
                            self, protocol_factory, server, ssl,
                            addrinfo.ai_family)

                        if reuse_port:
                            self._sock_set_reuseport(tcp._fileno())

                        try:
                            tcp.bind(addrinfo.ai_addr)
                            tcp.listen(backlog)
                        except OSError as err:
                            pyaddr = __convert_sockaddr_to_pyaddr(
                                <system.sockaddr*>addrinfo.ai_addr)
                            raise OSError(err.errno, 'error while attempting '
                                          'to bind on address %r: %s'
                                          % (pyaddr, err.strerror.lower()))

                        server._add_server(tcp)

                        addrinfo = addrinfo.ai_next

                completed = True
            finally:
                if not completed:
                    server.close()
        else:
            if sock is None:
                raise ValueError('Neither host/port nor sock were specified')
            tcp = TCPServer.new(self, protocol_factory, server, ssl,
                                uv.AF_UNSPEC)
            fileno = os_dup(sock.fileno())
            try:
                tcp.open(fileno)
                tcp._attach_fileobj(sock)
                tcp.listen(backlog)
                server._add_server(tcp)
            except:
                tcp._close()
                raise

        return server

    @aio_coroutine
    async def create_connection(self, protocol_factory, host=None, port=None, *,
                                ssl=None, family=0, proto=0, flags=0, sock=None,
                                local_addr=None, server_hostname=None):

        cdef:
            AddrInfo ai_local = None
            AddrInfo ai_remote
            system.addrinfo *rai
            system.addrinfo *lai
            TCPTransport tr

            object app_protocol
            object protocol
            object ssl_waiter

        app_protocol = protocol = protocol_factory()
        ssl_waiter = None
        if ssl:
            if server_hostname is None:
                if not host:
                    raise ValueError('You must set server_hostname '
                                     'when using ssl without a host')
                server_hostname = host

            ssl_waiter = self._new_future()
            sslcontext = None if isinstance(ssl, bool) else ssl
            protocol = aio_SSLProtocol(
                self, app_protocol, sslcontext, ssl_waiter,
                False, server_hostname)
        else:
            if server_hostname is not None:
                raise ValueError('server_hostname is only meaningful with ssl')

        if host is not None or port is not None:
            f1 = self._getaddrinfo(host, port, family,
                                   uv.SOCK_STREAM, proto, flags,
                                   0)  # 0 == don't unpack

            fs = [f1]

            f2 = None
            if local_addr is not None:
                if not isinstance(local_addr, (tuple, list)) or \
                        len(local_addr) != 2:
                    raise ValueError(
                        'local_addr must be a tuple of host and port')

                f2 = self._getaddrinfo(local_addr[0], local_addr[1], family,
                                       uv.SOCK_STREAM, proto, flags,
                                       0)  # 0 == don't unpack
                fs.append(f2)

            await aio_wait(fs, loop=self)

            ai_remote = f1.result()
            if ai_remote.data is NULL:
                raise OSError('getaddrinfo() returned empty list')

            if f2 is not None:
                ai_local = f2.result()
                if ai_local.data is NULL:
                    raise OSError(
                        'getaddrinfo() returned empty list for local_addr')

            exceptions = []
            rai = ai_remote.data
            while rai is not NULL:
                tr = None
                try:
                    waiter = self._new_future()
                    tr = TCPTransport.new(self, protocol, None, waiter)
                    if ai_local is not None:
                        lai = ai_local.data
                        while lai is not NULL:
                            try:
                                tr.bind(lai.ai_addr)
                                break
                            except OSError as exc:
                                exceptions.append(exc)
                            lai = lai.ai_next
                        else:
                            tr._close()
                            tr = None

                            rai = rai.ai_next
                            continue

                    tr.connect(rai.ai_addr)
                    await waiter

                except OSError as exc:
                    if tr is not None:
                        tr._close()
                        tr = None
                    exceptions.append(exc)
                except:
                    if tr is not None:
                        tr._close()
                        tr = None
                    raise
                else:
                    break

                rai = rai.ai_next

            else:
                # If they all have the same str(), raise one.
                model = str(exceptions[0])
                if all(str(exc) == model for exc in exceptions):
                    raise exceptions[0]
                # Raise a combined exception so the user can see all
                # the various error messages.
                raise OSError('Multiple exceptions: {}'.format(
                    ', '.join(str(exc) for exc in exceptions)))
        else:
            if sock is None:
                raise ValueError(
                    'host and port was not specified and no sock specified')

            waiter = self._new_future()
            protocol = protocol_factory()
            tr = TCPTransport.new(self, protocol, None, waiter)
            try:
                # libuv will make socket non-blocking
                fileno = os_dup(sock.fileno())
                tr.open(fileno)
                tr._attach_fileobj(sock)
                tr._init_protocol()
                await waiter
            except:
                tr._close()
                raise

        if ssl:
            await ssl_waiter
            return protocol._app_transport, app_protocol
        else:
            return tr, protocol

    @aio_coroutine
    async def create_unix_server(self, protocol_factory, str path=None,
                                 *, backlog=100, sock=None, ssl=None):

        cdef:
            UnixServer pipe
            Server server = Server(self)

        if ssl is not None and not isinstance(ssl, ssl_SSLContext):
            raise TypeError('ssl argument must be an SSLContext or None')

        pipe = UnixServer.new(self, protocol_factory, server, ssl)

        if path is not None:
            if sock is not None:
                raise ValueError(
                    'path and sock can not be specified at the same time')

            try:
                pipe.bind(path)
            except:
                pipe._close()
                raise

        else:
            if sock is None:
                raise ValueError(
                    'path was not specified, and no sock specified')

            if sock.family != uv.AF_UNIX:
                raise ValueError(
                    'A UNIX Domain Socket was expected, got {!r}'.format(sock))

            try:
                fileno = os_dup(sock.fileno())
                pipe.open(sock.fileno())
            except:
                pipe._close()
                raise

            pipe._attach_fileobj(sock)

        try:
            pipe.listen(backlog)
        except:
            pipe._close()
            raise

        server._add_server(pipe)
        return server

    @aio_coroutine
    async def create_unix_connection(self, protocol_factory, path, *,
                                     ssl=None, sock=None,
                                     server_hostname=None):

        cdef:
            UnixTransport tr
            object app_protocol
            object protocol
            object ssl_waiter

        app_protocol = protocol = protocol_factory()
        ssl_waiter = None
        if ssl:
            if server_hostname is None:
                raise ValueError('You must set server_hostname '
                                 'when using ssl without a host')

            ssl_waiter = self._new_future()
            sslcontext = None if isinstance(ssl, bool) else ssl
            protocol = aio_SSLProtocol(
                self, app_protocol, sslcontext, ssl_waiter,
                False, server_hostname)
        else:
            if server_hostname is not None:
                raise ValueError('server_hostname is only meaningful with ssl')

        if path is not None:
            if isinstance(path, str):
                path = PyUnicode_EncodeFSDefault(path)

            if sock is not None:
                raise ValueError(
                    'path and sock can not be specified at the same time')

            waiter = self._new_future()
            tr = UnixTransport.new(self, protocol, None, waiter)
            tr.connect(path)
            try:
                await waiter
            except:
                tr._close()
                raise

        else:
            if sock is None:
                raise ValueError('no path and sock were specified')

            if sock.family != uv.AF_UNIX:
                raise ValueError(
                    'A UNIX Domain Socket was expected, got {!r}'.format(sock))

            waiter = self._new_future()
            tr = UnixTransport.new(self, protocol, None, waiter)
            try:
                # libuv will make socket non-blocking
                fileno = os_dup(sock.fileno())
                tr.open(fileno)
                tr._attach_fileobj(sock)
                tr._init_protocol()
                await waiter
            except:
                tr._close()
                raise

        if ssl:
            await ssl_waiter
            return protocol._app_transport, app_protocol
        else:
            return tr, protocol

    def default_exception_handler(self, context):
        message = context.get('message')
        if not message:
            message = 'Unhandled exception in event loop'

        exception = context.get('exception')
        if exception is not None:
            exc_info = (type(exception), exception, exception.__traceback__)
        else:
            exc_info = False

        log_lines = [message]
        for key in sorted(context):
            if key in {'message', 'exception'}:
                continue
            value = context[key]
            value = repr(value)
            log_lines.append('{}: {}'.format(key, value))

        aio_logger.error('\n'.join(log_lines), exc_info=exc_info)

    def set_exception_handler(self, handler):
        if handler is not None and not callable(handler):
            raise TypeError('A callable object or None is expected, '
                            'got {!r}'.format(handler))
        self._exception_handler = handler

    def call_exception_handler(self, context):
        IF DEBUG:
            self._debug_exception_handler_cnt += 1

        if self._exception_handler is None:
            try:
                self.default_exception_handler(context)
            except Exception:
                # Second protection layer for unexpected errors
                # in the default implementation, as well as for subclassed
                # event loops with overloaded "default_exception_handler".
                aio_logger.error('Exception in default exception handler',
                                 exc_info=True)
        else:
            try:
                self._exception_handler(self, context)
            except Exception as exc:
                # Exception in the user set custom exception handler.
                try:
                    # Let's try default handler.
                    self.default_exception_handler({
                        'message': 'Unhandled error in exception handler',
                        'exception': exc,
                        'context': context,
                    })
                except Exception:
                    # Guard 'default_exception_handler' in case it is
                    # overloaded.
                    aio_logger.error('Exception in default exception handler '
                                     'while handling an unexpected error '
                                     'in custom exception handler',
                                     exc_info=True)

    def add_reader(self, fd, callback, *args):
        if len(args) == 0:
            args = None
        self._add_reader(fd, new_Handle(self, callback, args))

    def remove_reader(self, fd):
        self._remove_reader(fd)

    def add_writer(self, fd, callback, *args):
        if len(args) == 0:
            args = None
        self._add_writer(fd, new_Handle(self, callback, args))

    def remove_writer(self, fd):
        self._remove_writer(fd)

    def sock_recv(self, sock, n):
        if self._debug and sock.gettimeout() != 0:
            raise ValueError("the socket must be non-blocking")
        fut = self._new_future()
        self._sock_recv(fut, 0, sock, n)
        return fut

    def sock_sendall(self, sock, data):
        if self._debug and sock.gettimeout() != 0:
            raise ValueError("the socket must be non-blocking")
        fut = self._new_future()
        if data:
            self._sock_sendall(fut, 0, sock, data)
        else:
            fut.set_result(None)
        return fut

    def sock_accept(self, sock):
        if self._debug and sock.gettimeout() != 0:
            raise ValueError("the socket must be non-blocking")
        fut = self._new_future()
        self._sock_accept(fut, 0, sock)
        return fut

    def sock_connect(self, sock, address):
        if self._debug and sock.gettimeout() != 0:
            raise ValueError("the socket must be non-blocking")
        fut = self._new_future()
        try:
            if self._debug:
                aio__check_resolved_address(sock, address)
        except ValueError as err:
            fut.set_exception(err)
        else:
            self._sock_connect(fut, sock, address)
        return fut

    def run_in_executor(self, executor, func, *args):
        if aio_iscoroutine(func) or aio_iscoroutinefunction(func):
            raise TypeError("coroutines cannot be used with run_in_executor()")

        self._check_closed()

        if executor is None:
            executor = self._default_executor
            if executor is None:
                executor = cc_ThreadPoolExecutor(MAX_THREADPOOL_WORKERS)
                self._default_executor = executor

        return aio_wrap_future(executor.submit(func, *args), loop=self)

    def set_default_executor(self, executor):
        self._default_executor = executor

    @aio_coroutine
    async def __subprocess_run(self, protocol_factory, args,
                               stdin=subprocess_PIPE,
                               stdout=subprocess_PIPE,
                               stderr=subprocess_PIPE,
                               universal_newlines=False,
                               shell=True,
                               bufsize=0,
                               preexec_fn=None,
                               close_fds=None,
                               cwd=None,
                               env=None,
                               startupinfo=None,
                               creationflags=0,
                               restore_signals=True,
                               start_new_session=False,
                               executable=None,
                               pass_fds=(),

                               # For tests only! Do not use in your code. Ever.
                               __uvloop_sleep_after_fork=False
                            ):

        # TODO: To implement close_fds, restore_signals, and preexec_fn --
        # we'll likely need to add new functionality to libuv, mainly,
        # an ability to run a callback before calling execvp in the
        # child process.

        cdef:
            int debug_flags = 0

        if preexec_fn is not None:
            raise ValueError('preexec_fn parameter is not supported')
        if universal_newlines:
            raise ValueError("universal_newlines must be False")
        if bufsize != 0:
            raise ValueError("bufsize must be 0")
        if preexec_fn:
            raise ValueError('preexec_fn is not supported')
        if startupinfo is not None:
            raise ValueError('startupinfo is not supported')
        if creationflags != 0:
            raise ValueError('creationflags is not supported')

        if executable is not None:
            args[0] = executable

        if __uvloop_sleep_after_fork:
            debug_flags |= __PROCESS_DEBUG_SLEEP_AFTER_FORK

        waiter = self._new_future()
        protocol = protocol_factory()
        proc = UVProcessTransport.new(self, protocol,
                                      args, env, cwd, start_new_session,
                                      stdin, stdout, stderr, pass_fds,
                                      waiter,
                                      debug_flags)

        try:
            await waiter
        except:
            proc.close()
            raise

        return proc, protocol

    def subprocess_shell(self, protocol_factory, cmd, *,
                         shell=True,
                         **kwargs):

        if not shell:
            raise ValueError("shell must be True")

        args = [cmd]
        if shell:
            args = [b'/bin/sh', b'-c'] + args

        return self.__subprocess_run(protocol_factory, args, shell=True,
                                     **kwargs)

    def subprocess_exec(self,  protocol_factory, program, *args,
                        shell=False, **kwargs):

        if shell:
            raise ValueError("shell must be False")

        args = list((program,) + args)

        return self.__subprocess_run(protocol_factory, args, shell=False,
                                     **kwargs)

    @aio_coroutine
    async def connect_read_pipe(self, proto_factory, pipe):
        cdef:
            ReadUnixTransport transp
            int fileno = os_dup(pipe.fileno())

        waiter = self._new_future()
        proto = proto_factory()
        transp = ReadUnixTransport.new(self, proto, None, waiter)
        transp._add_extra_info('pipe', pipe)
        transp._attach_fileobj(pipe)
        try:
            transp.open(fileno)
            transp._init_protocol()
            await waiter
        except:
            transp.close()
            raise
        return transp, proto

    @aio_coroutine
    async def connect_write_pipe(self, proto_factory, pipe):
        cdef:
            WriteUnixTransport transp
            int fileno = os_dup(pipe.fileno())

        waiter = self._new_future()
        proto = proto_factory()
        transp = WriteUnixTransport.new(self, proto, None, waiter)
        transp._add_extra_info('pipe', pipe)
        transp._attach_fileobj(pipe)
        try:
            transp.open(fileno)
            transp._init_protocol()
            await waiter
        except:
            transp.close()
            raise
        return transp, proto

    def add_signal_handler(self, sig, callback, *args):
        cdef:
            Handle h
            UVSignal uvs

        if (aio_iscoroutine(callback)
                or aio_iscoroutinefunction(callback)):
            raise TypeError("coroutines cannot be used "
                            "with add_signal_handler()")

        self._check_signal(sig)
        self._check_closed()

        if sig in self._signal_handlers:
            IF DEBUG:
                assert isinstance(self._signal_handlers[sig], UVSignal)
            uvs = self._signal_handlers[sig]
            try:
                uvs.stop()
            finally:
                uvs._close()

        h = new_Handle(self, callback, args)
        uvs = UVSignal.new(self, h, sig)

        self._signal_handlers[sig] = uvs

        if sig == uv.SIGINT:
            # Needed for __signal_handler_sigint
            self._custom_sigint = 1
            return
        if sig == uv.SIGHUP:
            # loop._on_sigint and loop._on_sighup will handle
            # any signals installed by user
            return

        try:
            uvs.start()
        except OSError as exc:
            del self._signal_handlers[sig]
            if exc.errno == errno.EINVAL:
                raise RuntimeError('sig {} cannot be caught'.format(sig))
            else:
                raise

    def remove_signal_handler(self, sig):
        cdef:
            UVSignal uvs

        self._check_signal(sig)

        if sig not in self._signal_handlers:
            return False

        if sig == uv.SIGINT:
            self._custom_sigint = 0

        uvs = <UVSignal>(self._signal_handlers.pop(sig))
        try:
            uvs.stop()
        finally:
            uvs._close()

        return True

    @aio_coroutine
    async def create_datagram_endpoint(self, protocol_factory,
                                       local_addr=None, remote_addr=None, *,
                                       family=0, proto=0, flags=0,
                                       reuse_address=None, reuse_port=None,
                                       allow_broadcast=None, sock=None):

        cdef:
            UDPTransport udp = None
            system.addrinfo * lai
            system.addrinfo * rai

        if sock is not None:
            if (local_addr or remote_addr or
                    family or proto or flags or
                    reuse_address or reuse_port or allow_broadcast):
                # show the problematic kwargs in exception msg
                opts = dict(local_addr=local_addr, remote_addr=remote_addr,
                            family=family, proto=proto, flags=flags,
                            reuse_address=reuse_address, reuse_port=reuse_port,
                            allow_broadcast=allow_broadcast)
                problems = ', '.join(
                    '{}={}'.format(k, v) for k, v in opts.items() if v)
                raise ValueError(
                    'socket modifier keyword arguments can not be used '
                    'when sock is specified. ({})'.format(problems))
            sock.setblocking(False)
            udp = UDPTransport.__new__(UDPTransport)
            udp._init(self, uv.AF_UNSPEC)
            udp._open(sock.family, sock.fileno())
            udp._attach_fileobj(sock)
        else:
            reuse_address = bool(reuse_address)
            reuse_port = bool(reuse_port)
            if reuse_port and not has_SO_REUSEPORT:
                raise ValueError(
                    'reuse_port not supported by socket module')

            lads = None
            if local_addr is not None:
                if (not isinstance(local_addr, (tuple, list)) or
                        len(local_addr) != 2):
                    raise TypeError(
                        'local_addr must be a tuple of (host, port)')
                lads = await self._getaddrinfo(
                    local_addr[0], local_addr[1],
                    family, uv.SOCK_DGRAM, proto, flags,
                    0)

            rads = None
            if remote_addr is not None:
                if (not isinstance(remote_addr, (tuple, list)) or
                        len(remote_addr) != 2):
                    raise TypeError(
                        'remote_addr must be a tuple of (host, port)')
                rads = await self._getaddrinfo(
                    remote_addr[0], remote_addr[1],
                    family, uv.SOCK_DGRAM, proto, flags,
                    0)

            excs = []
            if lads is None:
                if rads is not None:
                    udp = UDPTransport.__new__(UDPTransport)
                    rai = (<AddrInfo>rads).data
                    udp._init(self, rai.ai_family)
                    udp._set_remote_address(rai.ai_addr[0])
                else:
                    if family not in (uv.AF_INET, uv.AF_INET6):
                        raise ValueError('unexpected address family')
                    udp = UDPTransport.__new__(UDPTransport)
                    udp._init(self, family)

                if reuse_port:
                    self._sock_set_reuseport(udp._fileno())

                socket = udp._get_socket()
                socket.bind(('0.0.0.0', 0))
            else:
                lai = (<AddrInfo>lads).data
                while lai is not NULL:
                    try:
                        udp = UDPTransport.__new__(UDPTransport)
                        udp._init(self, lai.ai_family)
                        if reuse_port:
                            self._sock_set_reuseport(udp._fileno())
                        udp._bind(lai.ai_addr, reuse_address)
                    except Exception as ex:
                        lai = lai.ai_next
                        excs.append(ex)
                        continue
                    else:
                        break
                else:
                    ctx = None
                    if len(excs):
                        ctx = excs[0]
                    raise OSError('could not bind to local_addr {}'.format(
                        local_addr)) from ctx

                if rads is not None:
                    rai = (<AddrInfo>lads).data
                    sock = udp._get_socket()
                    while rai is not NULL:
                        if rai.ai_family != lai.ai_family:
                            rai = rai.ai_next
                            continue
                        if rai.ai_protocol != lai.ai_protocol:
                            rai = rai.ai_next
                            continue
                        udp._set_remote_address(rai.ai_addr[0])
                        break
                    else:
                        raise OSError(
                            'could not bind to remote_addr {}'.format(
                                remote_addr))

                    udp._set_remote_address(rai.ai_addr[0])

        if allow_broadcast:
            udp._set_broadcast(1)

        protocol = protocol_factory()
        waiter = self._new_future()
        assert udp is not None
        udp._set_protocol(protocol)
        udp._set_waiter(waiter)
        udp._init_protocol()

        await waiter
        return udp, protocol


cdef void __loop_alloc_buffer(uv.uv_handle_t* uvhandle,
                              size_t suggested_size,
                              uv.uv_buf_t* buf) with gil:
    cdef:
        Loop loop = (<UVHandle>uvhandle.data)._loop

    if loop._recv_buffer_in_use == 1:
        buf.len = 0
        exc = RuntimeError('concurrent allocations')
        loop._handle_exception(exc)
        return

    loop._recv_buffer_in_use = 1
    buf.base = loop._recv_buffer
    buf.len = sizeof(loop._recv_buffer)


cdef inline void __loop_free_buffer(Loop loop):
    loop._recv_buffer_in_use = 0


include "cbhandles.pyx"

include "handles/handle.pyx"
include "handles/async_.pyx"
include "handles/idle.pyx"
include "handles/timer.pyx"
include "handles/signal.pyx"
include "handles/poll.pyx"
include "handles/basetransport.pyx"
include "handles/stream.pyx"
include "handles/streamserver.pyx"
include "handles/tcp.pyx"
include "handles/pipe.pyx"
include "handles/process.pyx"

include "request.pyx"
include "dns.pyx"

include "handles/udp.pyx"

include "server.pyx"

include "os_signal.pyx"


# Install PyMem* memory allocators
cdef vint __mem_installed = 0
cdef __install_pymem():
    global __mem_installed
    if __mem_installed:
        return
    __mem_installed = 1

    cdef int err
    err = uv.uv_replace_allocator(<uv.uv_malloc_func>PyMem_Malloc,
                                  <uv.uv_realloc_func>PyMem_Realloc,
                                  <uv.uv_calloc_func>PyMem_Calloc,
                                  <uv.uv_free_func>PyMem_Free)
    if err < 0:
        __mem_installed = 0
        raise convert_error(err)
