qubes/libvirtaio: document and prepare for upstream
QubesOS/qubes-issues#2622
This commit is contained in:
parent
a5c59a5075
commit
80807fb872
@ -1,27 +1,107 @@
|
|||||||
#!/usr/bin/env python3
|
#!/usr/bin/env python3
|
||||||
|
|
||||||
|
#
|
||||||
|
# Copyright 2017 Wojtek Porczyk <woju@invisiblethingslab.com>
|
||||||
|
#
|
||||||
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
# you may not use this file except in compliance with the License.
|
||||||
|
# You may obtain a copy of the License at
|
||||||
|
#
|
||||||
|
# http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
#
|
||||||
|
# Unless required by applicable law or agreed to in writing, software
|
||||||
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
# See the License for the specific language governing permissions and
|
||||||
|
# limitations under the License.
|
||||||
|
#
|
||||||
|
|
||||||
|
'''libvirtaio -- libvirt event loop implementation using asyncio
|
||||||
|
|
||||||
|
Register the implementation of default loop:
|
||||||
|
|
||||||
|
>>> import libvirtaio
|
||||||
|
>>> impl = libvirtaio.LibvirtAsyncIOEventImpl()
|
||||||
|
>>> impl.register()
|
||||||
|
|
||||||
|
Register the implementation on specific loop:
|
||||||
|
|
||||||
|
>>> import asyncio
|
||||||
|
>>> import libvirtaio
|
||||||
|
>>> impl = libvirtaio.LibvirtAsyncIOEventImpl(loop=asyncio.get_event_loop())
|
||||||
|
>>> impl.register()
|
||||||
|
|
||||||
|
This module also contains an execute_ff_callback function to be used from other
|
||||||
|
implementation, which parses the opaque object and executes the ff callback.
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html
|
||||||
|
'''
|
||||||
|
|
||||||
|
__version__ = '1.0'
|
||||||
|
__author__ = 'Wojtek Porczyk'
|
||||||
|
__all__ = ['LibvirtAsyncIOEventImpl', 'execute_ff_callback']
|
||||||
|
|
||||||
import asyncio
|
import asyncio
|
||||||
import ctypes
|
import ctypes
|
||||||
import itertools
|
import itertools
|
||||||
import logging
|
import logging
|
||||||
|
import warnings
|
||||||
|
|
||||||
import libvirt
|
import libvirt
|
||||||
|
|
||||||
|
try:
|
||||||
|
asyncio.ensure_future
|
||||||
|
except AttributeError:
|
||||||
|
# python < 3.4.4 (Debian < stretch, Fedora < 24)
|
||||||
|
asyncio.ensure_future = asyncio.async
|
||||||
|
|
||||||
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
|
ctypes.pythonapi.PyCapsule_GetPointer.restype = ctypes.c_void_p
|
||||||
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (
|
ctypes.pythonapi.PyCapsule_GetPointer.argtypes = (
|
||||||
ctypes.py_object, ctypes.c_char_p)
|
ctypes.py_object, ctypes.c_char_p)
|
||||||
|
|
||||||
virFreeCallback = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
|
virFreeCallback = ctypes.CFUNCTYPE(None, ctypes.c_void_p)
|
||||||
|
|
||||||
try:
|
def execute_ff_callback(opaque):
|
||||||
asyncio.ensure_future
|
'''Execute callback which frees the opaque buffer
|
||||||
except AttributeError:
|
|
||||||
asyncio.ensure_future = asyncio.async
|
.. warning::
|
||||||
|
This function should not be called from any called by libvirt's core.
|
||||||
|
It will most probably cause deadlock in C-level libvirt code. Instead it
|
||||||
|
should be scheduled and called from our stack.
|
||||||
|
|
||||||
|
See https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddHandleFunc
|
||||||
|
for more information.
|
||||||
|
|
||||||
|
This function is not dependent on any event loop implementation and can be
|
||||||
|
freely stolen. Also be vary that it introspects theoretically opaque objects
|
||||||
|
and can break when upgrading libvirt.
|
||||||
|
'''
|
||||||
|
|
||||||
|
# Now this is cheating but we have no better option. The opaque object is
|
||||||
|
# really a 3-tuple, which contains a the real opaque pointer and the ff
|
||||||
|
# callback, both of which are inside PyCapsules. If not specified, the ff
|
||||||
|
# may be None.
|
||||||
|
dummy, caps_opaque, caps_ff = opaque
|
||||||
|
ff = virFreeCallback(ctypes.pythonapi.PyCapsule_GetPointer(
|
||||||
|
caps_ff, b'virFreeCallback'))
|
||||||
|
if ff:
|
||||||
|
real_opaque = ctypes.pythonapi.PyCapsule_GetPointer(
|
||||||
|
caps_opaque, b'void*')
|
||||||
|
ff(real_opaque)
|
||||||
|
|
||||||
|
|
||||||
class LibvirtAsyncIOEventImpl(object):
|
|
||||||
class Callback(object):
|
class Callback(object):
|
||||||
|
'''Base class for holding callback
|
||||||
|
|
||||||
|
:param LibvirtAsyncIOEventImpl impl: the implementation in which we run
|
||||||
|
:param cb: the callback itself
|
||||||
|
:param opaque: the opaque tuple passed by libvirt
|
||||||
|
'''
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
_iden_counter = itertools.count()
|
_iden_counter = itertools.count()
|
||||||
|
|
||||||
def __init__(self, impl, cb, opaque, *args, **kwargs):
|
def __init__(self, impl, cb, opaque, *args, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.iden = next(self._iden_counter)
|
self.iden = next(self._iden_counter)
|
||||||
@ -34,127 +114,284 @@ class LibvirtAsyncIOEventImpl(object):
|
|||||||
self.iden, self.impl.callbacks[self.iden])
|
self.iden, self.impl.callbacks[self.iden])
|
||||||
self.impl.callbacks[self.iden] = self
|
self.impl.callbacks[self.iden] = self
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{} iden={}>'.format(self.__clas__.__name__, self.iden)
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
|
'''Schedule *ff* callback'''
|
||||||
self.impl.log.debug('callback %d close(), scheduling ff', self.iden)
|
self.impl.log.debug('callback %d close(), scheduling ff', self.iden)
|
||||||
|
self.impl.schedule_ff_callback(self.opaque)
|
||||||
|
|
||||||
# Now this is cheating but we have no better option.
|
#
|
||||||
dummy, caps_opaque, caps_ff = self.opaque
|
# file descriptors
|
||||||
ff = virFreeCallback(ctypes.pythonapi.PyCapsule_GetPointer(
|
#
|
||||||
caps_ff, b'virFreeCallback'))
|
|
||||||
|
|
||||||
if ff:
|
class Descriptor(object):
|
||||||
real_opaque = ctypes.pythonapi.PyCapsule_GetPointer(
|
'''Manager of one file descriptor
|
||||||
caps_opaque, b'void*')
|
|
||||||
self.impl.loop.call_soon(ff, real_opaque)
|
|
||||||
|
|
||||||
|
:param LibvirtAsyncIOEventImpl impl: the implementation in which we run
|
||||||
|
:param int fd: the file descriptor
|
||||||
|
'''
|
||||||
|
def __init__(self, impl, fd):
|
||||||
|
self.impl = impl
|
||||||
|
self.fd = fd
|
||||||
|
self.callbacks = {}
|
||||||
|
|
||||||
|
def _handle(self, event):
|
||||||
|
'''Dispatch the event to the descriptors
|
||||||
|
|
||||||
|
:param int event: The event (from libvirt's constants) being dispatched
|
||||||
|
'''
|
||||||
|
for callback in self.callbacks.values():
|
||||||
|
if callback.event is not None and callback.event & event:
|
||||||
|
callback.cb(callback.iden, self.fd, event, callback.opaque)
|
||||||
|
|
||||||
|
def update(self):
|
||||||
|
'''Register or unregister callbacks at event loop
|
||||||
|
|
||||||
|
This should be called after change of any ``.event`` in callbacks.
|
||||||
|
'''
|
||||||
|
# It seems like loop.add_{reader,writer} can be run multiple times
|
||||||
|
# and will still register the callback only once. Likewise,
|
||||||
|
# remove_{reader,writer} may be run even if the reader/writer
|
||||||
|
# is not registered (and will just return False).
|
||||||
|
|
||||||
|
# For the edge case of empty callbacks, any() returns False.
|
||||||
|
if any(callback.event & ~(
|
||||||
|
libvirt.VIR_EVENT_HANDLE_READABLE |
|
||||||
|
libvirt.VIR_EVENT_HANDLE_WRITABLE)
|
||||||
|
for callback in self.callbacks.values()):
|
||||||
|
warnings.warn(
|
||||||
|
'The only event supported are VIR_EVENT_HANDLE_READABLE '
|
||||||
|
'and VIR_EVENT_HANDLE_WRITABLE',
|
||||||
|
UserWarning)
|
||||||
|
|
||||||
|
if any(callback.event & libvirt.VIR_EVENT_HANDLE_READABLE
|
||||||
|
for callback in self.callbacks.values()):
|
||||||
|
self.impl.loop.add_reader(
|
||||||
|
self.fd, self._handle, libvirt.VIR_EVENT_HANDLE_READABLE)
|
||||||
|
else:
|
||||||
|
self.impl.loop.remove_reader(self.fd)
|
||||||
|
|
||||||
|
if any(callback.event & libvirt.VIR_EVENT_HANDLE_WRITABLE
|
||||||
|
for callback in self.callbacks.values()):
|
||||||
|
self.impl.loop.add_writer(
|
||||||
|
self.fd, self._handle, libvirt.VIR_EVENT_HANDLE_WRITABLE)
|
||||||
|
else:
|
||||||
|
self.impl.loop.remove_writer(self.fd)
|
||||||
|
|
||||||
|
def add_handle(self, callback):
|
||||||
|
'''Add a callback to the descriptor
|
||||||
|
|
||||||
|
:param FDCallback callback: the callback to add
|
||||||
|
:rtype: None
|
||||||
|
|
||||||
|
After adding the callback, it is immediately watched.
|
||||||
|
'''
|
||||||
|
self.callbacks[callback.iden] = callback
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
def remove_handle(self, iden):
|
||||||
|
'''Remove a callback from the descriptor
|
||||||
|
|
||||||
|
:param int iden: the identifier of the callback
|
||||||
|
:returns: the callback
|
||||||
|
:rtype: FDCallback
|
||||||
|
|
||||||
|
After removing the callback, the descriptor may be unwatched, if there
|
||||||
|
are no more handles for it.
|
||||||
|
'''
|
||||||
|
callback = self.callbacks.pop(iden)
|
||||||
|
self.update()
|
||||||
|
return callback
|
||||||
|
|
||||||
|
def close(self):
|
||||||
|
''''''
|
||||||
|
self.callbacks.clear()
|
||||||
|
self.update()
|
||||||
|
|
||||||
|
class DescriptorDict(dict):
|
||||||
|
'''Descriptors collection
|
||||||
|
|
||||||
|
This is used internally by LibvirtAsyncIOEventImpl to hold descriptors.
|
||||||
|
'''
|
||||||
|
def __init__(self, impl):
|
||||||
|
super().__init__()
|
||||||
|
self.impl = impl
|
||||||
|
|
||||||
|
def __missing__(self, fd):
|
||||||
|
descriptor = Descriptor(self.impl, fd)
|
||||||
|
self[fd] = descriptor
|
||||||
|
return descriptor
|
||||||
|
|
||||||
class FDCallback(Callback):
|
class FDCallback(Callback):
|
||||||
|
'''Callback for file descriptor (watcher)
|
||||||
|
|
||||||
|
:param Descriptor descriptor: the descriptor manager
|
||||||
|
:param int event: bitset of events on which to fire the callback
|
||||||
|
'''
|
||||||
# pylint: disable=too-few-public-methods
|
# pylint: disable=too-few-public-methods
|
||||||
|
|
||||||
def __init__(self, *args, descriptor, event, **kwargs):
|
def __init__(self, *args, descriptor, event, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.descriptor = descriptor
|
self.descriptor = descriptor
|
||||||
self.event = event
|
self.event = event
|
||||||
|
|
||||||
self.descriptor.callbacks[self.iden] = self
|
def __repr__(self):
|
||||||
|
return '<{} iden={} fd={} event={}>'.format(
|
||||||
|
self.__class__.__name__, self.iden, self.descriptor.fd, self.event)
|
||||||
|
|
||||||
def close(self):
|
def update(self, *, event):
|
||||||
del self.descriptor.callbacks[self.iden]
|
'''Update the callback and fix descriptor's watchers'''
|
||||||
super().close()
|
self.event = event
|
||||||
|
self.descriptor.update()
|
||||||
|
|
||||||
|
#
|
||||||
|
# timeouts
|
||||||
|
#
|
||||||
|
|
||||||
class TimeoutCallback(Callback):
|
class TimeoutCallback(Callback):
|
||||||
def __init__(self, *args, timeout=None, **kwargs):
|
'''Callback for timer'''
|
||||||
|
def __init__(self, *args, timeout, **kwargs):
|
||||||
super().__init__(*args, **kwargs)
|
super().__init__(*args, **kwargs)
|
||||||
self.timeout = timeout
|
self.timeout = timeout
|
||||||
self.task = None
|
self._task = None
|
||||||
|
|
||||||
|
def __repr__(self):
|
||||||
|
return '<{} iden={} timeout={}>'.format(
|
||||||
|
self.__class__.__name__, self.iden, self.timeout)
|
||||||
|
|
||||||
@asyncio.coroutine
|
@asyncio.coroutine
|
||||||
def timer(self):
|
def _timer(self):
|
||||||
|
'''An actual timer running on the event loop.
|
||||||
|
|
||||||
|
This is a coroutine.
|
||||||
|
'''
|
||||||
while True:
|
while True:
|
||||||
|
assert self.timeout >= 0, \
|
||||||
|
'invalid timeout {} for running timer'.format(self.timeout)
|
||||||
|
|
||||||
try:
|
try:
|
||||||
|
if self.timeout > 0:
|
||||||
timeout = self.timeout * 1e-3
|
timeout = self.timeout * 1e-3
|
||||||
self.impl.log.debug('sleeping %r', timeout)
|
self.impl.log.debug('sleeping %r', timeout)
|
||||||
yield from asyncio.sleep(timeout)
|
yield from asyncio.sleep(timeout)
|
||||||
|
else:
|
||||||
|
# scheduling timeout for next loop iteration
|
||||||
|
yield
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
self.impl.log.debug('timer %d cancelled', self.iden)
|
self.impl.log.debug('timer %d cancelled', self.iden)
|
||||||
break
|
break
|
||||||
|
|
||||||
self.cb(self.iden, self.opaque)
|
self.cb(self.iden, self.opaque)
|
||||||
self.impl.log.debug('timer %r callback ended', self.iden)
|
self.impl.log.debug('timer %r callback ended', self.iden)
|
||||||
|
|
||||||
def start(self):
|
def update(self, *, timeout=None):
|
||||||
self.impl.log.debug('timer %r start', self.iden)
|
'''Start or the timer, possibly updating timeout'''
|
||||||
if self.task is not None:
|
if timeout is not None:
|
||||||
return
|
self.timeout = timeout
|
||||||
self.task = asyncio.ensure_future(self.timer())
|
|
||||||
|
|
||||||
def stop(self):
|
if self.timeout >= 0 and self._task is None:
|
||||||
|
self.impl.log.debug('timer %r start', self.iden)
|
||||||
|
self._task = asyncio.ensure_future(self._timer(),
|
||||||
|
loop=self.impl.loop)
|
||||||
|
|
||||||
|
elif self.timeout < 0 and self._task is not None:
|
||||||
self.impl.log.debug('timer %r stop', self.iden)
|
self.impl.log.debug('timer %r stop', self.iden)
|
||||||
if self.task is None:
|
self._task.cancel() # pylint: disable=no-member
|
||||||
return
|
self._task = None
|
||||||
self.task.cancel() # pylint: disable=no-member
|
|
||||||
self.task = None
|
|
||||||
|
|
||||||
def close(self):
|
def close(self):
|
||||||
self.stop()
|
'''Stop the timer and call ff callback'''
|
||||||
|
self.timeout = -1
|
||||||
|
self.update()
|
||||||
super().close()
|
super().close()
|
||||||
|
|
||||||
|
#
|
||||||
|
# main implementation
|
||||||
|
#
|
||||||
|
|
||||||
class DescriptorDict(dict):
|
class LibvirtAsyncIOEventImpl(object):
|
||||||
class Descriptor(object):
|
'''Libvirt event adapter to asyncio.
|
||||||
def __init__(self, loop, fd):
|
|
||||||
self.loop = loop
|
:param loop: asyncio's event loop
|
||||||
self.fd = fd
|
|
||||||
|
If *loop* is not specified, the current (or default) event loop is used.
|
||||||
|
'''
|
||||||
|
|
||||||
|
def __init__(self, *, loop=None):
|
||||||
|
self.loop = loop or asyncio.get_event_loop()
|
||||||
self.callbacks = {}
|
self.callbacks = {}
|
||||||
|
self.descriptors = DescriptorDict(self)
|
||||||
self.loop.add_reader(
|
|
||||||
self.fd, self.handle, libvirt.VIR_EVENT_HANDLE_READABLE)
|
|
||||||
self.loop.add_writer(
|
|
||||||
self.fd, self.handle, libvirt.VIR_EVENT_HANDLE_WRITABLE)
|
|
||||||
|
|
||||||
def close(self):
|
|
||||||
self.loop.remove_reader(self.fd)
|
|
||||||
self.loop.remove_writer(self.fd)
|
|
||||||
|
|
||||||
def handle(self, event):
|
|
||||||
for callback in self.callbacks.values():
|
|
||||||
if callback.event is not None and callback.event & event:
|
|
||||||
callback.cb(
|
|
||||||
callback.iden, self.fd, event, callback.opaque)
|
|
||||||
|
|
||||||
def __init__(self, loop):
|
|
||||||
super().__init__()
|
|
||||||
self.loop = loop
|
|
||||||
def __missing__(self, fd):
|
|
||||||
descriptor = self.Descriptor(self.loop, fd)
|
|
||||||
self[fd] = descriptor
|
|
||||||
return descriptor
|
|
||||||
|
|
||||||
def __init__(self, loop):
|
|
||||||
self.loop = loop
|
|
||||||
self.callbacks = {}
|
|
||||||
self.descriptors = self.DescriptorDict(self.loop)
|
|
||||||
self.log = logging.getLogger(self.__class__.__name__)
|
self.log = logging.getLogger(self.__class__.__name__)
|
||||||
|
|
||||||
def register(self):
|
def register(self):
|
||||||
|
'''Register this instance as event loop implementation'''
|
||||||
# pylint: disable=bad-whitespace
|
# pylint: disable=bad-whitespace
|
||||||
|
self.log.debug('register()')
|
||||||
libvirt.virEventRegisterImpl(
|
libvirt.virEventRegisterImpl(
|
||||||
self.add_handle, self.update_handle, self.remove_handle,
|
self.add_handle, self.update_handle, self.remove_handle,
|
||||||
self.add_timeout, self.update_timeout, self.remove_timeout)
|
self.add_timeout, self.update_timeout, self.remove_timeout)
|
||||||
|
|
||||||
|
def schedule_ff_callback(self, opaque):
|
||||||
|
'''Schedule a ff callback from one of the handles or timers'''
|
||||||
|
self.loop.call_soon(execute_ff_callback, opaque)
|
||||||
|
|
||||||
|
def is_idle(self):
|
||||||
|
'''Returns False if there are leftovers from a connection
|
||||||
|
|
||||||
|
Those may happen if there are sematical problems while closing
|
||||||
|
a connection. For example, not deregistered events before .close().
|
||||||
|
'''
|
||||||
|
return not self.callbacks
|
||||||
|
|
||||||
def add_handle(self, fd, event, cb, opaque):
|
def add_handle(self, fd, event, cb, opaque):
|
||||||
|
'''Register a callback for monitoring file handle events
|
||||||
|
|
||||||
|
:param int fd: file descriptor to listen on
|
||||||
|
:param int event: bitset of events on which to fire the callback
|
||||||
|
:param cb: the callback to be called when an event occurrs
|
||||||
|
:param opaque: user data to pass to the callback
|
||||||
|
:rtype: int
|
||||||
|
:returns: handle watch number to be used for updating and \
|
||||||
|
unregistering for events
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddHandleFuncFunc
|
||||||
|
'''
|
||||||
self.log.debug('add_handle(fd=%d, event=%d, cb=%r, opaque=%r)',
|
self.log.debug('add_handle(fd=%d, event=%d, cb=%r, opaque=%r)',
|
||||||
fd, event, cb, opaque)
|
fd, event, cb, opaque)
|
||||||
callback = self.FDCallback(self, cb, opaque,
|
callback = FDCallback(self, cb, opaque,
|
||||||
descriptor=self.descriptors[fd], event=event)
|
descriptor=self.descriptors[fd], event=event)
|
||||||
|
self.callbacks[callback.iden] = callback
|
||||||
|
self.descriptors[fd].add_handle(callback)
|
||||||
return callback.iden
|
return callback.iden
|
||||||
|
|
||||||
def update_handle(self, watch, event):
|
def update_handle(self, watch, event):
|
||||||
|
'''Change event set for a monitored file handle
|
||||||
|
|
||||||
|
:param int watch: file descriptor watch to modify
|
||||||
|
:param int event: new events to listen on
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventUpdateHandleFunc
|
||||||
|
'''
|
||||||
self.log.debug('update_handle(watch=%d, event=%d)', watch, event)
|
self.log.debug('update_handle(watch=%d, event=%d)', watch, event)
|
||||||
self.callbacks[watch].event = event
|
return self.callbacks[watch].update(event=event)
|
||||||
|
|
||||||
def remove_handle(self, watch):
|
def remove_handle(self, watch):
|
||||||
|
'''Unregister a callback from a file handle.
|
||||||
|
|
||||||
|
:param int watch: file descriptor watch to stop listening on
|
||||||
|
:returns: None (see source for explanation)
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventRemoveHandleFunc
|
||||||
|
'''
|
||||||
self.log.debug('remove_handle(watch=%d)', watch)
|
self.log.debug('remove_handle(watch=%d)', watch)
|
||||||
callback = self.callbacks.pop(watch)
|
callback = self.callbacks.pop(watch)
|
||||||
|
assert callback is self.descriptors.remove_handle(watch)
|
||||||
callback.close()
|
callback.close()
|
||||||
|
|
||||||
# libvirt-python.git/libvirt-override.c suggests that the opaque value
|
# libvirt-python.git/libvirt-override.c suggests that the opaque value
|
||||||
@ -164,28 +401,45 @@ class LibvirtAsyncIOEventImpl(object):
|
|||||||
return None
|
return None
|
||||||
|
|
||||||
def add_timeout(self, timeout, cb, opaque):
|
def add_timeout(self, timeout, cb, opaque):
|
||||||
|
'''Register a callback for a timer event
|
||||||
|
|
||||||
|
:param int timeout: the timeout to monitor
|
||||||
|
:param cb: the callback to call when timeout has expired
|
||||||
|
:param opaque: user data to pass to the callback
|
||||||
|
:rtype: int
|
||||||
|
:returns: a timer value
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventAddTimeoutFunc
|
||||||
|
'''
|
||||||
self.log.debug('add_timeout(timeout=%d, cb=%r, opaque=%r)',
|
self.log.debug('add_timeout(timeout=%d, cb=%r, opaque=%r)',
|
||||||
timeout, cb, opaque)
|
timeout, cb, opaque)
|
||||||
if timeout <= 0:
|
callback = TimeoutCallback(self, cb, opaque, timeout=timeout)
|
||||||
# TODO we could think about registering timeouts of -1 as a special
|
self.callbacks[callback.iden] = callback
|
||||||
# case and emulate 0 somehow (60 Hz?)
|
callback.update()
|
||||||
self.log.warning('will not add timer with timeout %r', timeout)
|
|
||||||
return -1
|
|
||||||
|
|
||||||
callback = self.TimeoutCallback(self, cb, opaque, timeout=timeout)
|
|
||||||
callback.start()
|
|
||||||
return callback.iden
|
return callback.iden
|
||||||
|
|
||||||
def update_timeout(self, timer, timeout):
|
def update_timeout(self, timer, timeout):
|
||||||
|
'''Change frequency for a timer
|
||||||
|
|
||||||
|
:param int timer: the timer to modify
|
||||||
|
:param int timeout: the new timeout value in ms
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventUpdateTimeoutFunc
|
||||||
|
'''
|
||||||
self.log.debug('update_timeout(timer=%d, timeout=%d)', timer, timeout)
|
self.log.debug('update_timeout(timer=%d, timeout=%d)', timer, timeout)
|
||||||
callback = self.callbacks[timer]
|
return self.callbacks[timer].update(timeout=timeout)
|
||||||
callback.timeout = timeout
|
|
||||||
if timeout > 0:
|
|
||||||
callback.start()
|
|
||||||
else:
|
|
||||||
callback.stop()
|
|
||||||
|
|
||||||
def remove_timeout(self, timer):
|
def remove_timeout(self, timer):
|
||||||
|
'''Unregister a callback for a timer
|
||||||
|
|
||||||
|
:param int timer: the timer to remove
|
||||||
|
:returns: None (see source for explanation)
|
||||||
|
|
||||||
|
.. seealso::
|
||||||
|
https://libvirt.org/html/libvirt-libvirt-event.html#virEventRemoveTimeoutFunc
|
||||||
|
'''
|
||||||
self.log.debug('remove_timeout(timer=%d)', timer)
|
self.log.debug('remove_timeout(timer=%d)', timer)
|
||||||
callback = self.callbacks.pop(timer)
|
callback = self.callbacks.pop(timer)
|
||||||
callback.close()
|
callback.close()
|
||||||
|
Loading…
Reference in New Issue
Block a user