engine: More work to fix cascading error dialogs

If a remote network connection stalls, the tick queue becomes backed
up while we wait for the hung connection to continue. While this
happens, the queue is filled up with other requests to poll the hung
connection.

When the connection finally times out, the tick thread closes the
connection via an idle callback. However before that callback gets
a chance to run, all the other poll requests for the dead connection
are processed, all launching their own error dialog.

Mark the connection as 'closing' before conn.close is scheduled, and
use it to short circuit the tick() routine.
This commit is contained in:
Cole Robinson 2014-04-16 10:32:52 -04:00
parent 341a453d28
commit 1f7604b241
2 changed files with 55 additions and 40 deletions

View File

@ -87,6 +87,7 @@ class vmmConnection(vmmGObject):
self.connectThread = None self.connectThread = None
self.connectError = None self.connectError = None
self._backend = virtinst.VirtualConnection(self._uri) self._backend = virtinst.VirtualConnection(self._uri)
self._closing = False
self._caps = None self._caps = None
self._caps_xml = None self._caps_xml = None
@ -957,6 +958,10 @@ class vmmConnection(vmmGObject):
self.config.set_conn_autoconnect(self.get_uri(), val) self.config.set_conn_autoconnect(self.get_uri(), val)
def close(self): def close(self):
if self.state != self.STATE_DISCONNECTED:
logging.debug("conn.close() uri=%s", self.get_uri())
self._closing = True
def cleanup(devs): def cleanup(devs):
for dev in devs.values(): for dev in devs.values():
try: try:
@ -1002,6 +1007,7 @@ class vmmConnection(vmmGObject):
self.vms = {} self.vms = {}
self._change_state(self.STATE_DISCONNECTED) self._change_state(self.STATE_DISCONNECTED)
self._closing = False
def _cleanup(self): def _cleanup(self):
self.close() self.close()
@ -1164,7 +1170,43 @@ class vmmConnection(vmmGObject):
kwargs["stats_update"] = False kwargs["stats_update"] = False
self.idle_emit("priority-tick", kwargs) self.idle_emit("priority-tick", kwargs)
def tick(self, stats_update, def tick(self, *args, **kwargs):
e = None
try:
self._tick(*args, **kwargs)
except KeyboardInterrupt:
raise
except Exception, e:
pass
if e is None:
return
from_remote = getattr(libvirt, "VIR_FROM_REMOTE", None)
from_rpc = getattr(libvirt, "VIR_FROM_RPC", None)
sys_error = getattr(libvirt, "VIR_ERR_SYSTEM_ERROR", None)
dom = -1
code = -1
if isinstance(e, libvirt.libvirtError):
dom = e.get_error_domain()
code = e.get_error_code()
logging.debug("Error polling connection %s",
self.get_uri(), exc_info=True)
if (dom in [from_remote, from_rpc] and
code in [sys_error]):
e = None
logging.debug("Not showing user error since libvirtd "
"appears to have stopped.")
self._closing = True
self.idle_add(self.close)
if e:
raise e # pylint: disable=raising-bad-type
def _tick(self, stats_update,
pollvm=False, pollnet=False, pollvm=False, pollnet=False,
pollpool=False, polliface=False, pollpool=False, polliface=False,
pollnodedev=False, pollmedia=False, pollnodedev=False, pollmedia=False,
@ -1173,7 +1215,7 @@ class vmmConnection(vmmGObject):
main update function: polls for new objects, updates stats, ... main update function: polls for new objects, updates stats, ...
@force: Perform the requested polling even if async events are in use @force: Perform the requested polling even if async events are in use
""" """
if self.state != self.STATE_ACTIVE: if self.state != self.STATE_ACTIVE or self._closing:
return return
if not pollvm: if not pollvm:

View File

@ -26,8 +26,8 @@ import logging
import re import re
import Queue import Queue
import threading import threading
import traceback
import libvirt
from virtinst import util from virtinst import util
from virtManager import packageutils from virtManager import packageutils
@ -336,45 +336,18 @@ class vmmEngine(vmmGObject):
def _handle_tick_queue(self): def _handle_tick_queue(self):
while True: while True:
ignore1, ignore2, obj, kwargs = self._tick_queue.get() ignore1, ignore2, conn, kwargs = self._tick_queue.get()
self._tick_single_conn(obj, kwargs)
self._tick_queue.task_done()
return 1
def _tick_single_conn(self, conn, kwargs):
e = None
try: try:
conn.tick(**kwargs) conn.tick(**kwargs)
except KeyboardInterrupt:
raise
except Exception, e: except Exception, e:
pass tb = "".join(traceback.format_exc())
error_msg = (_("Error polling connection '%s': %s")
% (conn.get_uri(), e))
self.idle_add(lambda: self.err.show_err(error_msg,
details=tb))
if e is None: self._tick_queue.task_done()
return return 1
from_remote = getattr(libvirt, "VIR_FROM_REMOTE", None)
from_rpc = getattr(libvirt, "VIR_FROM_RPC", None)
sys_error = getattr(libvirt, "VIR_ERR_SYSTEM_ERROR", None)
dom = -1
code = -1
if isinstance(e, libvirt.libvirtError):
dom = e.get_error_domain()
code = e.get_error_code()
if (dom in [from_remote, from_rpc] and
code in [sys_error]):
logging.exception("Could not refresh connection %s",
conn.get_uri())
logging.debug("Closing connection since libvirtd "
"appears to have stopped")
else:
error_msg = _("Error polling connection '%s': %s") \
% (conn.get_uri(), e)
self.idle_add(lambda: self.err.show_err(error_msg))
self.idle_add(conn.close)
def increment_window_counter(self, src): def increment_window_counter(self, src):