handle SEC node connection properly in client

baseclient.py:
- select was not used properly, creating a busy loop
- added stop function in TCPConnection

mainwindow.py:
- fixed behaviour when a connection is broken:
  a message is shown, and the node is removed from the tree

Change-Id: I7223dfd9ea027681aff089f2fa16e134a16a7b84
Reviewed-on: https://forge.frm2.tum.de/review/20922
Tested-by: JenkinsCodeReview <bjoern_pedersen@frm2.tum.de>
Reviewed-by: Enrico Faulhaber <enrico.faulhaber@frm2.tum.de>
This commit is contained in:
zolliker 2019-07-16 15:13:20 +02:00 committed by Enrico Faulhaber
parent e1f017d678
commit 7c620901c9
2 changed files with 68 additions and 50 deletions

View File

@ -68,7 +68,7 @@ class TCPConnection(object):
self._readbuffer = queue.Queue(100)
io = socket.create_connection((self._host, self._port))
io.setblocking(False)
io.settimeout(0.3)
self.stopflag = False
self._io = io
if self._thread and self._thread.is_alive():
return
@ -76,49 +76,60 @@ class TCPConnection(object):
def _run(self):
try:
data = u''
while True:
newdata = b''
try:
dlist = [self._io.fileno()]
rlist, wlist, xlist = select(dlist, dlist, dlist, 1)
if dlist[0] in rlist + wlist:
newdata = self._io.recv(1024)
if dlist[0] in xlist:
print("Problem: exception on socket, reconnecting!")
for cb, arg in self.callbacks:
cb(arg)
return
except socket.timeout:
pass
except Exception as err:
print(err, "reconnecting")
for cb, arg in self.callbacks:
cb(arg)
return
data += newdata.decode('latin-1')
while '\n' in data:
line, data = data.split('\n', 1)
data = b''
while not self.stopflag:
rlist, _, xlist = select([self._io], [], [self._io], 1)
if xlist:
# on some strange systems, a closed connection is indicated by
# an exceptional condition instead of "read ready" + "empty recv"
newdata = b''
else:
if not rlist:
continue # check stopflag every second
# self._io is now ready to read some bytes
try:
self._readbuffer.put(line.strip('\r'),
block=True,
timeout=1)
newdata = self._io.recv(1024)
except socket.error as err:
if err.args[0] == socket.EAGAIN:
# if we receive an EAGAIN error, just continue
continue
newdata = b''
except Exception:
newdata = b''
if not newdata: # no data on recv indicates a closed connection
raise IOError('%s:%d disconnected' % (self._host, self._port))
lines = (data + newdata).split(b'\n')
for line in lines[:-1]: # last line is incomplete or empty
try:
self._readbuffer.put(line.strip(b'\r').decode('utf-8'),
block=True, timeout=1)
except queue.Full:
self.log.debug('rcv queue full! dropping line: %r' %
line)
finally:
self._thread = None
self.log.debug('rcv queue full! dropping line: %r' % line)
data = lines[-1]
except Exception as err:
self.log.error(err)
try:
self._io.shutdown(socket.SHUT_RDWR)
except socket.error:
pass
try:
self._io.close()
except socket.error:
pass
for cb, args in self.callbacks:
cb(*args)
def readline(self, block=False):
"""blocks until a full line was read and returns it"""
i = 10
while i:
try:
return self._readbuffer.get(block=True, timeout=1)
except queue.Empty:
pass
if not block:
i -= 1
def readline(self, timeout=None):
"""blocks until a full line was read and returns it
returns None when connection is stopped"""
if self.stopflag:
return None
return self._readbuffer.get(block=True, timeout=timeout)
def stop(self):
self.stopflag = True
self._readbuffer.put(None) # terminate pending readline
def readable(self):
return not self._readbuffer.empty()
@ -239,6 +250,8 @@ class Client(object):
while not self.stopflag:
line = self.connection.readline()
if line is None: # connection stopped
break
self.connection_established = True
self.log.debug('got answer %r' % line)
if line.startswith(('SECoP', 'SINE2020&ISSE,SECoP')):
@ -396,8 +409,8 @@ class Client(object):
self.callbacks.setdefault('%s:%s' % (module, parameter),
set()).discard(cb)
def register_shutdown_callback(self, func, arg):
self.connection.callbacks.append((func, arg))
def register_shutdown_callback(self, func, *args):
self.connection.callbacks.append((func, args))
def communicate(self, msgtype, spec='', data=None):
# only return the data portion....
@ -470,10 +483,11 @@ class Client(object):
def quit(self):
# after calling this the client is dysfunctional!
self.communicate(DISABLEEVENTSREQUEST)
# self.communicate(DISABLEEVENTSREQUEST)
self.stopflag = True
self.connection.stop()
if self._thread and self._thread.is_alive():
self.thread.join(self._thread)
self._thread.join(10)
def startup(self, _async=False):
self._issueDescribe()

View File

@ -65,6 +65,7 @@ class QSECNode(SECNode, QObject):
class MainWindow(QMainWindow):
showMessageSignal = pyqtSignal(str, str)
def __init__(self, parent=None):
super(MainWindow, self).__init__(parent)
@ -83,6 +84,7 @@ class MainWindow(QMainWindow):
self._paramCtrls = {}
self._topItems = {}
self._currentWidget = self.splitter.widget(1).layout().takeAt(0)
self.showMessageSignal.connect(self.showMessage)
# add localhost (if available) and SEC nodes given as arguments
args = sys.argv[1:]
@ -129,15 +131,17 @@ class MainWindow(QMainWindow):
current.parent().text(0), current.text(0))
def _removeSubTree(self, toplevel_item):
#....
pass
self.treeWidget.invisibleRootItem().removeChild(toplevel_item)
def _nodeDisconnected_callback(self, host):
node = self._nodes[host]
topItem = self._topItems[node]
self._removeSubTree(topItem)
self._removeSubTree(self._topItems[node])
del self._topItems[node]
node.quit()
QMessageBox(self.parent(), repr(host))
self.showMessageSignal.emit('connection closed', 'connection to %s closed' % host)
def showMessage(self, title, text):
QMessageBox.warning(self.parent(), title, text)
def _addNode(self, host):