Completed
Push — master ( d058c9...ca4b9c )
by Kenny
45s
created

plumd.SignalWaiter   A

Complexity

Total Complexity 6

Size/Duplication

Total Lines 51
Duplicated Lines 0 %
Metric Value
dl 0
loc 51
rs 10
wmc 6

2 Methods

Rating   Name   Duplication   Size   Complexity  
A wait() 0 9 2
B __init__() 0 22 4
1
# -*- coding: utf-8 -*-
2
"""Various helper classes.
3
4
.. moduleauthor:: Kenny Freeman <[email protected]>
5
6
"""
7
8
__author__ = 'Kenny Freeman'
9
__email__ = '[email protected]'
10
__license__     = "ISCL"
11
__docformat__   = 'reStructuredText'
12
13
import imp
14
import os
15
import os.path
16
import time
17
import fcntl
18
import socket
19
import signal
20
import inspect
21
22
import plumd
23
24
25
def get_hostname():
26
    """
27
    Returns the hostname of the system the code is running on. Eventually this
28
    may include more checks.
29
30
    :rtype: str
31
    """
32
    return socket.gethostname().split(".")[0]
33
34
35
def get_dict_keys(knames, kdict):
36
    """Returns a list of writers for a reader plugin.
37
38
    :param knames: a list of key values to get
39
    :type list: list
40
    :param kdict: a dictonary
41
    :rtype: list
42
    """
43
    vals = []
44
    if isinstance(knames, list) and len(knames) > 0:
45
        keys = kdict.keys()
46
        for kname in knames:
47
            if kname in keys:
48
                vals.append(kdict[kname])
49
    return vals
50
51
52
class Interval(object):
53
    """A simple helper class to ensure a loop occours every ltime seconds
54
    while optionally providing a means to interupt the loop with a
55
    :class:`threading.Event`.
56
57
58
    :param ltime: The target duration of a loop
59
    :type ltime: int or float
60
    :param evt: An event object that will cause the loop to complete early if
61
        the event is triggered while waiting.
62
    :type evt: threading.Event
63
    :raises: ValueError if the passed loop time is not an int or float or is < 0
64
65
66
    As an example, use it like this to loop on an interval:
67
68
    :Example:
69
70
        >>> import threading
71
        >>> evt = threading.Event()
72
        >>> while loop:
73
        >>>     with Interval(111.1, evt) as timer:
74
        >>>         do_things_here()
75
76
    .. note:: if the do_things_here() call returns before the loop timer
77
        completes the timer object will either evt.wait() if evt was defined or
78
        time.sleep() for the time remaining in the interval.
79
    """
80
81
    def __init__(self, ltime, evt=None):
82
        if ltime is None or ltime < 0:
83
            raise ValueError("an interval must be defined")
84
        self.ltime = ltime
85
        self.evt = evt
86
        self.lstart = None
87
88
89
    @property
90
    def remaining(self):
91
        """Return the remaining time in the loop.
92
93
        :rtype: float
94
        """
95
        if self.lstart is not None:
96
            return self.ltime - (time.time() - self.lstart)
97
        return 0.0
98
99
100
    @property
101
    def elapsed(self):
102
        """Return the elapsed time of the loop.
103
104
        :rtype: float
105
        """
106
        if self.lstart is not None:
107
            return time.time() - self.lstart
108
        return 0.0
109
110
111
    def __str__(self):
112
        """Return a nicely formatted string.
113
114
        :rtype: str
115
        """
116
        elapsed = None
117
        if self.lstart is not None:
118
            elapsed = time.time() - self.lstart
119
        s = "Interval {0} seconds, {1} elapsed, {2} started"
120
        return s.format(self.ltime, elapsed, self.lstart)
121
122
123
    def __repr__(self):
124
        """Return a nicely formatted string.
125
126
        :rtype: str
127
        """
128
        elapsed = None
129
        if self.lstart is not None:
130
            elapsed = time.time() - self.lstart
131
        s = "Interval {0} seconds, {1} elapsed, {2} started"
132
        return s.format(self.ltime, elapsed, self.lstart)
133
134
135
    def __enter__(self):
136
        """Called on entrance to with() statement - simply records loop start
137
        time.
138
139
        :rtype: Interval"""
140
        self.lstart = time.time()
141
        return self
142
143
144
    def __exit__(self, type, value, traceback):
145
        """Called on exit from a with() statement and either waits on the
146
        self.evt :class:`threading.Event` event if defined or time.sleep()'s
147
        the remaining interval time."""
148
        if self.lstart is None:
149
            self.ltstart = time.time()
150
        # block on the event for the remaining time, if any
151
        wtime = self.ltime - (time.time() - self.lstart)
152
        if wtime > 0:
153
            if self.evt is not None:
154
                self.evt.wait(wtime)
155
            else:
156
                time.sleep(wtime)
157
158
159
class SignalWaiter(object):
160
    """A helper class that makes waiting on signals very easy. Define the
161
    list of signals to wait for and to ignore and then call wait(). Instead of
162
    using signal handler functions it uses the signal.set_wakeup_fd() function
163
    and blocks on read().
164
165
    See http://www.pocketnix.org/doc/Fighting_set__wakeup__fd/ and others for
166
    more details.
167
168
    :param sig_wait: :class:`list` signals to wait for
169
    :type sig_wait: list
170
    :param sig_ignore: :class:`list` signals to ignore
171
    :type sig_ignore: list
172
    """
173
174
    def __init__(self, sig_wait, sig_ignore):
175
        """Create a :class:`SignalWaiter`"""
176
        self.sig_wait = sig_wait
177
        self.sig_ignore = sig_ignore
178
179
        # use a pipe to listen for signals
180
        self.rpipe, self.wpipe = os.pipe()
181
        # set non blocking mode on the write end of the pipe
182
        flags = fcntl.fcntl(self.wpipe, fcntl.F_GETFL, 0)
183
        flags = flags | os.O_NONBLOCK
184
        flags = fcntl.fcntl(self.wpipe, fcntl.F_SETFL, flags)
185
186
        # set the write end as a signal wakeup fd - we read signals from it
187
        signal.set_wakeup_fd(self.wpipe)
188
189
        # install dummy signal handlers that do nothing
190
        for signum in self.sig_wait:
191
            signal.signal(signum, lambda x,y: None)
192
193
        # install the ignore signal handler for sig_ignore signals
194
        for signum in self.sig_ignore:
195
            signal.signal(signum, signal.SIG_IGN)
196
197
        # now a call to wait() blocks the caller until a signal is received
198
        # only the signals we've registered handlers for will trigger
199
200
201
    def wait(self):
202
        """Blocks the calling thread until signal(s) are received.
203
204
        :rtype: :class:`int` or :class:`Exception`
205
        """
206
        try:
207
            return os.read(self.rpipe, 1)
208
        except Exception as e:
209
            return e
210