Don't modify status widgets outside of main thread

The sync module would indirectly modify the status widget (so that
the count of sync actions would be up to date).  If this happened
while urwid was drawing the screen, urwid may invalidate cached
information in mid-redraw, resulting in an exception and crash.
This updates the StatusHeader widget so that changes are merely
recorded to internal variables, and the actual widgets are updated
by a refresh method called from within the main loop.

Change-Id: I27043533d33d44aae31005670a1b727e9cc7bb90
Story: 260
This commit is contained in:
James E. Blair 2014-09-02 16:52:55 -07:00
parent 949cea5475
commit 6f52a7c870
2 changed files with 49 additions and 20 deletions

View File

@ -53,27 +53,55 @@ class StatusHeader(urwid.WidgetWrap):
def __init__(self, app): def __init__(self, app):
super(StatusHeader, self).__init__(urwid.Columns([])) super(StatusHeader, self).__init__(urwid.Columns([]))
self.app = app self.app = app
self.title = urwid.Text(u'Start') self.title_widget = urwid.Text(u'Start')
self.error = urwid.Text('') self.error_widget = urwid.Text('')
self.offline = urwid.Text('') self.offline_widget = urwid.Text('')
self.sync = urwid.Text(u'Sync: 0') self.sync_widget = urwid.Text(u'Sync: 0')
self._w.contents.append((self.title, ('pack', None, False))) self._w.contents.append((self.title_widget, ('pack', None, False)))
self._w.contents.append((urwid.Text(u''), ('weight', 1, False))) self._w.contents.append((urwid.Text(u''), ('weight', 1, False)))
self._w.contents.append((self.error, ('pack', None, False))) self._w.contents.append((self.error_widget, ('pack', None, False)))
self._w.contents.append((self.offline, ('pack', None, False))) self._w.contents.append((self.offline_widget, ('pack', None, False)))
self._w.contents.append((self.sync, ('pack', None, False))) self._w.contents.append((self.sync_widget, ('pack', None, False)))
self.error = None
self.offline = None
self.title = None
self.sync = None
self._error = False
self._offline = False
self._title = ''
self._sync = 0
def update(self, title=None, error=False, offline=None): def update(self, title=None, error=None, offline=None, refresh=True):
if title: if title is not None:
self.title.set_text(title) self.title = title
if error: if error is not None:
self.error.set_text(('error', u'Error')) self.error = error
if offline is not None: if offline is not None:
if offline: self.offline = offline
self.error.set_text(u'Offline') self.sync = self.app.sync.queue.qsize()
if refresh:
self.refresh()
def refresh(self):
if self._title != self.title:
self._title = self.title
self.title_widget.set_text(self._title)
if self._error != self.error:
self._error = self.error
if self.error:
self.error_widget.set_text(('error', u'Error'))
else: else:
self.error.set_text(u'') self.error_widget.set_text(u'')
self.sync.set_text(u' Sync: %i' % self.app.sync.queue.qsize()) if self._offline != self.offline:
self._offline = self.offline
if self._offline:
self.offline_widget.set_text(u'Offline')
else:
self.offline_widget.set_text(u'')
if self._sync != self.sync:
self._sync = self.sync
self.sync_widget.set_text(u' Sync: %i' % self._sync)
class SearchDialog(mywid.ButtonDialog): class SearchDialog(mywid.ButtonDialog):
signals = ['search', 'cancel'] signals = ['search', 'cancel']
@ -194,6 +222,7 @@ class App(object):
self.loop.widget = widget self.loop.widget = widget
def refresh(self, data=None): def refresh(self, data=None):
self.status.refresh()
widget = self.loop.widget widget = self.loop.widget
while isinstance(widget, urwid.Overlay): while isinstance(widget, urwid.Overlay):
widget = widget.contents[0][0] widget = widget.contents[0][0]

View File

@ -790,16 +790,16 @@ class Sync(object):
self.submitTask(SyncSubscribedProjectsTask(HIGH_PRIORITY)) self.submitTask(SyncSubscribedProjectsTask(HIGH_PRIORITY))
self.submitTask(UploadReviewsTask(HIGH_PRIORITY)) self.submitTask(UploadReviewsTask(HIGH_PRIORITY))
self.offline = True self.offline = True
self.app.status.update(offline=True) self.app.status.update(offline=True, refresh=False)
os.write(pipe, 'refresh\n') os.write(pipe, 'refresh\n')
time.sleep(30) time.sleep(30)
return task return task
except Exception: except Exception:
task.complete(False) task.complete(False)
self.log.exception('Exception running task %s' % (task,)) self.log.exception('Exception running task %s' % (task,))
self.app.status.update(error=True) self.app.status.update(error=True, refresh=False)
self.offline = False self.offline = False
self.app.status.update(offline=False) self.app.status.update(offline=False, refresh=False)
os.write(pipe, 'refresh\n') os.write(pipe, 'refresh\n')
return None return None