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):
super(StatusHeader, self).__init__(urwid.Columns([]))
self.app = app
self.title = urwid.Text(u'Start')
self.error = urwid.Text('')
self.offline = urwid.Text('')
self.sync = urwid.Text(u'Sync: 0')
self._w.contents.append((self.title, ('pack', None, False)))
self.title_widget = urwid.Text(u'Start')
self.error_widget = urwid.Text('')
self.offline_widget = urwid.Text('')
self.sync_widget = urwid.Text(u'Sync: 0')
self._w.contents.append((self.title_widget, ('pack', None, 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.offline, ('pack', None, False)))
self._w.contents.append((self.sync, ('pack', None, False)))
self._w.contents.append((self.error_widget, ('pack', None, False)))
self._w.contents.append((self.offline_widget, ('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):
if title:
self.title.set_text(title)
if error:
self.error.set_text(('error', u'Error'))
def update(self, title=None, error=None, offline=None, refresh=True):
if title is not None:
self.title = title
if error is not None:
self.error = error
if offline is not None:
if offline:
self.error.set_text(u'Offline')
self.offline = 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:
self.error.set_text(u'')
self.sync.set_text(u' Sync: %i' % self.app.sync.queue.qsize())
self.error_widget.set_text(u'')
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):
signals = ['search', 'cancel']
@ -194,6 +222,7 @@ class App(object):
self.loop.widget = widget
def refresh(self, data=None):
self.status.refresh()
widget = self.loop.widget
while isinstance(widget, urwid.Overlay):
widget = widget.contents[0][0]

View File

@ -790,16 +790,16 @@ class Sync(object):
self.submitTask(SyncSubscribedProjectsTask(HIGH_PRIORITY))
self.submitTask(UploadReviewsTask(HIGH_PRIORITY))
self.offline = True
self.app.status.update(offline=True)
self.app.status.update(offline=True, refresh=False)
os.write(pipe, 'refresh\n')
time.sleep(30)
return task
except Exception:
task.complete(False)
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.app.status.update(offline=False)
self.app.status.update(offline=False, refresh=False)
os.write(pipe, 'refresh\n')
return None