Example #1
0
    def __init__(self, listener_socket, active_handler, threads_active = 5, queue_handler = None, threads_queued = 10, thread_stopping_hook = None):
        """
Constructor __init__(Dispatcher)

:param listener_socket: Listener socket
:param active_handler: Thread to be used for activated connections
:param threads_active: Allowed simultaneous threads
:param queue_handler: Thread to be used for queued connections
:param threads_queued: Allowed queued threads
:param thread_stopping_hook: Thread stopping hook definition

:since: v0.2.00
        """

        asyncore.dispatcher.__init__(self, sock = listener_socket)

        self.active = False
        """
Listener state
        """
        self.active_handler = (active_handler if (issubclass(active_handler, Handler)) else None)
        """
Active queue handler
        """
        self.actives = None
        """
Active counter
        """
        self.actives_list = [ ]
        """
Active queue
        """
        self.listener_handle_connections = (listener_socket.type & socket.SOCK_STREAM == socket.SOCK_STREAM)
        """
Listener socket
        """
        self.listener_socket = listener_socket
        """
Listener socket
        """
        self.listener_startup_timeout = 45
        """
Listener startup timeout
        """
        self.local = None
        """
Local data handle
        """
        self._lock = InstanceLock()
        """
Thread safety lock
        """
        self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False)
        """
The LogHandler is called whenever debug messages should be logged or errors
happened.
        """
        self.queue_handler = (queue_handler if (isinstance(queue_handler, Handler)) else None)
        """
Passive queue handler
        """
        self.queue_max = threads_queued
        """
Passive queue maximum
        """
        self.stopping_hook = ("" if (thread_stopping_hook is None) else thread_stopping_hook)
        """
Stopping hook definition
        """
        self.thread = None
        """
Thread if started and active
        """
        self.waiting = 0
        """
Thread safety lock
        """

        self.actives = BoundedSemaphore(threads_active if (self.listener_handle_connections) else 1)
Example #2
0
class Dispatcher(asyncore.dispatcher):
    """
The dNG server infrastructure allows an application to provide active
listeners, threaded connection establishment and to queue a defined amount
of requests transparently.

:author:     direct Netware Group et al.
:copyright:  (C) direct Netware Group - All rights reserved
:package:    pas
:subpackage: server
:since:      v0.2.00
:license:    https://www.direct-netware.de/redirect?licenses;mpl2
             Mozilla Public License, v. 2.0
    """

    # pylint: disable=unused-argument

    def __init__(self, listener_socket, active_handler, threads_active = 5, queue_handler = None, threads_queued = 10, thread_stopping_hook = None):
        """
Constructor __init__(Dispatcher)

:param listener_socket: Listener socket
:param active_handler: Thread to be used for activated connections
:param threads_active: Allowed simultaneous threads
:param queue_handler: Thread to be used for queued connections
:param threads_queued: Allowed queued threads
:param thread_stopping_hook: Thread stopping hook definition

:since: v0.2.00
        """

        asyncore.dispatcher.__init__(self, sock = listener_socket)

        self.active = False
        """
Listener state
        """
        self.active_handler = (active_handler if (issubclass(active_handler, Handler)) else None)
        """
Active queue handler
        """
        self.actives = None
        """
Active counter
        """
        self.actives_list = [ ]
        """
Active queue
        """
        self.listener_handle_connections = (listener_socket.type & socket.SOCK_STREAM == socket.SOCK_STREAM)
        """
Listener socket
        """
        self.listener_socket = listener_socket
        """
Listener socket
        """
        self.listener_startup_timeout = 45
        """
Listener startup timeout
        """
        self.local = None
        """
Local data handle
        """
        self._lock = InstanceLock()
        """
Thread safety lock
        """
        self.log_handler = NamedLoader.get_singleton("dNG.data.logging.LogHandler", False)
        """
The LogHandler is called whenever debug messages should be logged or errors
happened.
        """
        self.queue_handler = (queue_handler if (isinstance(queue_handler, Handler)) else None)
        """
Passive queue handler
        """
        self.queue_max = threads_queued
        """
Passive queue maximum
        """
        self.stopping_hook = ("" if (thread_stopping_hook is None) else thread_stopping_hook)
        """
Stopping hook definition
        """
        self.thread = None
        """
Thread if started and active
        """
        self.waiting = 0
        """
Thread safety lock
        """

        self.actives = BoundedSemaphore(threads_active if (self.listener_handle_connections) else 1)
    #

    def _active_activate(self, _socket):
        """
Run the active handler for the given socket.

:param _socket: Active socket resource

:since: v0.2.00
        """

        handler = self.active_handler()
        handler.set_instance_data(self, _socket)
        handler.start()

        if (self.log_handler is not None): self.log_handler.debug("{0!r} started a new thread '{1!r}'", self, handler, context = "pas_server")
    #

    def _active_queue(self, _socket):
        """
Put's an transport on the active queue or tries to temporarily save it on
the passive queue.

:param _socket: Active socket resource

:return: (bool) True if queued
:since:  v0.2.00
        """

        _return = False

        if (self.active):
            if (self.actives.acquire(self.queue_handler is None)):
                with self._lock:
                    if (self.active):
                        self.actives_list.append(_socket)
                        _return = True
                    else: self.actives.release()
                #
            else:
                handler = self.queue_handler()
                handler.set_instance_data(self, _socket)
                handler.start()

                self.waiting += 1
            #
        #

        return _return
    #

    def active_unqueue(self, _socket):
        """
Unqueue the given ID from the active queue.

:param _socket: Active socket resource

:since: v0.2.00
        """

        if (self._unqueue(self.actives_list, _socket)): self.actives.release()
    #

    def _active_unqueue_all(self):
        """
Unqueue all entries from the active queue (canceling running processes).

:since: v0.2.00
        """

        with self._lock:
            if (self.actives_list is not None):
                for _socket in self.actives_list:
                    if (self._unqueue(self.actives_list, _socket)): self.actives.release()
                #
            #
        #
    #

    def _ensure_thread_local(self):
        """
For thread safety some variables are defined per thread. This method makes
sure that these variables are defined.

:since: v0.2.00
        """

        if (self.local is None): self.local = local()
        if (not hasattr(self.local, "sockets")): self.local.sockets = { }
    #

    def handle_accept(self):
        """
python.org: Called on listening channels (passive openers) when a connection
can be established with a new remote endpoint that has issued a connect()
call for the local endpoint.

Deprecated since version 3.2.

:since: v0.2.00
        """

        # pylint: disable=broad-except

        if (self.active and self.listener_handle_connections):
            socket_data = None

            try: socket_data = self.accept()
            except Exception as handled_exception:
                if (self.log_handler is None): TracedException.print_current_stack_trace()
                else: self.log_handler.error(handled_exception, context = "pas_server")
            #

            if (socket_data is not None): self.handle_accepted(socket_data[0], socket_data[1])
        #
    #

    def handle_accepted(self, sock, addr):
        """
python.org: Called on listening channels (passive openers) when a connection
has been established with a new remote endpoint that has issued a connect()
call for the local endpoint.

:since: v0.2.00
        """

        # pylint: disable=broad-except

        if (self.active and self.listener_handle_connections):
            try:
                if (self._active_queue(sock)): self._active_activate(sock)
            except ShutdownException as handled_exception:
                exception = handled_exception.get_cause()

                if (exception is None and self.log_handler is not None): self.log_handler.error(handled_exception, context = "pas_server")
                else: handled_exception.print_stack_trace()
            except Exception as handled_exception:
                if (self.log_handler is None): TracedException.print_current_stack_trace()
                else: self.log_handler.error(handled_exception, context = "pas_server")
            #
        #
    #

    def handle_close(self):
        """
python.org: Called when the socket is closed.

:since: v0.2.00
        """

        if (self.active): self.stop()
    #

    def handle_connect(self):
        """
python.org: Called when the active opener's socket actually makes a
connection. Might send a "welcome" banner, or initiate a protocol
negotiation with the remote endpoint, for example.

:since: v0.2.00
        """

        if (self.active): self._start_listening()
    #

    def handle_error(self):
        """
python.org: Called when an exception is raised and not otherwise handled.

:since: v0.2.00
        """

        if (self.log_handler is None): TracedException.print_current_stack_trace()
        else: self.log_handler.error(traceback.format_exc(), context = "pas_server")
    #

    def handle_read(self):
        """
python.org: Called when the asynchronous loop detects that a "read()" call
on the channel's socket will succeed.

:since: v0.2.00
        """

        # pylint: disable=broad-except

        if ((not self.listener_handle_connections) and self.active):
            try:
                if (self._active_queue(self.listener_socket)): self._active_activate(self.listener_socket)
            except ShutdownException as handled_exception:
                exception = handled_exception.get_cause()

                if (exception is None and self.log_handler is not None): self.log_handler.error(handled_exception, context = "pas_server")
                else: handled_exception.print_stack_trace()
            except Exception as handled_exception:
                if (self.log_handler is None): TracedException.print_current_stack_trace()
                else: self.log_handler.error(handled_exception, context = "pas_server")
            #
        #
    #

    def handle_expt(self):
        """
python.org: Called when there is out of band (OOB) data for a socket
connection. This will almost never happen, as OOB is tenuously supported and
rarely used.

:since: v0.2.00
        """

        if (self.active): self._active_unqueue_all()
    #

    def _init(self):
        """
Initializes the dispatcher and stopping hook.

:since: v0.2.00
        """

        if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}._init()- (#echo(__LINE__)#)", self, context = "pas_server")

        if (self.stopping_hook is not None):
            stopping_hook = ("dNG.pas.Status.onShutdown" if (self.stopping_hook == "") else self.stopping_hook)
            Hook.register_weakref(stopping_hook, self.thread_stop)
        #
    #

    def is_active(self):
        """
Returns the listener status.

:return: (bool) True if active and listening
:since:  v0.2.00
        """

        return self.active
    #

    def start(self):
        """
Starts the prepared dispatcher in a new thread.

:since: v0.2.00
        """

        if (not self.active):
            is_already_active = False

            with self._lock:
                # Thread safety
                is_already_active = self.active
                if (not is_already_active): self.active = True
            #

            if (not is_already_active):
                self._init()
                Thread(target = self.run).start()
            #
        #
    #

    def _start_listening(self):
        """
Try to start listening on the prepared socket. Uses the defined startup
timeout to wait for the socket to become available before throwing an
exception.

:since: v0.2.00
        """

        # pylint: disable=broad-except,raising-bad-type

        _exception = None
        timeout_time = (time.time() + self.listener_startup_timeout)

        while (time.time() < timeout_time):
            try:
                if (_exception is not None): time.sleep(0.2)
                _exception = None

                self.listen(self.queue_max)

                break
            except Exception as handled_exception: _exception = handled_exception
        #

        if (_exception is not None): raise _exception
    #

    def run(self):
        """
Run the main loop for this server instance.

:since: v0.2.00
        """

        # pylint: disable=broad-except

        if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}.run()- (#echo(__LINE__)#)", self, context = "pas_server")

        self._ensure_thread_local()

        try:
            if (not self.active):
                with self._lock:
                    # Thread safety
                    if (not self.active):
                        self.active = True
                        self._init()
                    #
                #
            #

            if (self.listener_handle_connections): self._start_listening()

            self.add_channel(self.local.sockets)
            asyncore.loop(5, map = self.local.sockets)
        except ShutdownException as handled_exception:
            if (self.active):
                exception = handled_exception.get_cause()
                if (exception is not None and self.log_handler is not None): self.log_handler.error(exception, context = "pas_server")
            #
        except Exception as handled_exception:
            if (self.active):
                if (self.log_handler is None): TracedException.print_current_stack_trace()
                else: self.log_handler.error(handled_exception, context = "pas_server")
            #
        finally: self.stop()
    #

    def set_log_handler(self, log_handler):
        """
Sets the LogHandler.

:param log_handler: LogHandler to use

:since: v0.2.00
        """

        self.log_handler = log_handler
    #

    def stop(self):
        """
Stops the listener and unqueues all running sockets.

:since: v0.2.00
        """

        # pylint: disable=broad-except

        self._lock.acquire()

        if (self.active):
            if (self.log_handler is not None): self.log_handler.debug("#echo(__FILEPATH__)# -{0!r}.stop()- (#echo(__LINE__)#)", self, context = "pas_server")

            self.active = False

            if (self.stopping_hook is not None and len(self.stopping_hook) > 0): Hook.unregister(self.stopping_hook, self.thread_stop)
            self.stopping_hook = ""

            self._lock.release()

            try: self.close()
            except Exception: pass

            self._active_unqueue_all()
        else: self._lock.release()
    #

    def thread_stop(self, params = None, last_return = None):
        """
Stops the running server instance by a stopping hook call.

:param params: Parameter specified
:param last_return: The return value from the last hook called.

:return: (mixed) Return value
:since:  v0.2.00
        """

        self.stop()
        return last_return
    #

    def _unqueue(self, queue, _socket):
        """
Unqueues a previously active socket connection.

:param queue: Queue object
:param id: Queue ID

:return: (bool) True on success
:since:  v0.2.00
        """

        # pylint: disable=broad-except

        _return = False

        self._lock.acquire()

        if (queue is not None and _socket in queue):
            queue.remove(_socket)
            self._lock.release()

            _return = True

            if (self.listener_handle_connections):
                try: _socket.close()
                except socket.error: pass
            #
        else: self._lock.release()

        return _return
    #

    def writable(self):
        """
python.org: Called each time around the asynchronous loop to determine
whether a channel's socket should be added to the list on which write events
can occur.

:return: (bool) Always False
:since:  v0.2.00
        """

        return False
    #

    @staticmethod
    def prepare_socket(listener_type, *listener_data):
        """
Prepare socket returns a bound socket for the given listener data.

:param listener_type: Listener type
:param listener_data: Listener data

:since: v0.2.00
        """

        _return = None

        if (listener_type == socket.AF_INET or listener_type == socket.AF_INET6):
            listener_data = ( Binary.str(listener_data[0]), listener_data[1] )

            _return = socket.socket(listener_type, socket.SOCK_STREAM)
            _return.setblocking(0)
            if (hasattr(socket, "SO_REUSEADDR")): _return.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            _return.bind(listener_data)
        elif (listener_type == socket.AF_UNIX):
            unixsocket_path_name = path.normpath(Binary.str(listener_data[0]))
            if (os.access(unixsocket_path_name, os.F_OK)): os.unlink(unixsocket_path_name)

            _return = socket.socket(listener_type, socket.SOCK_STREAM)
            if (hasattr(socket, "SO_REUSEADDR")): _return.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
            _return.bind(unixsocket_path_name)

            socket_chmod = 0
            socket_chmod_value = int(Settings.get("pas_global_server_chmod_unix_sockets", "600"), 8)

            if ((1000 & socket_chmod_value) == 1000): socket_chmod |= stat.S_ISVTX
            if ((2000 & socket_chmod_value) == 2000): socket_chmod |= stat.S_ISGID
            if ((4000 & socket_chmod_value) == 4000): socket_chmod |= stat.S_ISUID
            if ((0o100 & socket_chmod_value) == 0o100): socket_chmod |= stat.S_IXUSR
            if ((0o200 & socket_chmod_value) == 0o200): socket_chmod |= stat.S_IWUSR
            if ((0o400 & socket_chmod_value) == 0o400): socket_chmod |= stat.S_IRUSR
            if ((0o010 & socket_chmod_value) == 0o010): socket_chmod |= stat.S_IXGRP
            if ((0o020 & socket_chmod_value) == 0o020): socket_chmod |= stat.S_IWGRP
            if ((0o040 & socket_chmod_value) == 0o040): socket_chmod |= stat.S_IRGRP
            if ((0o001 & socket_chmod_value) == 0o001): socket_chmod |= stat.S_IXOTH
            if ((0o002 & socket_chmod_value) == 0o002): socket_chmod |= stat.S_IWOTH
            if ((0o004 & socket_chmod_value) == 0o004): socket_chmod |= stat.S_IROTH

            os.chmod(unixsocket_path_name, socket_chmod)
        #

        return _return