Properly cleanup all dialogs

On app close, all dialogs are properly cleaned up so there are no
dangling references and python can garbage collect.

While this isn't that important when the app shuts down, it ensures that
lifecycle changes while the app is running (vm start/stop/remove, conn
start/stop/remove) have the infrastructure to properly release resources.
This commit is contained in:
Cole Robinson 2011-04-13 09:27:02 -04:00
parent ff3606e740
commit f2781ee62a
15 changed files with 388 additions and 85 deletions

View File

@ -41,8 +41,8 @@ class vmmGObject(gobject.GObject):
self._gobject_timeouts = []
self._gconf_handles = []
self._object_key = str(self)
self.config.add_object(self._object_key)
self.object_key = str(self)
self.config.add_object(self.object_key)
def cleanup(self):
# Do any cleanup required to drop reference counts so object is
@ -78,6 +78,11 @@ class vmmGObject(gobject.GObject):
gobject.source_remove(handle)
self._gobject_timeouts.remove(handle)
def _printtrace(self, msg):
import traceback
print "%s (%s %s)\n:%s" % (msg, self.object_key, self.refcount(),
"".join(traceback.format_stack()))
def refcount(self):
# Function generates 2 temporary refs, so adjust total accordingly
return (sys.getrefcount(self) - 2)
@ -91,9 +96,9 @@ class vmmGObject(gobject.GObject):
getattr(gobject.GObject, "__del__")(self)
try:
self.config.remove_object(self._object_key)
self.config.remove_object(self.object_key)
except:
logging.exception("Error removing %s" % self._object_key)
logging.exception("Error removing %s" % self.object_key)
class vmmGObjectUI(vmmGObject):
def __init__(self, filename, windowname):

View File

@ -131,8 +131,33 @@ class vmmCloneVM(vmmGObjectUI):
self.change_mac_close()
self.change_storage_close()
self.topwin.hide()
self.orig_vm = None
self.clone_design = None
self.storage_list = {}
self.target_list = []
self.net_list = {}
self.mac_list = []
return 1
def cleanup(self):
self.close()
self.conn = None
self.change_mac.destroy()
self.change_mac = None
self.change_storage.destroy()
self.change_storage = None
if self.storage_browser:
self.storage_browser.cleanup()
self.storage_browser = None
vmmGObjectUI.cleanup(self)
def change_mac_close(self, ignore1=None, ignore2=None):
self.change_mac.hide()
return 1

View File

@ -146,6 +146,7 @@ class vmmConnection(vmmGObject):
self.hostinfo = None
self.hal_helper_remove_sig = None
self.hal_handles = []
self.netdev_initialized = False
self.netdev_error = ""
@ -164,6 +165,7 @@ class vmmConnection(vmmGObject):
sig = hal_helper.connect("device-removed",
self._haldev_removed)
self.hal_helper_remove_sig = sig
self.hal_handles.append(sig)
def _init_netdev(self):
"""
@ -186,7 +188,8 @@ class vmmConnection(vmmGObject):
else:
error = hal_helper.get_init_error()
if not error:
hal_helper.connect("netdev-added", self._netdev_added)
self.hal_handles.append(
hal_helper.connect("netdev-added", self._netdev_added))
self._set_hal_remove_sig(hal_helper)
else:
@ -225,7 +228,8 @@ class vmmConnection(vmmGObject):
else:
error = hal_helper.get_init_error()
if not error:
hal_helper.connect("optical-added", self._optical_added)
self.hal_handles.append(
hal_helper.connect("optical-added", self._optical_added))
self._set_hal_remove_sig(hal_helper)
else:
@ -922,6 +926,16 @@ class vmmConnection(vmmGObject):
self._change_state(self.STATE_DISCONNECTED)
def cleanup(self):
# Do this first, so signals are unregistered before we change state
vmmGObject.cleanup(self)
self.close()
hal_helper = self.get_hal_helper()
if hal_helper:
for h in self.hal_handles:
hal_helper.disconnect(h)
def _open_dev_conn(self, uri):
"""
Allow using virtinsts connection hacking to fake capabilities

View File

@ -155,9 +155,33 @@ class vmmCreate(vmmGObjectUI):
if self.config_window:
self.config_window.close()
if self.storage_browser:
self.storage_browser.close()
return 1
def cleanup(self):
self.close()
self.remove_conn()
self.conn = None
self.caps = None
self.capsguest = None
self.capsdomain = None
self.guest = None
self.disk = None
self.nic = None
try:
if self.storage_browser:
self.storage_browser.cleanup()
self.storage_browser = None
except:
logging.exception("Error cleaning up create")
vmmGObjectUI.cleanup(self)
def remove_timers(self):
try:
if self.host_storage_timer:
@ -166,15 +190,20 @@ class vmmCreate(vmmGObjectUI):
except:
pass
def remove_conn(self):
if not self.conn:
return
for signal in self.conn_signals:
self.conn.disconnect(signal)
self.conn_signals = []
self.conn = None
def set_conn(self, newconn, force_validate=False):
if self.conn == newconn and not force_validate:
return
if self.conn:
for signal in self.conn_signals:
self.conn.disconnect(signal)
self.conn_signals = []
self.remove_conn()
self.conn = newconn
if self.conn:
self.set_conn_state()

View File

@ -153,6 +153,26 @@ class vmmCreateInterface(vmmGObjectUI):
return 1
def cleanup(self):
self.close()
try:
self.conn = None
self.interface = None
self.ip_config.destroy()
self.ip_config = None
self.bridge_config.destroy()
self.bridge_config = None
self.bond_config.destroy()
self.bond_config = None
except:
logging.exception("Error cleaning up addiface")
vmmGObjectUI.cleanup(self)
###########################
# Initialization routines #
###########################

View File

@ -75,6 +75,21 @@ class vmmCreateNetwork(vmmGObjectUI):
self.reset_state()
self.topwin.present()
def is_visible(self):
if self.topwin.flags() & gtk.VISIBLE:
return 1
return 0
def close(self, ignore1=None, ignore2=None):
self.topwin.hide()
return 1
def cleanup(self):
self.close()
self.conn = None
vmmGObjectUI.cleanup(self)
def set_initial_state(self):
notebook = self.window.get_widget("create-pages")
notebook.set_show_tabs(False)
@ -310,14 +325,6 @@ class vmmCreateNetwork(vmmGObjectUI):
self.window.get_widget("create-finish").show()
self.window.get_widget("create-finish").grab_focus()
def close(self, ignore1=None, ignore2=None):
self.topwin.hide()
return 1
def is_visible(self):
if self.topwin.flags() & gtk.VISIBLE:
return 1
return 0
def finish(self, ignore=None):
name = self.get_config_name()

View File

@ -111,6 +111,14 @@ class vmmCreatePool(vmmGObjectUI):
self.topwin.hide()
return 1
def cleanup(self):
self.close()
self.conn = None
self._pool = None
vmmGObjectUI.cleanup(self)
def set_initial_state(self):
self.window.get_widget("pool-pages").set_show_tabs(False)

View File

@ -83,6 +83,14 @@ class vmmDeleteDialog(vmmGObjectUI):
self.conn = None
return 1
def cleanup(self):
self.close()
self.vm = None
self.conn = None
vmmGObjectUI.cleanup(self)
def reset_state(self):
# Set VM name in title'

View File

@ -45,10 +45,11 @@ from virtManager.create import vmmCreate
from virtManager.host import vmmHost
from virtManager.error import vmmErrorDialog
from virtManager.systray import vmmSystray
import virtManager.uihelpers as uihelpers
import virtManager.util as util
# Enable this to get a report of leaked objects on app shutdown
debug_ref_leaks = False
debug_ref_leaks = True
def default_uri():
tryuri = None
@ -246,8 +247,10 @@ class vmmEngine(vmmGObject):
self.init_systray()
self.config.on_stats_update_interval_changed(self.reschedule_timer)
self.config.on_view_system_tray_changed(self.system_tray_changed)
self.add_gconf_handle(
self.config.on_stats_update_interval_changed(self.reschedule_timer))
self.add_gconf_handle(
self.config.on_view_system_tray_changed(self.system_tray_changed))
self.schedule_timer()
self.load_stored_uris()
@ -349,8 +352,6 @@ class vmmEngine(vmmGObject):
def connect_to_uri(self, uri, readOnly=None, autoconnect=False,
do_start=True):
self.windowConnect = None
try:
conn = self._check_connection(uri)
if not conn:
@ -368,10 +369,9 @@ class vmmEngine(vmmGObject):
def _do_connect(self, src_ignore, uri):
return self.connect_to_uri(uri)
def _connect_cancelled(self, connect_ignore):
self.windowConnect = None
def _connect_cancelled(self, src):
if len(self.connections.keys()) == 0:
self.exit_app()
self.exit_app(src)
def _do_vm_removed(self, connection, hvuri, vmuuid):
@ -393,10 +393,6 @@ class vmmEngine(vmmGObject):
self.connections[hvuri]["windowDetails"][vmuuid].cleanup()
del(self.connections[hvuri]["windowDetails"][vmuuid])
if self.connections[hvuri]["windowHost"] is not None:
self.connections[hvuri]["windowHost"].close()
self.connections[hvuri]["windowHost"] = None
if (self.windowCreate and
self.windowCreate.conn and
self.windowCreate.conn.get_uri() == hvuri):
@ -461,18 +457,79 @@ class vmmEngine(vmmGObject):
def decrement_window_counter(self):
self.windows -= 1
logging.debug("window counter decremented to %s" % self.windows)
# Don't exit if system tray is enabled
if self.windows <= 0 and not self.systray.is_visible():
if (self.windows <= 0 and
self.systray and
not self.systray.is_visible()):
self.exit_app()
def exit_app(self, ignore_src=None):
conns = self.connections.values()
for conn in conns:
conn["connection"].close()
self.connections = {}
def cleanup(self):
try:
vmmGObject.cleanup(self)
uihelpers.cleanup()
self.err = None
if self.timer != None:
gobject.source_remove(self.timer)
if self.systray:
self.systray.cleanup()
self.systray = None
self.get_manager()
if self.windowManager:
self.windowManager.cleanup()
self.windowManager = None
if self.windowPreferences:
self.windowPreferences.cleanup()
self.windowPreferences = None
if self.windowAbout:
self.windowAbout.cleanup()
self.windowAbout = None
if self.windowConnect:
self.windowConnect.cleanup()
self.windowConnect = None
if self.windowCreate:
self.windowCreate.cleanup()
self.windowCreate = None
if self.windowMigrate:
self.windowMigrate.cleanup()
self.windowMigrate = None
# Do this last, so any manually 'disconnected' signals
# take precedence over cleanup signal removal
for uri in self.connections:
self.cleanup_connection(uri)
self.connections = {}
except:
logging.exception("Error cleaning up engine")
def exit_app(self, src=None):
if self.err is None:
# Already in cleanup
return
self.cleanup()
if debug_ref_leaks:
for name in self.config.get_objects():
objs = self.config.get_objects()
if src and src.object_key in objs:
# Whatever UI initiates the app exit will always appear
# to leak
logging.debug("Exitting app from %s, skipping leak check" %
src.object_key)
objs.remove(src.object_key)
# Engine will always appear to leak
objs.remove(self.object_key)
for name in objs:
logging.debug("Leaked %s" % name)
logging.debug("Exiting app normally.")
@ -502,13 +559,28 @@ class vmmEngine(vmmGObject):
return conn
def cleanup_connection(self, uri):
try:
if self.connections[uri]["windowHost"]:
self.connections[uri]["windowHost"].cleanup()
if self.connections[uri]["windowClone"]:
self.connections[uri]["windowClone"].cleanup()
details = self.connections[uri]["windowDetails"]
for win in details.values():
win.cleanup()
self.connections[uri]["connection"].cleanup()
except:
logging.exception("Error cleaning up conn in engine")
def remove_connection(self, uri):
conn = self.connections[uri]["connection"]
conn.close()
del self.connections[uri]
self.cleanup_connection(uri)
del(self.connections[uri])
self.emit("connection-removed", uri)
self.config.remove_connection(conn.get_uri())
self.config.remove_connection(uri)
def connect(self, name, callback, *args):
handle_id = vmmGObject.connect(self, name, callback, *args)
@ -683,7 +755,8 @@ class vmmEngine(vmmGObject):
def _do_show_manager(self, src):
try:
self.get_manager().show()
manager = self.get_manager()
manager.show()
except Exception, e:
if not src:
raise

View File

@ -312,6 +312,43 @@ class vmmHost(vmmGObjectUI):
self.engine.decrement_window_counter()
return 1
def cleanup(self):
self.close()
try:
self.conn = None
self.engine = None
if self.addnet:
self.addnet.cleanup()
self.addnet = None
if self.addpool:
self.addpool.cleanup()
self.addpool = None
if self.addvol:
self.addvol.cleanup()
self.addvol = None
if self.addinterface:
self.addinterface.cleanup()
self.addinterface = None
self.volmenu.destroy()
self.volmenu = None
self.cpu_usage_graph.destroy()
self.cpu_usage_graph = None
self.memory_usage_graph.destroy()
self.memory_usage_graph = None
except:
logging.exception("Error cleaning up host dialog")
vmmGObjectUI.cleanup(self)
def show_help(self, src_ignore):
self.emit("action-show-help", "virt-manager-host-window")

View File

@ -129,17 +129,18 @@ class vmmManager(vmmGObjectUI):
self.topwin.set_default_size(w or 550, h or 550)
self.prev_position = None
self.init_vmlist()
self.init_stats()
self.init_toolbar()
self.vmmenu = gtk.Menu()
self.vmmenushutdown = gtk.Menu()
self.vmmenu_items = {}
self.vmmenushutdown_items = {}
self.connmenu = gtk.Menu()
self.connmenu_items = {}
self.init_context_menus()
# There seem to be ref counting issues with calling
# list.get_column, so avoid it
self.diskcol = None
self.netcol = None
self.cpucol = None
self.window.signal_autoconnect({
"on_menu_view_cpu_usage_activate": (self.toggle_stats_visible,
@ -171,7 +172,12 @@ class vmmManager(vmmGObjectUI):
"on_menu_edit_preferences_activate": self.show_preferences,
"on_menu_help_about_activate": self.show_about,
"on_menu_help_activate": self.show_help,
})
})
self.init_vmlist()
self.init_stats()
self.init_toolbar()
self.init_context_menus()
# XXX: Help docs useless/out of date
self.window.get_widget("menu_help").hide()
@ -223,6 +229,38 @@ class vmmManager(vmmGObjectUI):
self.engine.decrement_window_counter()
return 1
def cleanup(self):
self.close()
try:
self.engine = None
self.rows = None
self.diskcol = None
self.cpucol = None
self.netcol = None
if self.delete_dialog:
self.delete_dialog.cleanup()
self.delete_dialog = None
self.vmmenu.destroy()
self.vmmenu = None
self.vmmenu_items = None
self.vmmenushutdown.destroy()
self.vmmenushutdown = None
self.vmmenushutdown_items = None
self.connmenu.destroy()
self.connmenu = None
self.connmenu_items = None
except:
logging.exception("Error cleaning up manager")
vmmGObjectUI.cleanup(self)
def is_visible(self):
return bool(self.topwin.flags() & gtk.VISIBLE)
@ -235,19 +273,25 @@ class vmmManager(vmmGObjectUI):
################
def init_stats(self):
self.config.on_vmlist_cpu_usage_visible_changed(
self.toggle_cpu_usage_visible_widget)
self.config.on_vmlist_disk_io_visible_changed(
self.toggle_disk_io_visible_widget)
self.config.on_vmlist_network_traffic_visible_changed(
self.toggle_network_traffic_visible_widget)
self.add_gconf_handle(
self.config.on_vmlist_cpu_usage_visible_changed(
self.toggle_cpu_usage_visible_widget))
self.add_gconf_handle(
self.config.on_vmlist_disk_io_visible_changed(
self.toggle_disk_io_visible_widget))
self.add_gconf_handle(
self.config.on_vmlist_network_traffic_visible_changed(
self.toggle_network_traffic_visible_widget))
# Register callbacks with the global stats enable/disable values
# that disable the associated vmlist widgets if reporting is disabled
self.config.on_stats_enable_disk_poll_changed(self.enable_polling,
cfg.STATS_DISK)
self.config.on_stats_enable_net_poll_changed(self.enable_polling,
cfg.STATS_NETWORK)
self.add_gconf_handle(
self.config.on_stats_enable_disk_poll_changed(self.enable_polling,
cfg.STATS_DISK))
self.add_gconf_handle(
self.config.on_stats_enable_net_poll_changed(self.enable_polling,
cfg.STATS_NETWORK))
self.window.get_widget("menu_view_stats_cpu").set_active(
self.config.is_vmlist_cpu_usage_visible())
@ -441,6 +485,9 @@ class vmmManager(vmmGObjectUI):
model.set_sort_column_id(COL_NAME, gtk.SORT_ASCENDING)
self.diskcol = diskIOCol
self.netcol = networkTrafficCol
self.cpucol = cpuUsageCol
##################
# Helper methods #
@ -1090,19 +1137,14 @@ class vmmManager(vmmGObjectUI):
widget.set_label(current_text)
def toggle_network_traffic_visible_widget(self, *ignore):
vmlist = self.window.get_widget("vm-list")
col = vmlist.get_column(COL_NETWORK)
col.set_visible(self.config.is_vmlist_network_traffic_visible())
self.netcol.set_visible(
self.config.is_vmlist_network_traffic_visible())
def toggle_disk_io_visible_widget(self, *ignore):
vmlist = self.window.get_widget("vm-list")
col = vmlist.get_column(COL_DISK)
col.set_visible(self.config.is_vmlist_disk_io_visible())
self.diskcol.set_visible(self.config.is_vmlist_disk_io_visible())
def toggle_cpu_usage_visible_widget(self, *ignore):
vmlist = self.window.get_widget("vm-list")
col = vmlist.get_column(COL_CPU)
col.set_visible(self.config.is_vmlist_cpu_usage_visible())
self.cpucol.set_visible(self.config.is_vmlist_cpu_usage_visible())
def toggle_stats_visible(self, src, stats_id):
visible = src.get_active()

View File

@ -91,6 +91,19 @@ class vmmMigrateDialog(vmmGObjectUI):
self.topwin.hide()
return 1
def cleanup(self):
self.close()
self.vm = None
self.conn = None
self.engine = None
self.destconn_rows = None
# Not sure why we need to do this manually, but it matters
self.window.get_widget("migrate-dest").get_model().clear()
vmmGObjectUI.cleanup(self)
def init_state(self):
# [hostname, conn, can_migrate, tooltip]
dest_model = gtk.ListStore(str, object, bool, str)

View File

@ -35,23 +35,23 @@ class vmmPreferences(vmmGObjectUI):
def __init__(self):
vmmGObjectUI.__init__(self, "vmm-preferences.glade", "vmm-preferences")
self.config.on_view_system_tray_changed(self.refresh_view_system_tray)
self.config.on_console_popup_changed(self.refresh_console_popup)
self.config.on_console_accels_changed(self.refresh_console_accels)
self.config.on_console_scaling_changed(self.refresh_console_scaling)
self.config.on_stats_update_interval_changed(self.refresh_update_interval)
self.config.on_stats_history_length_changed(self.refresh_history_length)
self.config.on_sound_local_changed(self.refresh_sound_local)
self.config.on_sound_remote_changed(self.refresh_sound_remote)
self.config.on_graphics_type_changed(self.refresh_graphics_type)
self.config.on_stats_enable_disk_poll_changed(self.refresh_disk_poll)
self.config.on_stats_enable_net_poll_changed(self.refresh_net_poll)
self.add_gconf_handle(self.config.on_view_system_tray_changed(self.refresh_view_system_tray))
self.add_gconf_handle(self.config.on_console_popup_changed(self.refresh_console_popup))
self.add_gconf_handle(self.config.on_console_accels_changed(self.refresh_console_accels))
self.add_gconf_handle(self.config.on_console_scaling_changed(self.refresh_console_scaling))
self.add_gconf_handle(self.config.on_stats_update_interval_changed(self.refresh_update_interval))
self.add_gconf_handle(self.config.on_stats_history_length_changed(self.refresh_history_length))
self.add_gconf_handle(self.config.on_sound_local_changed(self.refresh_sound_local))
self.add_gconf_handle(self.config.on_sound_remote_changed(self.refresh_sound_remote))
self.add_gconf_handle(self.config.on_graphics_type_changed(self.refresh_graphics_type))
self.add_gconf_handle(self.config.on_stats_enable_disk_poll_changed(self.refresh_disk_poll))
self.add_gconf_handle(self.config.on_stats_enable_net_poll_changed(self.refresh_net_poll))
self.config.on_confirm_forcepoweroff_changed(self.refresh_confirm_forcepoweroff)
self.config.on_confirm_poweroff_changed(self.refresh_confirm_poweroff)
self.config.on_confirm_pause_changed(self.refresh_confirm_pause)
self.config.on_confirm_removedev_changed(self.refresh_confirm_removedev)
self.config.on_confirm_interface_changed(self.refresh_confirm_interface)
self.add_gconf_handle(self.config.on_confirm_forcepoweroff_changed(self.refresh_confirm_forcepoweroff))
self.add_gconf_handle(self.config.on_confirm_poweroff_changed(self.refresh_confirm_poweroff))
self.add_gconf_handle(self.config.on_confirm_pause_changed(self.refresh_confirm_pause))
self.add_gconf_handle(self.config.on_confirm_removedev_changed(self.refresh_confirm_removedev))
self.add_gconf_handle(self.config.on_confirm_interface_changed(self.refresh_confirm_interface))
self.refresh_view_system_tray()
self.refresh_update_interval()

View File

@ -18,6 +18,8 @@
# MA 02110-1301 USA.
#
import logging
import gobject
import gtk
@ -90,7 +92,9 @@ class vmmSystray(vmmGObject):
self.init_systray_menu()
self.config.on_view_system_tray_changed(self.show_systray)
self.add_gconf_handle(
self.config.on_view_system_tray_changed(self.show_systray))
self.show_systray()
def is_visible(self):
@ -102,6 +106,20 @@ class vmmSystray(vmmGObject):
self.systray_icon and
self.systray_icon.is_embedded())
def cleanup(self):
vmmGObject.cleanup(self)
try:
self.err = None
if self.systray_menu:
self.systray_menu.destroy()
self.systray_menu = None
self.systray_icon = None
except:
logging.exception("Error cleaning up systray")
# Initialization routines
def init_systray_menu(self):

View File

@ -50,6 +50,10 @@ def set_error_parent(parent):
err_dial.set_parent(parent)
err_dial = err_dial
def cleanup():
global err_dial
err_dial = None
############################################################
# Helpers for shared storage UI between create/addhardware #
############################################################