import asyncio
import functools
import logging

import concurrent.futures


logger = logging.getLogger(__name__)


def get_loop(stop_on=()):
    '''
    Get the current asyncio loop. If none exists (e.g. not on the main thread)
    a new loop is created.
    :param stop_on:
    '''
    try:
        loop = asyncio.get_event_loop()
    except (AssertionError, RuntimeError):
        # unfortunately there is no way to tell if there is already an event loop
        # active apart from trying to get it, and catching the assertion error
        # raised if there is no active loop.
        loop = asyncio.new_event_loop()
        asyncio.set_event_loop(loop)
    if stop_on:
        for sig in stop_on:
            loop.add_signal_handler(sig, loop.stop)
    return loop


def async_call(loop, executor, method, *args, **kwargs):
    '''
    Provide a coroutine which can be yielded from, be it from a coroutine
    function or from a plain function / method. In the later case it is executed
    on the executor provided.
    :param loop: asyncio event loop
    :param executor: concurrent.futures.Executor
    :param method: coroutine function, function or method
    '''
    if asyncio.iscoroutinefunction(method):
        return method(*args, **kwargs)
    else:
        if kwargs:
            method = functools.partial(method, **kwargs)
        return loop.run_in_executor(executor, method, *args)


def run_coroutine_threadsafe(coro, loop):
    '''
    Run a asyncio coroutine in the given loop safely and return a
    concurrent.futures.Future
    :param coro: asyncio.couroutine
    :param loop: asyncio loop
    '''
    future = concurrent.futures.Future()
    scheduled = concurrent.futures.Future()

    def task_done(task):
        try:
            future.set_result(task.result())
        except Exception as exc:
            logger.debug('task failed', exc_info=True)
            future.set_exception(exc)


    def schedule_task():
        try:
            logger.debug('scheduling coro %s as task', coro)
            task = loop.create_task(coro)
            task.add_done_callback(task_done)
            scheduled.set_result(task)
        except Exception as exc:
            logger.exception('unable to schedule task')
            future.set_exception(exc)

    schedule_task_handle = loop.call_soon_threadsafe(schedule_task)

    def cancel_task():
        return scheduled.cancel()

    def future_done(future):
        try:
            schedule_task_handle.cancel()
            loop.call_soon_threadsafe(cancel_task)
        except Exception:
            logger.exception('canceling future failed')
    future.add_done_callback(future_done)

    return future


@asyncio.coroutine
def drain(writer):
    '''
    StreamWriter.drain only drains when there is more data buffered than the
    low_water mark. This method performs a full drain.
    '''
    if writer.transport.get_write_buffer_size():
        low, high = writer.transport.get_write_buffer_limits()
        writer.transport.set_write_buffer_limits(low=0)
        yield from writer.drain()
        writer.transport.set_write_buffer_limits(high, low)



@asyncio.coroutine
def readexactly(self, n):
    '''
    Implementation of StreamReader.readexactly which has an O(n) instead of
    O(2n) memory footprint (as is the case for asyncio in python 3.5.2). Also
    it is 25% to 35% faster in terms of throughput. See also
    https://github.com/python/asyncio/issues/394.
    '''
    if self._exception is not None:
        raise self._exception

    if not n:
        return b''

    if len(self._buffer) >= n and not self._eof:
        data = self._buffer[:n]
        del self._buffer[:n]
        self._maybe_resume_transport()
        return data

    data = bytearray(n)
    pos = 0

    while n:
        if not self._buffer:
            yield from self._wait_for_data('readexactly')

        if self._eof or not self._buffer:
            raise asyncio.IncompleteReadError(data[:pos], pos + n)

        available = len(self._buffer)
        if available <= n:
            data[pos:pos + available] = self._buffer
            self._buffer.clear()
            n -= available
            pos += available
        else:
            data[pos:pos + n] = self._buffer[:n]
            del self._buffer[:n]
            n = 0
        self._maybe_resume_transport()

    return data

@@ -1667,47 +1667,19 @@
     
-scheduled = concurrent.futures.Future()
+task = None
 %0A%0A  
@@ -1886,17 +1886,16 @@
 n(exc)%0A%0A
-%0A
     def 
@@ -1903,32 +1903,52 @@
 chedule_task():%0A
+        global task%0A
         try:%0A   
@@ -2028,29 +2028,45 @@
 k = 
-loop.create_task(coro
+asyncio.ensure_future(coro, loop=loop
 )%0A  
@@ -2113,47 +2113,8 @@
 ne)%0A
-            scheduled.set_result(task)%0A
     
@@ -2232,25 +2232,24 @@
 eption(exc)%0A
-%0A
     schedule
@@ -2317,87 +2317,67 @@
 def 
-cancel_task():%0A        return scheduled
+propagate_cancel(future):%0A        if future
 .cancel
-()%0A
+led:
 %0A    
-def future_done(future):%0A
     
@@ -2393,24 +2393,28 @@
             
+    
 schedule_tas
@@ -2431,16 +2431,49 @@
 ancel()%0A
+                if task:%0A        
         
@@ -2506,21 +2506,25 @@
 afe(
+task.
 cancel
-_task)%0A
+)%0A    
     
@@ -2541,24 +2541,28 @@
  Exception:%0A
+    
             
@@ -2638,19 +2638,24 @@
 ack(
-future_done
+propagate_cancel
 )%0A%0A 

clean impl