Example #1
0
    def __receive_thread_worker(self):
        waitobj = IOWait()
        waitobj.watch( self._socket, read=True)
        ### BMX poller = select.poll()
        ### BMX poller.register(self._socket, select.POLLIN | select.POLLPRI | select.POLLHUP | select.POLLERR)
        __buffer = b''

        self._is_receiving = True
        self._receiving_callback and self._receiving_callback(self)
        while self._is_connected and self.__running:
            ### BMX events = poller.poll(1000)
            events = waitobj.wait(1000)     ### BMX
            ### BMX for fd, event in events:
            for fileno, read, write in events:  ### BMX
                ### BMX if event & select.POLLHUP:
                ### BMX     self.logger.warning("Client socket closed")
                # Check if POLLIN event triggered
                ### BMX if event & (select.POLLIN | select.POLLPRI):
                if read:
                    msg = self._socket.recv(4096)
                    # Check if incoming message is not empty
                    if msg:
                        # If we transfer in text mode decode message to string
                        if not self._binary:
                            msg = str.rstrip(str(msg, 'utf-8'))
                        # If we work in line mode (with a terminator) slice buffer into single chunks based on terminator
                        if self.terminator:
                            __buffer += msg
                            while True:
                                # terminator = int means fixed size chunks
                                if isinstance(self.terminator, int):
                                    i = self.terminator
                                    if i > len(__buffer):
                                        break
                                # terminator is str or bytes means search for it
                                else:
                                    i = __buffer.find(self.terminator)
                                    if i == -1:
                                        break
                                    i += len(self.terminator)
                                line = __buffer[:i]
                                __buffer = __buffer[i:]
                                if self._data_received_callback is not None:
                                    self._data_received_callback(self, line)
                        # If not in terminator mode just forward what we received
                        else:
                            if self._data_received_callback is not None:
                                self._data_received_callback(self, msg)
                    # If empty peer has closed the connection
                    else:
                        # Peer connection closed
                        self.logger.warning("Connection closed by peer {}".format(self._host))
                        self._is_connected = False
                        ### BMX poller.unregister()
                        waitobj.unwatch(self._socket)
                        self._disconnected_callback and self._disconnected_callback(self)
                        if self._autoreconnect:
                            self.logger.debug("Autoreconnect enabled for {}".format(self._host))
                            self.connect()
        self._is_receiving = False
Example #2
0
class Connections(Base):
    """
    Within SmartHome.py there is one instance of this class

    The filenumber of a connection is the key to the contained dicts of
    _connections and _servers
    Additionally the filenumber is used for either epoll or kqueue depending
    on the environment found for select.
    A filenumber of value -1 is an error value.
    """

    _connections = {}
    _servers = {}
    if hasattr(select, 'epoll'):
        _ro = select.EPOLLIN | select.EPOLLHUP | select.EPOLLERR
        _rw = _ro | select.EPOLLOUT

    def __init__(self):
        Base.__init__(self)
        Base._poller = self
        if hasattr(select, 'epoll'):
            self._epoll = select.epoll()
        elif hasattr(select, 'kqueue'):
            self._kqueue = select.kqueue()
        else:
            logger.debug("Init connections using IOWait")
            self._connections_found = 0
            self._waitobj = IOWait()

    def register_server(self, fileno, obj):
        if fileno == -1:
            logger.error("{} tried to register a server with filenumber == -1".format(obj))
            return
        self._servers[fileno] = obj
        self._connections[fileno] = obj
        if hasattr(select, 'epoll'):
            self._epoll.register(fileno, self._ro)
        elif hasattr(select, 'kqueue'):
            event = [
                select.kevent(fileno,
                       filter=select.KQ_FILTER_READ,
                       flags=select.KQ_EV_ADD)
            ]
            self._kqueue.control(event, 0, 0)
        else:
            logger.debug("register_server: put watch on fileno {}".format(fileno))
            self._waitobj.watch(fileno, read=True)

    def register_connection(self, fileno, obj):
        if fileno == -1:
            logger.error("tried to register a connection with filenumber == -1")
            return
        self._connections[fileno] = obj
        if hasattr(select, 'epoll'):
            self._epoll.register(fileno, self._ro)
        elif hasattr(select, 'kqueue'):
            event = [
                select.kevent(fileno,
                       filter=select.KQ_FILTER_READ,
                       flags=select.KQ_EV_ADD)
            ]
            self._kqueue.control(event, 0, 0)
        else:
            logger.debug("register_connection: put watch on fileno {}".format(fileno))
            self._waitobj.watch(fileno, read=True)

    def unregister_connection(self, fileno):
        if fileno == -1:
            logger.error("tried to unregister a connection with filenumber == -1")
            return
        try:
            if hasattr(select, 'epoll'):
                self._epoll.unregister(fileno)
            elif hasattr(select, 'kqueue'):
                pass
            else:
                logger.debug("unregister_connection: unwatch fileno {}".format(fileno))
                self._waitobj.unwatch(fileno)
        except Exception as e:
            logger.error("unregister a connection with filenumber == {} for epoll failed".format(fileno))

        try:
            del(self._connections[fileno])
        except:
            pass

        try:
            del(self._servers[fileno])
        except:
            pass

    def monitor(self, obj):
        self._monitor.append(obj)

    def check(self):
        for obj in self._monitor:
            if not obj.connected:
                obj.connect()

    def trigger(self, fileno):
        if fileno == -1:
            logger.error("tried to trigger a connection with filenumber == -1")
            return
        if self._connections[fileno].outbuffer:
            if hasattr(select, 'epoll'):
                self._epoll.modify(fileno, self._rw)
            elif hasattr(select, 'kqueue'):
                event = [
                    select.kevent(fileno,
                           filter=select.KQ_FILTER_WRITE,
                           flags=select.KQ_EV_ADD | KQ_EV_ONESHOT)
                ]
                self._kqueue.control(event, 0, 0)
            else:
                logger.error("trigger: Operating System without epoll or kqueue is currently not supported, please report this error")

    def poll(self):
        time.sleep(0.0000000001)  # give epoll.modify a chance
        if not self._connections:
            time.sleep(1)
            return

        if -1 in self._connections:
            logger.error("fileno -1 was found, please report to SmartHomeNG team")
            del( self._connections[-1])

        if hasattr(select, 'epoll') or hasattr(select, 'kqueue'):
            for fileno in self._connections:
            # Fix for: "RuntimeError: dictionary changed size during iteration"
            #connections_keys = self._connections.keys()
            #for fileno in connections_keys:
                if fileno not in self._servers:
                    if hasattr(select, 'epoll'):
                        if self._connections[fileno].outbuffer:
                            try:
                                self._epoll.modify(fileno, self._rw)
                            except OSError as e:
                                # as with python 3.6 an OSError will be raised when a socket is already closed like with a settimeout
                                # the socket will need to be recreated then
                                logger.error("OSError {} for epoll.modify(RW) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache
                            except PermissionError as e:
                                logger.error("PermissionError {} for epoll.modify(RW) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache
                            except FileNotFoundError as e:
                                logger.error("FileNotFoundError {} for epoll.modify(RW) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache
                        else:
                            try:
                                self._epoll.modify(fileno, self._ro)
                            except OSError as e:
                                # as with python 3.6 an OSError will be raised when a socket is already closed like with a settimeout
                                # the socket will need to be recreated then
                                logger.error("OSError {} for epoll.modify(RO) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache
                            except PermissionError as e:
                                logger.error("PermissionError {} for epoll.modify(RO) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache
                            except FileNotFoundError as e:
                                logger.error("FileNotFoundError {} for epoll.modify(RO) with fileno {} for object {}, please report to SmartHomeNG team".format(e, fileno, self._connections[fileno]))
                                # here we could try to get rid of the connection that causes the headache

                    elif hasattr(select, 'kqueue'):
                        event = []
                        if self._connections[fileno].outbuffer:
                            event.append(select.kevent(fileno,
                                                filter=select.KQ_FILTER_WRITE,
                                                flags=select.KQ_EV_ADD | KQ_EV_ONESHOT))
                        else:
                            event.append(select.kevent(fileno,
                                                filter=select.KQ_FILTER_READ,
                                                flags=select.KQ_EV_ADD))
                        self._kqueue.control(event, 0, 0)

            if hasattr(select, 'epoll'):
                for fileno, event in self._epoll.poll(timeout=1):
                    if fileno in self._servers:
                        server = self._servers[fileno]
                        server.handle_connection()
                    else:
                        if event & select.EPOLLIN:
                            try:
                                con = self._connections[fileno]
                                con._in()
                            except Exception as e:
                                con.close()
                                continue
                            if event & select.EPOLLOUT:
                                try:
                                    con = self._connections[fileno]
                                    con._out()
                                except Exception as e:
                                    con.close()
                                    continue
                            if event & (select.EPOLLHUP | select.EPOLLERR):
                                try:
                                    con = self._connections[fileno]
                                    con.close()
                                    continue
                                except:
                                    pass
            elif hasattr(select, 'kqueue'):
                for event in self._kqueue.control(None, 1):
                    fileno = event.ident
                    if fileno in self._servers:
                        server = self._servers[fileno]
                        server.handle_connection()
                    else:
                        if event.filter == select.KQ_FILTER_READ:
                            try:
                                con = self._connections[fileno]
                                con._in()
                            except Exception as e:  # noqa
                                con.close()
                                continue
                            if event.filter == select.KQ_FILTER_WRITE:
                                try:
                                    con = self._connections[fileno]
                                    con._out()
                                except Exception as e:  # noqa
                                    con.close()
                                    continue
                            if event.flags & select.KQ_EV_EOF:
                                try:
                                    con = self._connections[fileno]
                                    con.close()
                                    continue
                                except:
                                    pass
        else:
            # not using  epoll or kqueue
            n_connections = len(self._connections)
            if self._connections_found != n_connections:
                logger.debug("lib/connection.py poll() for len(self._connections)={}".format(n_connections))
                self._connections_found = n_connections

            watched = self._waitobj.get_watched()
            nwatched = len(watched)
            logger.debug("iowait in alpha status with {} watched connections".format(nwatched))
            events = self._waitobj.wait()
            nevents = len(events)
            logger.debug("iowait reports {} events".format(nevents))
            for fileno, read, write in events:
                logger.debug("event for fileno={}, read={}, write={}".format(fileno, read, write))
                if fileno in self._servers:
                    server = self._servers[fileno]
                    server.handle_connection()
                else:
                    logger.debug("fileno {} not in self._servers".format(fileno))
                    if read:
                        try:
                            con = self._connections[fileno]
                            con._in()
                        except Exception as e:  # noqa
                            con.close()
                            continue
                        if write:
                            try:
                                con = self._connections[fileno]
                                con._out()
                            except Exception as e:  # noqa
                                con.close()
                                continue


    def close(self):
        if -1 in self._connections:
            logger.error("Connections.close() tried to close a filenumber == -1")

        try:
            for fileno in self._connections:
                try:
                    self._connections[fileno].close()
                except:
                    pass
        except:
            pass