summaryrefslogtreecommitdiffstats
path: root/src/python/openlmi/common/TimerManager.py
diff options
context:
space:
mode:
authorMichal Minar <miminar@redhat.com>2013-07-03 18:15:23 +0200
committerMichal Minar <miminar@redhat.com>2013-07-04 10:33:48 +0200
commita4e6cc3c9f273cbbf911340edd02fa17a8379917 (patch)
tree11e5616c41385a9a9a455f4874e12cf5bbd0c376 /src/python/openlmi/common/TimerManager.py
parent4c0ec0ef72a6aef0413b9e8eca9380bd31bf4444 (diff)
downloadopenlmi-providers-a4e6cc3c9f273cbbf911340edd02fa17a8379917.tar.gz
openlmi-providers-a4e6cc3c9f273cbbf911340edd02fa17a8379917.tar.xz
openlmi-providers-a4e6cc3c9f273cbbf911340edd02fa17a8379917.zip
renamed openlmi namespace to lmi
To comply with lmi shell, which is placed in *lmi* package, and to make our imports shorter, we are renaming *openlmi* namespace to *lmi*.
Diffstat (limited to 'src/python/openlmi/common/TimerManager.py')
-rw-r--r--src/python/openlmi/common/TimerManager.py421
1 files changed, 0 insertions, 421 deletions
diff --git a/src/python/openlmi/common/TimerManager.py b/src/python/openlmi/common/TimerManager.py
deleted file mode 100644
index 42d48cd..0000000
--- a/src/python/openlmi/common/TimerManager.py
+++ /dev/null
@@ -1,421 +0,0 @@
-# Copyright (C) 2013 Red Hat, Inc. All rights reserved.
-#
-# This library is free software; you can redistribute it and/or
-# modify it under the terms of the GNU Lesser General Public
-# License as published by the Free Software Foundation; either
-# version 2.1 of the License, or (at your option) any later version.
-#
-# This library is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
-# Lesser General Public License for more details.
-#
-# You should have received a copy of the GNU Lesser General Public
-# License along with this library; if not, write to the Free Software
-# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
-#
-# Authors: Jan Safranek <jsafrane@redhat.com>
-# -*- coding: utf-8 -*-
-"""
-Module with functionality to create timers, which can be used in CMPI providers.
-
-Default python threading.Timer is not suitable, because it creates thread
-for each timer, which is inefficient. In addition, each such thread would need
-to be registered at CIMOM to enable logging in timer callbacks.
-
-Usage:
-
-1. Initialize the TimerManager when your provider initializes!
-Otherwise you may encounter weird exceptions.
-
-2. When any provider needs timer, create it using Time.create_timer() to create
-Timer instance.
-
-3. Call Timer.start() to start the timer. It will call registered callback
-when the timer expires. The callback is called in context of TimerManager
-thread, which has enabled logging to CIMOM, i.e. the callback can log as usual.
-
-4. (optionally) cancel the timer before expiration using Timer.cancel().
-However, this does not guarantee that the timer callback won't be called -
-it may be already being scheduled / called.
-
-.. autoclass:: TimerManager
- :members:
-
-.. autoclass:: Timer
- :members:
-
-.. autoclass:: MonotonicClock
- :members:
-"""
-
-import ctypes
-import openlmi.common.singletonmixin as singletonmixin
-import threading
-import Queue
-import openlmi.common.cmpi_logging as cmpi_logging
-
-class TimerException(Exception):
- pass
-
-class MonotonicClock(object):
- """
- Monotonic clock, represented by clock_gettime() and CLOCK_MONOTONIC.
- This clock is not influenced by NTP or administrator setting time or date.
- """
- CLOCK_MONOTONIC = ctypes.c_int(1)
-
- class timespec(ctypes.Structure):
- _fields_ = [
- ("tv_sec", ctypes.c_long),
- ("tv_nsec", ctypes.c_long)]
-
- def __init__(self):
- libc = ctypes.CDLL("librt.so.1")
- self._clock_gettime = libc.clock_gettime
-
- def now(self):
- """
- Return current time, i.e. float representing seconds with precision up
- to nanoseconds (depends on glibc). The actual value of current time is
- meaningless, it can be used only to measure time differences.
-
- :returns: ``float`` with current time in seconds.
- """
- t = MonotonicClock.timespec(0, 0)
- ret = self._clock_gettime(self.CLOCK_MONOTONIC, ctypes.pointer(t))
-
- if ret < 0:
- raise TimerException("Cannot get clock time, clock_gettime() failed.")
- return t.tv_sec + t.tv_nsec * 10 ** (-9)
-
-class Timer(object):
- """
- A class representing a timer. A timer has a timeout and after the timeout,
- given callback is called and the timer is deleted.
- """
-
- @cmpi_logging.trace_method
- def __init__(self, timer_manager, name, callback=None, *args, **kwargs):
- """
- Create a timer. If specified, given callback is registered.
- The callback is called with *args and **kwargs.
-
- :param timer_manager: (``TimerManager)`` Instance of the timer manager
- which will manage the timer.
- :param name: (``string``) Name of the timer, used for logging.
- :param callback: (``function``) Callback to call when the timer expires.
- :param *args, **kwargs: Parameters of the callback.
- """
- self._mgr = timer_manager
- self._name = name
- self._callback = callback
- self._args = args
- self._kwargs = kwargs
-
- cmpi_logging.logger.trace_info("Timer: Timer %s created" % name)
-
- @cmpi_logging.trace_method
- def set_callback(self, callback, *args, **kwargs):
- """
- Set callback to call when the timer expires.
-
- :param callback: (``function``) Callback to call when the timer expires.
- :param *args, **kwargs: Parameters of the callback.
- """
- self._callback = callback
- self._args = args
- self._kwargs = kwargs
-
- @cmpi_logging.trace_method
- def start(self, timeout):
- """
- Start the timer with given timeout. After the timeout, the registered
- callback will be called.
-
- :param timeout: (``float``) Timeout in seconds.
- """
-
- self._timeout = timeout
- now = self._mgr.now()
- self._end_time = now + timeout
- cmpi_logging.logger.trace_info(
- "Timer: Timer %s started at %f for %f seconds"
- % (self._name, now, self._timeout))
- self._mgr._add_timer(self)
-
- @cmpi_logging.trace_method
- def cancel(self):
- """
- Cancel the timer. This method does not guarantee that the callback won't
- be called, the timer might be calling the callback right now,
- """
- cmpi_logging.logger.trace_info("Timer: Timer %s cancelled"
- % (self._name))
- self._mgr._remove_timer(self)
-
- @cmpi_logging.trace_method
- def _expired(self, now):
- """
- Returns True, if the timer is expired.
-
- :param now: (``float``) Current time, as returned by MonotonicClock.now().
- :returns: (``boolean``) ``True``, if the timer is expired.
- """
- if self._end_time <= now:
- cmpi_logging.logger.trace_info("Timer: Timer %s has expired"
- % (self._name))
- return True
- return False
-
- @cmpi_logging.trace_method
- def _expire(self):
- """
- Called when the timer expired. It calls the callback.
- """
- cmpi_logging.logger.trace_info("Timer: Calling callback for timer %s"
- % (self._name))
- self._callback(*self._args, **self._kwargs)
-
-class TimerManager(singletonmixin.Singleton):
- """
- Manages set of timers.
-
- Python standard Timer class creates a thread for
-
- each timer, which is inefficient. This class uses only one thread, which
- is registered at CIMOM, i.e. it can log as usual.
-
- This class is singleton, use TimerManager.get_instance() to get the
- instance.
-
- Still, the singleton needs to be initialized with ProviderEnvironment to
- enable logging in the timer thread. Use TimerManager.get_instance(env) in
- you provider initialization.
- """
-
- # Commands to the timer thread
- COMMAND_STOP = 1
- COMMAND_RESCHEDULE = 2
-
- @cmpi_logging.trace_method
- def __init__(self, env=None):
- """
- Initialize new thread manager.
-
- :param env: (``ProviderEnvironment``) Environment to use for logging.
- """
- self._clock = MonotonicClock()
- self._lock = threading.RLock()
- self._queue = Queue.Queue()
-
- # Array of timers. Assumption: nr. of timers is relatively small,
- # i.e. hundreds at the worst.
- self._timers = []
-
- new_broker = None
- if env:
- broker = env.get_cimom_handle()
- new_broker = broker.PrepareAttachThread()
-
- self._timer_thread = threading.Thread(
- target=self._timer_loop, args=(new_broker,))
- self._timer_thread.daemon = False
- self._timer_thread.start()
-
- def create_timer(self, name, callback=None, *args, **kwargs):
- """
- Create new timer. If specified, given callback is registered.
- The callback is called with *args and **kwargs.
-
- :param name: (``string``) Name of the timer, used for logging.
- :param callback: (``function``) Callback to call when the timer expires.
- :param *args, **kwargs: Parameters of the callback.
- """
- return Timer(self, name, callback, *args, **kwargs)
-
- def _timer_loop(self, broker):
- """
- TimerManager thread main loop. It waits for timeout of all timers
- and calls their callbacks.
-
- :param broker: (``BrokerCIMOMHandle``) CIM broker handle, used for
- logging.
- """
- if broker:
- broker.AttachThread()
- cmpi_logging.logger.info("Started Timer thread.")
- while True:
- self._handle_expired()
- timeout = self._find_timeout()
- if timeout != 0:
- # Wait for the timeout or any change in timers.
- try:
- command = self._queue.get(timeout=timeout)
- self._queue.task_done()
- if command == self.COMMAND_STOP:
- break # stop the thread
- # process COMMAND_RESCHEDULE in next loop
- except Queue.Empty:
- # Timeout has happened, ignore the exception.
- pass
- cmpi_logging.logger.info("Stopped Timer thread.")
-
- @cmpi_logging.trace_method
- def _handle_expired(self):
- """
- Finds all expired timers, calls their callback and removes them from
- list of timers.
- """
-
- # Get list of expired timers.
- with self._lock:
- now = self.now()
- cmpi_logging.logger.trace_info(
- "Timer: Checking for expired, now=%f." % (now))
- expired = [t for t in self._timers if t._expired(now)]
-
- # Call the callbacks (unlocked!).
- for t in expired:
- t._expire()
-
- # Remove the timers (locked).
- with self._lock:
- for t in expired:
- try:
- cmpi_logging.logger.trace_info(
- "Timer: Removing %s" % (t._name))
- self._timers.remove(t)
- except ValueError:
- # The timer has already been removed.
- pass
-
- @cmpi_logging.trace_method
- def _find_timeout(self):
- """
- Return nearest timeout, in seconds (as float, i.e. subsecond timeout
- is possible). If no timer is scheduled, None is returned.
- If there are expired timers, 0 is returned.
-
- :returns: Positive ``float``: Nearest timeout.
- :returns: ``0``: Some timer has expired.
- :returns: ``None``: No timer is scheduled.
- """
- with self._lock:
- if not self._timers:
- cmpi_logging.logger.trace_info(
- "Timer: No timers scheduled, waiting forever.")
- return None
- closest = min(self._timers, key=lambda timer: timer._end_time)
- now = self.now()
- timeout = closest._end_time - now
- if timeout > 0:
- cmpi_logging.logger.trace_info(
- "Timer: Waiting for %f seconds, now=%f."
- % (timeout, now))
- return timeout
- cmpi_logging.logger.trace_info(
- "Timer: Some timer has already expired, no waiting.")
- return 0
-
- @cmpi_logging.trace_method
- def _add_timer(self, timer):
- """
- Adds timer to list of timers. The timer must be started, i.e. its
- timeout must be nozero!
- This is internal method called by Timer.start().
-
- :param timer: (``Timer``) Timer to add.
- """
- with self._lock:
- self._timers.append(timer)
- # Wake up the timer manager thread.
- self._queue.put(self.COMMAND_RESCHEDULE)
- cmpi_logging.logger.trace_info("Timer: Timer %s added" % (timer._name))
-
- @cmpi_logging.trace_method
- def _remove_timer(self, timer):
- """
- Remove timer from list of timers.
- This is internal method called by Timer.cancel().
- :param timer: (``Timer``) Timer to remove.
- """
- with self._lock:
- try:
- self._timers.remove(timer)
- except ValueError:
- pass
- # Wake up the timer manager thread.
- self._queue.put(self.COMMAND_RESCHEDULE)
- cmpi_logging.logger.trace_info("Timer: Timer %s removed"
- % (timer._name))
-
- def now(self):
- """
- Return current time, not influenced by NTP or admin setting date or
- time. The actual value of current time is meaningless, it can be used
- only to measure time differences.
-
- :returns: ``float`` Current time, in seconds.
- """
- return self._clock.now()
-
- @cmpi_logging.trace_method
- def shutdown(self):
- """
- Stop the thread. This method blocks until the thread is safely
- destroyed.
- """
- self._queue.put(self.COMMAND_STOP)
- self._timer_thread.join()
-
-if __name__ == "__main__":
- cmpi_logging.logger = cmpi_logging.CMPILogger("")
- import time
-
- class Env(object):
- def AttachThread(self):
- pass
- def PrepareAttachThread(self):
- return self
- def get_cimom_handle(self):
- return self
-
- clock = MonotonicClock()
-
- start = clock.now()
- time.sleep(0.5)
- print "Clock 0.5:", clock.now() - start
-
- time.sleep(0.5)
- print "Clock 1:", clock.now() - start
-
- mgr = TimerManager.get_instance(Env())
-
- def callback(msg):
- if callback.first:
- t = mgr.create_timer("internal 0.5")
- t.set_callback(callback, "internal 0.5")
- t.start(0.5)
- callback.first = False
-
- print clock.now(), msg
-
- callback.first = True
-
- t1 = mgr.create_timer("one second")
- t1.set_callback(callback, "1")
- t1.start(1)
- t2 = mgr.create_timer("two seconds")
- t2.set_callback(callback, "2")
- t2.start(2)
- t22 = mgr.create_timer("two seconds 2")
- t22.set_callback(callback, "2 again")
- t22.start(2)
- t15 = mgr.create_timer("one+half seconds")
- t15.set_callback(callback, "1.5")
- t15.start(1.5)
-
- time.sleep(4)
-
- mgr.stop_thread()