Skip to content

Commit

Permalink
WebviewWidget: avoid hangs with webengine
Browse files Browse the repository at this point in the history
Waiting for execution to complete was handled in an unsafe way. As all
js executions set the same flag when they were done, it was possible
that multiple executions set the flag before the thread waiting for them
could have noticed it. As a result, if two or more executions ended at
the same time the first waiting thread to notice that would clear the
flag and remaining threads would keep waiting indefinetly.

Should fix ocasionally stalled PyQt5 builds on travis.
  • Loading branch information
astaric committed Apr 7, 2017
1 parent 0858e4a commit 3b11a90
Show file tree
Hide file tree
Showing 2 changed files with 37 additions and 7 deletions.
42 changes: 36 additions & 6 deletions Orange/widgets/utils/webview.py
Expand Up @@ -5,6 +5,7 @@
(extends QWebView), as available.
"""
import os
import threading
from os.path import join, dirname, abspath
import warnings
from random import random
Expand Down Expand Up @@ -444,6 +445,36 @@ def exposeObject(self, name, obj):


elif HAVE_WEBENGINE:
class IdStore:
"""Generates and stores unique ids.
Used in WebviewWidget._evalJS below to match scheduled js executions
and returned results. WebEngine operations are async, so locking is
used to guard against problems that could occur if multiple executions
ended at exactly the same time.
"""

def __init__(self):
self.id = 0
self.lock = threading.Lock()
self.ids = set()

def create(self):
with self.lock:
self.id += 1
return self.id

def store(self, id):
with self.lock:
self.ids.add(id)

def __contains__(self, id):
return id in self.ids

def remove(self, id):
with self.lock:
self.ids.remove(id)


class _JSObjectChannel(QObject):
""" This class hopefully prevent options data from being
Expand Down Expand Up @@ -487,7 +518,6 @@ def is_all_exposed(self):
@inherit_docstrings
class WebviewWidget(_WebViewBase, WebEngineView):
_html = _NOTSET
_result = _NOTSET

def __init__(self, parent=None, bridge=None, *, debug=False, **kwargs):
WebEngineView.__init__(self, parent, bridge, debug=debug, **kwargs)
Expand All @@ -502,18 +532,18 @@ def __init__(self, parent=None, bridge=None, *, debug=False, **kwargs):
self._jsobject_channel = jsobj = _JSObjectChannel(self)
self.page().webChannel().registerObject(
'__js_object_channel', jsobj)
self._results = IdStore()

def _evalJS(self, code):
while not self._jsobject_channel.is_all_exposed():
qApp.processEvents(QEventLoop.ExcludeUserInputEvents)
if sip.isdeleted(self):
return
self.runJavaScript(code,
lambda result: setattr(self, '_result', result))
while self._result is _NOTSET:
result = self._results.create()
self.runJavaScript(code, lambda x: self._results.store(result))
while result not in self._results:
qApp.processEvents(QEventLoop.ExcludeUserInputEvents)
result, self._result = self._result, _NOTSET
return result
self._results.remove(result)

def onloadJS(self, code):
self._onloadJS(code, injection_point=QWebEngineScript.Deferred)
Expand Down
2 changes: 1 addition & 1 deletion Orange/widgets/visualize/owmap.py
Expand Up @@ -370,7 +370,7 @@ def _update_js_markers(self, visible, in_subset):
raw_values=self._raw_sizes[visible]))
self.evalJS('''
window.latlon_data = latlon_data.data;
window.selected_markers = selected_markers.data
window.selected_markers = selected_markers.data;
add_markers(latlon_data);
''')

Expand Down

0 comments on commit 3b11a90

Please sign in to comment.