qubes: Allow for explicit closing of objects
This commit is contained in:
parent
8547d06cc9
commit
de8ff20976
@ -689,6 +689,21 @@ class PropertyHolder(qubes.events.Emitter):
|
||||
# pylint: disable=no-member
|
||||
self.log.fatal(msg)
|
||||
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
|
||||
# Remove all properties -- somewhere in them there are cyclic
|
||||
# references. This just removes all the properties, just in case.
|
||||
# They are removed directly, bypassing write_once.
|
||||
for prop in self.property_list():
|
||||
# pylint: disable=protected-access
|
||||
try:
|
||||
delattr(self, prop._attr_name)
|
||||
except AttributeError:
|
||||
pass
|
||||
|
||||
|
||||
# pylint: disable=wrong-import-position
|
||||
from qubes.vm import VMProperty
|
||||
from qubes.app import Qubes
|
||||
|
121
qubes/app.py
121
qubes/app.py
@ -32,6 +32,7 @@ import subprocess
|
||||
import sys
|
||||
import tempfile
|
||||
import time
|
||||
import traceback
|
||||
import uuid
|
||||
|
||||
import lxml.etree
|
||||
@ -138,20 +139,22 @@ class VirConnectWrapper(object):
|
||||
class VMMConnection(object):
|
||||
'''Connection to Virtual Machine Manager (libvirt)'''
|
||||
|
||||
def __init__(self, offline_mode=None):
|
||||
def __init__(self, app, offline_mode=None):
|
||||
'''
|
||||
|
||||
:param offline_mode: enable/disable offline mode; default is to
|
||||
enable when running in chroot as root, otherwise disable
|
||||
'''
|
||||
self._libvirt_conn = None
|
||||
self._xs = None
|
||||
self._xc = None
|
||||
self.app = app
|
||||
if offline_mode is None:
|
||||
offline_mode = bool(os.getuid() == 0 and
|
||||
os.stat('/') != os.stat('/proc/1/root/.'))
|
||||
self._offline_mode = offline_mode
|
||||
|
||||
self._libvirt_conn = None
|
||||
self._xs = None
|
||||
self._xc = None
|
||||
|
||||
@property
|
||||
def offline_mode(self):
|
||||
'''Check or enable offline mode (do not actually connect to vmm)'''
|
||||
@ -218,36 +221,15 @@ class VMMConnection(object):
|
||||
self.init_vmm_connection()
|
||||
return self._xc
|
||||
|
||||
def register_event_handlers(self, app):
|
||||
'''Register libvirt event handlers, which will translate libvirt
|
||||
events into qubes.events. This function should be called only in
|
||||
'qubesd' process and only when mainloop has been already set.
|
||||
'''
|
||||
self.libvirt_conn.domainEventRegisterAny(
|
||||
None, # any domain
|
||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
||||
self._domain_event_callback,
|
||||
app
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def _domain_event_callback(_conn, domain, event, _detail, opaque):
|
||||
'''Generic libvirt event handler (virConnectDomainEventCallback),
|
||||
translate libvirt event into qubes.events.
|
||||
'''
|
||||
app = opaque
|
||||
try:
|
||||
vm = app.domains[domain.name()]
|
||||
except KeyError:
|
||||
# ignore events for unknown domains
|
||||
return
|
||||
|
||||
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
|
||||
vm.fire_event('domain-shutdown')
|
||||
|
||||
def __del__(self):
|
||||
def close(self):
|
||||
libvirt.registerErrorHandler(None, None)
|
||||
if self._xs:
|
||||
self._xs.close()
|
||||
self._xs = None
|
||||
if self._libvirt_conn:
|
||||
self._libvirt_conn.close()
|
||||
self._libvirt_conn = None
|
||||
self._xc = None # and pray it will get garbage-collected
|
||||
|
||||
|
||||
class QubesHost(object):
|
||||
@ -398,6 +380,12 @@ class VMCollection(object):
|
||||
self._dict = dict()
|
||||
|
||||
|
||||
def close(self):
|
||||
del self.app
|
||||
self._dict.clear()
|
||||
del self._dict
|
||||
|
||||
|
||||
def __repr__(self):
|
||||
return '<{} {!r}>'.format(
|
||||
self.__class__.__name__, list(sorted(self.keys())))
|
||||
@ -729,6 +717,10 @@ class Qubes(qubes.PropertyHolder):
|
||||
**kwargs):
|
||||
#: logger instance for logging global messages
|
||||
self.log = logging.getLogger('app')
|
||||
self.log.debug('init() -> %#x', id(self))
|
||||
self.log.debug('stack:')
|
||||
for frame in traceback.extract_stack():
|
||||
self.log.debug('%s', frame)
|
||||
|
||||
self._extensions = qubes.ext.get_extensions()
|
||||
|
||||
@ -742,7 +734,7 @@ class Qubes(qubes.PropertyHolder):
|
||||
self.pools = {}
|
||||
|
||||
#: Connection to VMM
|
||||
self.vmm = VMMConnection(offline_mode=offline_mode)
|
||||
self.vmm = VMMConnection(self, offline_mode=offline_mode)
|
||||
|
||||
#: Information about host system
|
||||
self.host = QubesHost(self)
|
||||
@ -759,6 +751,7 @@ class Qubes(qubes.PropertyHolder):
|
||||
|
||||
self.__load_timestamp = None
|
||||
self.__locked_fh = None
|
||||
self._domain_event_callback_id = None
|
||||
|
||||
#: jinja2 environment for libvirt XML templates
|
||||
self.env = jinja2.Environment(
|
||||
@ -910,6 +903,40 @@ class Qubes(qubes.PropertyHolder):
|
||||
self._release_lock()
|
||||
|
||||
|
||||
def close(self):
|
||||
'''Deconstruct the object and break circular references
|
||||
|
||||
After calling this the object is unusable, not even for saving.'''
|
||||
|
||||
self.log.debug('close() <- %#x', id(self))
|
||||
for frame in traceback.extract_stack():
|
||||
self.log.debug('%s', frame)
|
||||
|
||||
super().close()
|
||||
|
||||
if self._domain_event_callback_id is not None:
|
||||
self.vmm.libvirt_conn.domainEventDeregisterAny(
|
||||
self._domain_event_callback_id)
|
||||
self._domain_event_callback_id = None
|
||||
|
||||
# Only our Lord, The God Almighty, knows what references
|
||||
# are kept in extensions.
|
||||
del self._extensions
|
||||
|
||||
for vm in self.domains:
|
||||
vm.close()
|
||||
self.domains.close()
|
||||
del self.domains
|
||||
|
||||
self.vmm.close()
|
||||
del self.vmm
|
||||
|
||||
del self.host
|
||||
|
||||
if self.__locked_fh:
|
||||
self._release_lock()
|
||||
|
||||
|
||||
def _acquire_lock(self, for_save=False):
|
||||
assert self.__locked_fh is None, 'double lock'
|
||||
|
||||
@ -1129,6 +1156,34 @@ class Qubes(qubes.PropertyHolder):
|
||||
raise qubes.exc.QubesException('No driver %s for pool %s' %
|
||||
(driver, name))
|
||||
|
||||
def register_event_handlers(self):
|
||||
'''Register libvirt event handlers, which will translate libvirt
|
||||
events into qubes.events. This function should be called only in
|
||||
'qubesd' process and only when mainloop has been already set.
|
||||
'''
|
||||
self._domain_event_callback_id = (
|
||||
self.vmm.libvirt_conn.domainEventRegisterAny(
|
||||
None, # any domain
|
||||
libvirt.VIR_DOMAIN_EVENT_ID_LIFECYCLE,
|
||||
self._domain_event_callback,
|
||||
None))
|
||||
|
||||
def _domain_event_callback(self, _conn, domain, event, _detail, _opaque):
|
||||
'''Generic libvirt event handler (virConnectDomainEventCallback),
|
||||
translate libvirt event into qubes.events.
|
||||
'''
|
||||
if not self.events_enabled:
|
||||
return
|
||||
|
||||
try:
|
||||
vm = self.domains[domain.name()]
|
||||
except KeyError:
|
||||
# ignore events for unknown domains
|
||||
return
|
||||
|
||||
if event == libvirt.VIR_DOMAIN_EVENT_STOPPED:
|
||||
vm.fire_event('domain-shutdown')
|
||||
|
||||
@qubes.events.handler('domain-pre-delete')
|
||||
def on_domain_pre_deleted(self, event, vm):
|
||||
# pylint: disable=unused-argument
|
||||
|
@ -106,6 +106,8 @@ class Emitter(object, metaclass=EmitterMeta):
|
||||
self.events_enabled = False
|
||||
self.__handlers__ = collections.defaultdict(set)
|
||||
|
||||
def close(self):
|
||||
self.events_enabled = False
|
||||
|
||||
def add_handler(self, event, func):
|
||||
'''Add event handler to subject's class.
|
||||
|
@ -35,7 +35,7 @@ def main(args=None):
|
||||
loop.close()
|
||||
raise
|
||||
|
||||
args.app.vmm.register_event_handlers(args.app)
|
||||
args.app.register_event_handlers()
|
||||
|
||||
if args.debug:
|
||||
qubes.log.enable_debug()
|
||||
|
@ -297,6 +297,22 @@ class BaseVM(qubes.PropertyHolder):
|
||||
if hasattr(self, 'name'):
|
||||
self.init_log()
|
||||
|
||||
def close(self):
|
||||
super().close()
|
||||
|
||||
if self._qdb_connection_watch is not None:
|
||||
asyncio.get_event_loop().remove_reader(
|
||||
self._qdb_connection_watch.watch_fd())
|
||||
self._qdb_connection_watch.close()
|
||||
del self._qdb_connection_watch
|
||||
|
||||
del self.app
|
||||
del self.features
|
||||
del self.storage
|
||||
# TODO storage may have circ references, but it doesn't leak fds
|
||||
del self.devices
|
||||
del self.tags
|
||||
|
||||
def load_extras(self):
|
||||
# features
|
||||
for node in self.xml.xpath('./features/feature'):
|
||||
|
Loading…
Reference in New Issue
Block a user