Beispiel #1
0
    def __init__(
        self,
        handler,
        opts=None,
        plugins=(),
        # A calibre logging object. If None, a default log that logs to
        # stdout is used
        log=None,
        # A calibre logging object for access logging, by default no access
        # logging is performed
        access_log=None):
        self.ready = False
        self.handler = handler
        self.opts = opts or Options()
        if self.opts.trusted_ips:
            self.opts.trusted_ips = tuple(
                parse_trusted_ips(self.opts.trusted_ips))
        self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
        self.jobs_manager = JobsManager(self.opts, self.log)
        self.access_log = access_log

        ba = (self.opts.listen_on, int(self.opts.port))
        if not ba[0]:
            # AI_PASSIVE does not work with host of '' or None
            ba = ('0.0.0.0', ba[1])
        self.bind_address = ba
        self.bound_address = None
        self.connection_map = {}

        self.ssl_context = None
        if self.opts.ssl_certfile is not None and self.opts.ssl_keyfile is not None:
            self.ssl_context = ssl.create_default_context(
                ssl.Purpose.CLIENT_AUTH)
            self.ssl_context.load_cert_chain(certfile=self.opts.ssl_certfile,
                                             keyfile=self.opts.ssl_keyfile)
            self.ssl_context.set_servername_callback(self.on_ssl_servername)

        self.pre_activated_socket = None
        if self.opts.allow_socket_preallocation:
            from calibre.srv.pre_activated import pre_activated_socket
            self.pre_activated_socket = pre_activated_socket()
            if self.pre_activated_socket is not None:
                set_socket_inherit(self.pre_activated_socket, False)
                self.bind_address = self.pre_activated_socket.getsockname()

        self.create_control_connection()
        self.pool = ThreadPool(self.log,
                               self.job_completed,
                               count=self.opts.worker_count)
        self.plugin_pool = PluginPool(self, plugins)
Beispiel #2
0
    def __init__(
        self,
        handler,
        opts=None,
        plugins=(),
        # A calibre logging object. If None, a default log that logs to
        # stdout is used
        log=None,
        # A calibre logging object for access logging, by default no access
        # logging is performed
        access_log=None
    ):
        self.ready = False
        self.handler = handler
        self.opts = opts or Options()
        self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
        self.jobs_manager = JobsManager(self.opts, self.log)
        self.access_log = access_log

        ba = (self.opts.listen_on, int(self.opts.port))
        if not ba[0]:
            # AI_PASSIVE does not work with host of '' or None
            ba = ('0.0.0.0', ba[1])
        self.bind_address = ba
        self.bound_address = None
        self.connection_map = {}

        self.ssl_context = None
        if self.opts.ssl_certfile is not None and self.opts.ssl_keyfile is not None:
            self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            self.ssl_context.load_cert_chain(certfile=self.opts.ssl_certfile, keyfile=self.opts.ssl_keyfile)
            self.ssl_context.set_servername_callback(self.on_ssl_servername)

        self.pre_activated_socket = None
        if self.opts.allow_socket_preallocation:
            from calibre.srv.pre_activated import pre_activated_socket
            self.pre_activated_socket = pre_activated_socket()
            if self.pre_activated_socket is not None:
                set_socket_inherit(self.pre_activated_socket, False)
                self.bind_address = self.pre_activated_socket.getsockname()

        self.create_control_connection()
        self.pool = ThreadPool(self.log, self.job_completed, count=self.opts.worker_count)
        self.plugin_pool = PluginPool(self, plugins)
Beispiel #3
0
    def test_jobs_manager(self):
        'Test the jobs manager'
        from calibre.srv.jobs import JobsManager
        O = namedtuple('O', 'max_jobs max_job_time')

        class FakeLog(list):
            def error(self, *args):
                self.append(' '.join(args))

        jm = JobsManager(O(1, 5), FakeLog())
        job_id = jm.start_job('simple test',
                              'calibre.srv.jobs',
                              'sleep_test',
                              args=(1.0, ))
        job_id2 = jm.start_job('t2',
                               'calibre.srv.jobs',
                               'sleep_test',
                               args=(3, ))
        jid = jm.start_job('err test', 'calibre.srv.jobs', 'error_test')
        status = jm.job_status(job_id)[0]
        s = ('waiting', 'running')
        self.assertIn(status, s)
        status2 = jm.job_status(job_id2)[0]
        self.assertEqual(status2, 'waiting')
        while jm.job_status(job_id)[0] in s:
            time.sleep(0.01)
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual(status, 'finished')
        self.assertFalse(was_aborted)
        self.assertFalse(tb)
        self.assertEqual(result, 1.0)
        status2 = jm.job_status(job_id2)[0]
        time.sleep(0.01)
        self.assertEqual(status2, 'running')
        jm.abort_job(job_id2)
        self.assertTrue(jm.wait_for_running_job(job_id2))
        status, result, tb, was_aborted = jm.job_status(job_id2)
        self.assertTrue(was_aborted)
        self.assertTrue(jm.wait_for_running_job(jid))
        status, result, tb, was_aborted = jm.job_status(jid)
        self.assertTrue(tb), self.assertIn('a testing error', tb)
        jm.start_job('simple test',
                     'calibre.srv.jobs',
                     'sleep_test',
                     args=(1.0, ))
        jm.shutdown(), jm.wait_for_shutdown(monotonic() + 1)
Beispiel #4
0
class ServerLoop(object):

    LISTENING_MSG = 'calibre server listening on'

    def __init__(
        self,
        handler,
        opts=None,
        plugins=(),
        # A calibre logging object. If None, a default log that logs to
        # stdout is used
        log=None,
        # A calibre logging object for access logging, by default no access
        # logging is performed
        access_log=None):
        self.ready = False
        self.handler = handler
        self.opts = opts or Options()
        self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
        self.jobs_manager = JobsManager(self.opts, self.log)
        self.access_log = access_log

        ba = (self.opts.listen_on, int(self.opts.port))
        if not ba[0]:
            # AI_PASSIVE does not work with host of '' or None
            ba = ('0.0.0.0', ba[1])
        self.bind_address = ba
        self.bound_address = None
        self.connection_map = {}

        self.ssl_context = None
        if self.opts.ssl_certfile is not None and self.opts.ssl_keyfile is not None:
            self.ssl_context = ssl.create_default_context(
                ssl.Purpose.CLIENT_AUTH)
            self.ssl_context.load_cert_chain(certfile=self.opts.ssl_certfile,
                                             keyfile=self.opts.ssl_keyfile)
            self.ssl_context.set_servername_callback(self.on_ssl_servername)

        self.pre_activated_socket = None
        if self.opts.allow_socket_preallocation:
            from calibre.srv.pre_activated import pre_activated_socket
            self.pre_activated_socket = pre_activated_socket()
            if self.pre_activated_socket is not None:
                set_socket_inherit(self.pre_activated_socket, False)
                self.bind_address = self.pre_activated_socket.getsockname()

        self.create_control_connection()
        self.pool = ThreadPool(self.log,
                               self.job_completed,
                               count=self.opts.worker_count)
        self.plugin_pool = PluginPool(self, plugins)

    def on_ssl_servername(self, socket, server_name, ssl_context):
        c = self.connection_map.get(socket.fileno())
        if getattr(c, 'ssl_handshake_done', False):
            c.ready = False
            c.ssl_terminated = True
            # We do not allow client initiated SSL renegotiation
            return ssl.ALERT_DESCRIPTION_NO_RENEGOTIATION

    def create_control_connection(self):
        self.control_in, self.control_out = create_sock_pair()

    def __str__(self):
        return "%s(%r)" % (self.__class__.__name__, self.bind_address)

    __repr__ = __str__

    @property
    def num_active_connections(self):
        return len(self.connection_map)

    def do_bind(self):
        # Get the correct address family for our host (allows IPv6 addresses)
        host, port = self.bind_address
        try:
            info = socket.getaddrinfo(host, port, socket.AF_UNSPEC,
                                      socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
        except socket.gaierror:
            if ':' in host:
                info = [(socket.AF_INET6, socket.SOCK_STREAM, 0, "",
                         self.bind_address + (0, 0))]
            else:
                info = [(socket.AF_INET, socket.SOCK_STREAM, 0, "",
                         self.bind_address)]

        self.socket = None
        msg = "No socket could be created"
        for res in info:
            af, socktype, proto, canonname, sa = res
            try:
                self.bind(af, socktype, proto)
            except socket.error as serr:
                msg = "%s -- (%s: %s)" % (msg, sa, as_unicode(serr))
                if self.socket:
                    self.socket.close()
                self.socket = None
                continue
            break
        if not self.socket:
            raise socket.error(msg)

    def initialize_socket(self):
        if self.pre_activated_socket is None:
            try:
                self.do_bind()
            except socket.error as err:
                if not self.opts.fallback_to_detected_interface:
                    raise
                ip = get_external_ip()
                if ip == self.bind_address[0]:
                    raise
                self.log.warn(
                    'Failed to bind to %s with error: %s. Trying to bind to the default interface: %s instead'
                    % (self.bind_address[0], as_unicode(err), ip))
                self.bind_address = (ip, self.bind_address[1])
                self.do_bind()
        else:
            self.socket = self.pre_activated_socket
            self.pre_activated_socket = None
            self.setup_socket()

    def serve(self):
        self.connection_map = {}
        self.socket.listen(min(socket.SOMAXCONN, 128))
        self.bound_address = ba = self.socket.getsockname()
        if isinstance(ba, tuple):
            ba = ':'.join(map(type(''), ba))
        self.pool.start()
        with TemporaryDirectory(prefix='srv-') as tdir:
            self.tdir = tdir
            self.ready = True
            if self.LISTENING_MSG:
                self.log(self.LISTENING_MSG, ba)
            self.plugin_pool.start()

            while self.ready:
                try:
                    self.tick()
                except SystemExit:
                    self.shutdown()
                    raise
                except KeyboardInterrupt:
                    break
                except:
                    self.log.exception('Error in ServerLoop.tick')
            self.shutdown()

    def serve_forever(self):
        """ Listen for incoming connections. """
        self.initialize_socket()
        self.serve()

    def setup_socket(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
        # activate dual-stack.
        if (hasattr(socket, 'AF_INET6')
                and self.socket.family == socket.AF_INET6
                and self.bind_address[0] in ('::', '::0', '::0.0.0.0')):
            try:
                self.socket.setsockopt(socket.IPPROTO_IPV6, socket.IPV6_V6ONLY,
                                       0)
            except (AttributeError, socket.error):
                # Apparently, the socket option is not available in
                # this machine's TCP stack
                pass
        self.socket.setblocking(0)

    def bind(self, family, atype, proto=0):
        '''Create (or recreate) the actual socket object.'''
        self.socket = socket.socket(family, atype, proto)
        set_socket_inherit(self.socket, False)
        self.setup_socket()
        self.socket.bind(self.bind_address)

    def tick(self):
        now = monotonic()
        read_needed, write_needed, readable, remove, close_needed = [], [], [], [], []
        has_ssl = self.ssl_context is not None
        for s, conn in self.connection_map.iteritems():
            if now - conn.last_activity > self.opts.timeout:
                if conn.handle_timeout():
                    conn.last_activity = now
                else:
                    remove.append((s, conn))
                    continue
            wf = conn.wait_for
            if wf is READ or wf is RDWR:
                if wf is RDWR:
                    write_needed.append(s)
                if conn.read_buffer.has_data:
                    readable.append(s)
                else:
                    if has_ssl:
                        conn.drain_ssl_buffer()
                        if conn.ready:
                            (readable if conn.read_buffer.has_data else
                             read_needed).append(s)
                        else:
                            close_needed.append((s, conn))
                    else:
                        read_needed.append(s)
            elif wf is WRITE:
                write_needed.append(s)

        for s, conn in remove:
            self.log('Closing connection because of extended inactivity: %s' %
                     conn.state_description)
            self.close(s, conn)

        for x, conn in close_needed:
            self.close(s, conn)

        if readable:
            writable = []
        else:
            try:
                readable, writable, _ = select.select(
                    [self.socket.fileno(),
                     self.control_out.fileno()] + read_needed, write_needed,
                    [], self.opts.timeout)
            except ValueError:  # self.socket.fileno() == -1
                self.ready = False
                self.log.error('Listening socket was unexpectedly terminated')
                return
            except (select.error, socket.error) as e:
                # select.error has no errno attribute. errno is instead
                # e.args[0]
                if getattr(e, 'errno', e.args[0]) in socket_errors_eintr:
                    return
                for s, conn in tuple(self.connection_map.iteritems()):
                    try:
                        select.select([s], [], [], 0)
                    except (select.error, socket.error) as e:
                        if getattr(e, 'errno',
                                   e.args[0]) not in socket_errors_eintr:
                            self.close(s, conn)  # Bad socket, discard
                return

        if not self.ready:
            return

        ignore = set()
        for s, conn, event in self.get_actions(readable, writable):
            if s in ignore:
                continue
            try:
                conn.handle_event(event)
                if not conn.ready:
                    self.close(s, conn)
            except JobQueueFull:
                self.log.exception('Server busy handling request: %s' %
                                   conn.state_description)
                if conn.ready:
                    if conn.response_started:
                        self.close(s, conn)
                    else:
                        try:
                            conn.report_busy()
                        except Exception:
                            self.close(s, conn)
            except Exception as e:
                ignore.add(s)
                ssl_terminated = getattr(conn, 'ssl_terminated', False)
                if ssl_terminated:
                    self.log.warn(
                        'Client tried to initiate SSL renegotiation, closing connection'
                    )
                    self.close(s, conn)
                else:
                    self.log.exception('Unhandled exception in state: %s' %
                                       conn.state_description)
                    if conn.ready:
                        if conn.response_started:
                            self.close(s, conn)
                        else:
                            try:
                                conn.report_unhandled_exception(
                                    e, traceback.format_exc())
                            except Exception:
                                self.close(s, conn)
                    else:
                        self.log.error(
                            'Error in SSL handshake, terminating connection: %s'
                            % as_unicode(e))
                        self.close(s, conn)

    def wakeup(self):
        self.control_in.sendall(WAKEUP)

    def job_completed(self):
        self.control_in.sendall(JOB_DONE)

    def dispatch_job_results(self):
        while True:
            try:
                s, ok, result = self.pool.get_nowait()
            except Empty:
                break
            conn = self.connection_map.get(s)
            if conn is not None:
                yield s, conn, (ok, result)

    def close(self, s, conn):
        self.connection_map.pop(s, None)
        conn.close()

    def get_actions(self, readable, writable):
        listener = self.socket.fileno()
        control = self.control_out.fileno()
        for s in readable:
            if s == listener:
                sock, addr = self.accept()
                if sock is not None:
                    s = sock.fileno()
                    if s > -1:
                        self.connection_map[s] = conn = self.handler(
                            sock, self.opts, self.ssl_context, self.tdir, addr,
                            self.pool, self.log, self.access_log, self.wakeup)
                        if self.ssl_context is not None:
                            yield s, conn, RDWR
            elif s == control:
                try:
                    c = self.control_out.recv(1)
                except socket.error:
                    if not self.ready:
                        return
                    self.log.error('Control socket raised an error, resetting')
                    self.create_control_connection()
                    continue
                if c == JOB_DONE:
                    for s, conn, event in self.dispatch_job_results():
                        yield s, conn, event
                elif c == WAKEUP:
                    pass
                elif not c:
                    if not self.ready:
                        return
                    self.log.error(
                        'Control socket failed to recv(), resetting')
                    self.create_control_connection()
            else:
                yield s, self.connection_map[s], READ
        for s in writable:
            try:
                conn = self.connection_map[s]
            except KeyError:
                continue  # Happens if connection was closed during read phase
            yield s, conn, WRITE

    def accept(self):
        try:
            sock, addr = self.socket.accept()
            set_socket_inherit(sock, False), sock.setblocking(False)
            return sock, addr
        except socket.error:
            return None, None

    def stop(self):
        self.ready = False
        self.wakeup()

    def shutdown(self):
        self.jobs_manager.shutdown()
        try:
            if getattr(self, 'socket', None):
                self.socket.close()
                self.socket = None
        except socket.error:
            pass
        for s, conn in tuple(self.connection_map.iteritems()):
            self.close(s, conn)
        wait_till = monotonic() + self.opts.shutdown_timeout
        for pool in (self.plugin_pool, self.pool):
            pool.stop(wait_till)
            if pool.workers:
                self.log.warn('Failed to shutdown %d workers in %s cleanly' %
                              (len(pool.workers), pool.__class__.__name__))
        self.jobs_manager.wait_for_shutdown(wait_till)
Beispiel #5
0
    def test_jobs_manager(self):
        'Test the jobs manager'
        from calibre.srv.jobs import JobsManager
        O = namedtuple('O', 'max_jobs max_job_time')

        class FakeLog(list):
            def error(self, *args):
                self.append(' '.join(args))

        s = ('waiting', 'running')
        jm = JobsManager(O(1, 5), FakeLog())

        def job_status(jid):
            return jm.job_status(jid)[0]

        # Start jobs
        job_id1 = jm.start_job('simple test',
                               'calibre.srv.jobs',
                               'sleep_test',
                               args=(1.0, ))
        job_id2 = jm.start_job('t2',
                               'calibre.srv.jobs',
                               'sleep_test',
                               args=(3, ))
        job_id3 = jm.start_job('err test', 'calibre.srv.jobs', 'error_test')

        # Job 1
        job_id = job_id1
        status = jm.job_status(job_id)[0]
        self.assertIn(status, s)
        for jid in (job_id2, job_id3):
            self.assertEqual(job_status(jid), 'waiting')
        while job_status(job_id) in s:
            time.sleep(0.01)
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual(status, 'finished')
        self.assertFalse(was_aborted)
        self.assertFalse(tb)
        self.assertEqual(result, 1.0)

        # Job 2
        job_id = job_id2
        while job_status(job_id) == 'waiting':
            time.sleep(0.01)
        self.assertEqual('running', job_status(job_id))
        jm.abort_job(job_id)
        self.assertIn(jm.wait_for_running_job(job_id), (True, None))
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual('finished', status)
        self.assertTrue(was_aborted)

        # Job 3
        job_id = job_id3
        while job_status(job_id) == 'waiting':
            time.sleep(0.01)
        self.assertIn(jm.wait_for_running_job(job_id), (True, None))
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual(status, 'finished')
        self.assertFalse(was_aborted)
        self.assertTrue(tb)
        self.assertIn('a testing error', tb)
        jm.start_job('simple test',
                     'calibre.srv.jobs',
                     'sleep_test',
                     args=(1.0, ))
        jm.shutdown(), jm.wait_for_shutdown(monotonic() + 1)
Beispiel #6
0
class ServerLoop(object):

    LISTENING_MSG = 'calibre server listening on'

    def __init__(
        self,
        handler,
        opts=None,
        plugins=(),
        # A calibre logging object. If None, a default log that logs to
        # stdout is used
        log=None,
        # A calibre logging object for access logging, by default no access
        # logging is performed
        access_log=None
    ):
        self.ready = False
        self.handler = handler
        self.opts = opts or Options()
        self.log = log or ThreadSafeLog(level=ThreadSafeLog.DEBUG)
        self.jobs_manager = JobsManager(self.opts, self.log)
        self.access_log = access_log

        ba = (self.opts.listen_on, int(self.opts.port))
        if not ba[0]:
            # AI_PASSIVE does not work with host of '' or None
            ba = ('0.0.0.0', ba[1])
        self.bind_address = ba
        self.bound_address = None
        self.connection_map = {}

        self.ssl_context = None
        if self.opts.ssl_certfile is not None and self.opts.ssl_keyfile is not None:
            self.ssl_context = ssl.create_default_context(ssl.Purpose.CLIENT_AUTH)
            self.ssl_context.load_cert_chain(certfile=self.opts.ssl_certfile, keyfile=self.opts.ssl_keyfile)

        self.pre_activated_socket = None
        if self.opts.allow_socket_preallocation:
            from calibre.srv.pre_activated import pre_activated_socket
            self.pre_activated_socket = pre_activated_socket()
            if self.pre_activated_socket is not None:
                set_socket_inherit(self.pre_activated_socket, False)
                self.bind_address = self.pre_activated_socket.getsockname()

        self.create_control_connection()
        self.pool = ThreadPool(self.log, self.job_completed, count=self.opts.worker_count)
        self.plugin_pool = PluginPool(self, plugins)

    def create_control_connection(self):
        self.control_in, self.control_out = create_sock_pair()

    def __str__(self):
        return "%s(%r)" % (self.__class__.__name__, self.bind_address)
    __repr__ = __str__

    @property
    def num_active_connections(self):
        return len(self.connection_map)

    def do_bind(self):
        # Get the correct address family for our host (allows IPv6 addresses)
        host, port = self.bind_address
        try:
            info = socket.getaddrinfo(
                host, port, socket.AF_UNSPEC,
                socket.SOCK_STREAM, 0, socket.AI_PASSIVE)
        except socket.gaierror:
            if ':' in host:
                info = [(socket.AF_INET6, socket.SOCK_STREAM,
                        0, "", self.bind_address + (0, 0))]
            else:
                info = [(socket.AF_INET, socket.SOCK_STREAM,
                        0, "", self.bind_address)]

        self.socket = None
        msg = "No socket could be created"
        for res in info:
            af, socktype, proto, canonname, sa = res
            try:
                self.bind(af, socktype, proto)
            except socket.error as serr:
                msg = "%s -- (%s: %s)" % (msg, sa, as_unicode(serr))
                if self.socket:
                    self.socket.close()
                self.socket = None
                continue
            break
        if not self.socket:
            raise socket.error(msg)

    def serve_forever(self):
        """ Listen for incoming connections. """

        if self.pre_activated_socket is None:
            try:
                self.do_bind()
            except socket.error as err:
                if not self.opts.fallback_to_detected_interface:
                    raise
                ip = get_external_ip()
                if ip == self.bind_address[0]:
                    raise
                self.log.warn('Failed to bind to %s with error: %s. Trying to bind to the default interface: %s instead' % (
                    self.bind_address[0], as_unicode(err), ip))
                self.bind_address = (ip, self.bind_address[1])
                self.do_bind()
        else:
            self.socket = self.pre_activated_socket
            self.pre_activated_socket = None
            self.setup_socket()

        self.connection_map = {}
        self.socket.listen(min(socket.SOMAXCONN, 128))
        self.bound_address = ba = self.socket.getsockname()
        if isinstance(ba, tuple):
            ba = ':'.join(map(type(''), ba))
        self.pool.start()
        with TemporaryDirectory(prefix='srv-') as tdir:
            self.tdir = tdir
            self.ready = True
            if self.LISTENING_MSG:
                self.log(self.LISTENING_MSG, ba)
            self.plugin_pool.start()

            while self.ready:
                try:
                    self.tick()
                except SystemExit:
                    self.shutdown()
                    raise
                except KeyboardInterrupt:
                    break
                except:
                    self.log.exception('Error in ServerLoop.tick')
            self.shutdown()

    def setup_socket(self):
        self.socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.socket.setsockopt(socket.IPPROTO_TCP, socket.TCP_NODELAY, 1)

        # If listening on the IPV6 any address ('::' = IN6ADDR_ANY),
        # activate dual-stack.
        if (hasattr(socket, 'AF_INET6') and self.socket.family == socket.AF_INET6 and
                self.bind_address[0] in ('::', '::0', '::0.0.0.0')):
            try:
                self.socket.setsockopt(
                    socket.IPPROTO_IPV6, socket.IPV6_V6ONLY, 0)
            except (AttributeError, socket.error):
                # Apparently, the socket option is not available in
                # this machine's TCP stack
                pass
        self.socket.setblocking(0)

    def bind(self, family, atype, proto=0):
        '''Create (or recreate) the actual socket object.'''
        self.socket = socket.socket(family, atype, proto)
        set_socket_inherit(self.socket, False)
        self.setup_socket()
        self.socket.bind(self.bind_address)

    def tick(self):
        now = monotonic()
        read_needed, write_needed, readable, remove = [], [], [], []
        for s, conn in self.connection_map.iteritems():
            if now - conn.last_activity > self.opts.timeout:
                if conn.handle_timeout():
                    conn.last_activity = now
                else:
                    remove.append((s, conn))
                    continue
            wf = conn.wait_for
            if wf is READ:
                (readable if conn.read_buffer.has_data else read_needed).append(s)
            elif wf is WRITE:
                write_needed.append(s)
            elif wf is RDWR:
                write_needed.append(s)
                (readable if conn.read_buffer.has_data else read_needed).append(s)

        for s, conn in remove:
            self.log('Closing connection because of extended inactivity: %s' % conn.state_description)
            self.close(s, conn)

        if readable:
            writable = []
        else:
            try:
                readable, writable, _ = select.select([self.socket.fileno(), self.control_out.fileno()] + read_needed, write_needed, [], self.opts.timeout)
            except ValueError:  # self.socket.fileno() == -1
                self.ready = False
                self.log.error('Listening socket was unexpectedly terminated')
                return
            except (select.error, socket.error) as e:
                # select.error has no errno attribute. errno is instead
                # e.args[0]
                if getattr(e, 'errno', e.args[0]) in socket_errors_eintr:
                    return
                for s, conn in tuple(self.connection_map.iteritems()):
                    try:
                        select.select([s], [], [], 0)
                    except (select.error, socket.error) as e:
                        if getattr(e, 'errno', e.args[0]) not in socket_errors_eintr:
                            self.close(s, conn)  # Bad socket, discard
                return

        if not self.ready:
            return

        ignore = set()
        for s, conn, event in self.get_actions(readable, writable):
            if s in ignore:
                continue
            try:
                conn.handle_event(event)
                if not conn.ready:
                    self.close(s, conn)
            except JobQueueFull:
                self.log.exception('Server busy handling request: %s' % conn.state_description)
                if conn.ready:
                    if conn.response_started:
                        self.close(s, conn)
                    else:
                        try:
                            conn.report_busy()
                        except Exception:
                            self.close(s, conn)
            except Exception as e:
                ignore.add(s)
                self.log.exception('Unhandled exception in state: %s' % conn.state_description)
                if conn.ready:
                    if conn.response_started:
                        self.close(s, conn)
                    else:
                        try:
                            conn.report_unhandled_exception(e, traceback.format_exc())
                        except Exception:
                            self.close(s, conn)
                else:
                    self.log.error('Error in SSL handshake, terminating connection: %s' % as_unicode(e))
                    self.close(s, conn)

    def wakeup(self):
        self.control_in.sendall(WAKEUP)

    def job_completed(self):
        self.control_in.sendall(JOB_DONE)

    def dispatch_job_results(self):
        while True:
            try:
                s, ok, result = self.pool.get_nowait()
            except Empty:
                break
            conn = self.connection_map.get(s)
            if conn is not None:
                yield s, conn, (ok, result)

    def close(self, s, conn):
        self.connection_map.pop(s, None)
        conn.close()

    def get_actions(self, readable, writable):
        listener = self.socket.fileno()
        control = self.control_out.fileno()
        for s in readable:
            if s == listener:
                sock, addr = self.accept()
                if sock is not None:
                    s = sock.fileno()
                    if s > -1:
                        self.connection_map[s] = conn = self.handler(
                            sock, self.opts, self.ssl_context, self.tdir, addr, self.pool, self.log, self.access_log, self.wakeup)
                        if self.ssl_context is not None:
                            yield s, conn, RDWR
            elif s == control:
                try:
                    c = self.control_out.recv(1)
                except socket.error:
                    if not self.ready:
                        return
                    self.log.error('Control socket raised an error, resetting')
                    self.create_control_connection()
                    continue
                if c == JOB_DONE:
                    for s, conn, event in self.dispatch_job_results():
                        yield s, conn, event
                elif c == WAKEUP:
                    pass
                elif not c:
                    if not self.ready:
                        return
                    self.log.error('Control socket failed to recv(), resetting')
                    self.create_control_connection()
            else:
                yield s, self.connection_map[s], READ
        for s in writable:
            try:
                conn = self.connection_map[s]
            except KeyError:
                continue  # Happens if connection was closed during read phase
            yield s, conn, WRITE

    def accept(self):
        try:
            sock, addr = self.socket.accept()
            set_socket_inherit(sock, False), sock.setblocking(False)
            return sock, addr
        except socket.error:
            return None, None

    def stop(self):
        self.ready = False
        self.wakeup()

    def shutdown(self):
        self.jobs_manager.shutdown()
        try:
            if getattr(self, 'socket', None):
                self.socket.close()
                self.socket = None
        except socket.error:
            pass
        for s, conn in tuple(self.connection_map.iteritems()):
            self.close(s, conn)
        wait_till = monotonic() + self.opts.shutdown_timeout
        for pool in (self.plugin_pool, self.pool):
            pool.stop(wait_till)
            if pool.workers:
                self.log.warn('Failed to shutdown %d workers in %s cleanly' % (len(pool.workers), pool.__class__.__name__))
        self.jobs_manager.wait_for_shutdown(wait_till)
Beispiel #7
0
    def test_jobs_manager(self):
        'Test the jobs manager'
        from calibre.srv.jobs import JobsManager
        O = namedtuple('O', 'max_jobs max_job_time')
        class FakeLog(list):
            def error(self, *args):
                self.append(' '.join(args))
        s = ('waiting', 'running')
        jm = JobsManager(O(1, 5), FakeLog())
        def job_status(jid):
            return jm.job_status(jid)[0]

        # Start jobs
        job_id1 = jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,))
        job_id2 = jm.start_job('t2', 'calibre.srv.jobs', 'sleep_test', args=(3,))
        job_id3 = jm.start_job('err test', 'calibre.srv.jobs', 'error_test')

        # Job 1
        job_id = job_id1
        status = jm.job_status(job_id)[0]
        self.assertIn(status, s)
        for jid in (job_id2, job_id3):
            self.assertEqual(job_status(jid), 'waiting')
        while job_status(job_id) in s:
            time.sleep(0.01)
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual(status, 'finished')
        self.assertFalse(was_aborted)
        self.assertFalse(tb)
        self.assertEqual(result, 1.0)

        # Job 2
        job_id = job_id2
        while job_status(job_id) == 'waiting':
            time.sleep(0.01)
        self.assertEqual('running', job_status(job_id))
        jm.abort_job(job_id)
        self.assertIn(jm.wait_for_running_job(job_id), (True, None))
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual('finished', status)
        self.assertTrue(was_aborted)

        # Job 3
        job_id = job_id3
        while job_status(job_id) == 'waiting':
            time.sleep(0.01)
        self.assertIn(jm.wait_for_running_job(job_id), (True, None))
        status, result, tb, was_aborted = jm.job_status(job_id)
        self.assertEqual(status, 'finished')
        self.assertFalse(was_aborted)
        self.assertTrue(tb), self.assertIn('a testing error', tb)
        jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,))
        jm.shutdown(), jm.wait_for_shutdown(monotonic() + 1)
Beispiel #8
0
 def test_jobs_manager(self):
     'Test the jobs manager'
     from calibre.srv.jobs import JobsManager
     O = namedtuple('O', 'max_jobs max_job_time')
     class FakeLog(list):
         def error(self, *args):
             self.append(' '.join(args))
     jm = JobsManager(O(1, 5), FakeLog())
     job_id = jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,))
     job_id2 = jm.start_job('t2', 'calibre.srv.jobs', 'sleep_test', args=(3,))
     jid = jm.start_job('err test', 'calibre.srv.jobs', 'error_test')
     status = jm.job_status(job_id)[0]
     s = ('waiting', 'running')
     self.assertIn(status, s)
     status2 = jm.job_status(job_id2)[0]
     self.assertEqual(status2, 'waiting')
     while jm.job_status(job_id)[0] in s:
         time.sleep(0.01)
     status, result, tb, was_aborted = jm.job_status(job_id)
     self.assertEqual(status, 'finished')
     self.assertFalse(was_aborted)
     self.assertFalse(tb)
     self.assertEqual(result, 1.0)
     status2 = jm.job_status(job_id2)[0]
     time.sleep(0.01)
     self.assertEqual(status2, 'running')
     jm.abort_job(job_id2)
     self.assertTrue(jm.wait_for_running_job(job_id2))
     status, result, tb, was_aborted = jm.job_status(job_id2)
     self.assertTrue(was_aborted)
     self.assertTrue(jm.wait_for_running_job(jid))
     status, result, tb, was_aborted = jm.job_status(jid)
     self.assertTrue(tb), self.assertIn('a testing error', tb)
     jm.start_job('simple test', 'calibre.srv.jobs', 'sleep_test', args=(1.0,))
     jm.shutdown(), jm.wait_for_shutdown(monotonic() + 1)