# Copyright (c) 2015 Canonical Ltd.
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.

""" Async Handler
Provides async operations for various api calls and other non-blocking
work.
"""

import logging
from concurrent.futures import ThreadPoolExecutor
import os
import subprocess
import errno
import fcntl
from tornado.ioloop import IOLoop

BUFSIZ = 1024

log = logging.getLogger("async")


AsyncPool = ThreadPoolExecutor(1)
log.debug('AsyncPool={}'.format(AsyncPool))


def _set_nonblocking(fd):
    flags = fcntl.fcntl(fd, fcntl.F_GETFL)
    fcntl.fcntl(fd, fcntl.F_SETFL, flags | os.O_NONBLOCK)


def check_output(call, callback):
    """ A non-blocking version of subprocess.check_output
    on the tornado ioloop.
    """
    ioloop = IOLoop.instance()
    out_r, out_w = os.pipe()
    nul_f = open(os.devnull, 'w')
    p = subprocess.Popen(call, stdout=out_w, stderr=nul_f)
    nul_f.close()
    os.close(out_w)
    _set_nonblocking(out_r)
    data = []

    def _poll():
        if p.returncode is None:
            p.poll()

        if p.returncode is not None:
            ioloop.remove_handler(out_r)
            os.close(out_r)

            callback("".join(data), p.returncode)

    def _handle_read(fd, events):
        if events & ioloop.READ:
            try:
                buf = os.read(out_r, BUFSIZ)
            except (IOError, OSError) as e:
                if e.args[0] == errno.EBADF:
                    _poll()
                elif e.args[0] not in (errno.EWOULDBLOCK, errno.EAGAIN):
                    raise

            if not buf:
                _poll()
            else:
                data.append(buf)

        if events & ioloop.ERROR:
            _poll()

    ioloop.add_handler(out_r, _handle_read,
                       ioloop.ERROR | ioloop.READ)

@@ -1256,112 +1256,8 @@
 tor%0A
-import os%0Aimport subprocess%0Aimport errno%0Aimport fcntl%0Afrom tornado.ioloop import IOLoop%0A%0ABUFSIZ = 1024%0A%0A
 log 
@@ -1375,566 +1375,83 @@
 def 
-_set_nonblocking(fd):%0A    flags = fcntl.fcntl(fd, fcntl.F_GETFL)%0A    fcntl.fcntl(fd, fcntl.F_SETFL, flags %7C os.O_NONBLOCK)%0A%0A%0Adef check_output(call, callback):%0A    %22%22%22 A non-blocking version of subprocess.check_output%0A    on the tornado ioloop.%0A    %22%22%22%0A    ioloop = IOLoop.instance()%0A    out_r, out_w = os.pipe()%0A    nul_f = open(os.devnull, 'w')%0A    p = subprocess.Popen(call, stdout=out_w, stderr=nul
+submit(func, exc_callback):%0A    def cb(cb
 _f)
+:
 %0A    
-nul_f.close()%0A    os.close(out_w)%0A    _set_nonblocking(out_r)%0A    data = %5B%5D%0A%0A    def _poll():%0A        if p.returncode is None:%0A            p.poll
+    e = cb_f.exception
 ()%0A
-%0A
     
@@ -1461,747 +1461,94 @@
  if 
-p.returncode is not None:%0A            ioloop.remove_handler(out_r)%0A            os.close(out_r)%0A%0A            callback(%22%22.join(data), p.returncod
+e:%0A            exc_callback(
 e)%0A
-%0A
     
-def _handle_read(fd, events):%0A        if events & ioloop.READ:%0A            try:%0A                buf = os.read(out_r, BUFSIZ)%0A            except (IOError, OSError) as e:%0A                if e.args%5B0%5D == errno.EBADF:%0A                    _poll()%0A                elif e.args%5B0%5D not in (errno.EWOULDBLOCK, errno.EAGAIN):%0A                    raise%0A%0A            if not buf:%0A                _poll()%0A            else:%0A                data.append(buf)%0A%0A        if events & ioloop.ERROR:%0A            _poll()%0A%0A    ioloop.add_handler(out_r, _handle_read,%0A                       ioloop.ERROR %7C ioloop.READ
+f = AsyncPool.submit(func)%0A    f.add_done_callback(cb
 )%0A

fix async