Run update-copyright.py
[rss2email.git] / rss2email / util.py
1 # Copyright (C) 2012 W. Trevor King <wking@tremily.us>
2 #
3 # This file is part of rss2email.
4 #
5 # rss2email is free software: you can redistribute it and/or modify it under
6 # the terms of the GNU General Public License as published by the Free Software
7 # Foundation, either version 2 of the License, or (at your option) version 3 of
8 # the License.
9 #
10 # rss2email is distributed in the hope that it will be useful, but WITHOUT ANY
11 # WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
12 # A PARTICULAR PURPOSE.  See the GNU General Public License for more details.
13 #
14 # You should have received a copy of the GNU General Public License along with
15 # rss2email.  If not, see <http://www.gnu.org/licenses/>.
16
17 """Odds and ends
18 """
19
20 import sys as _sys
21 import threading as _threading
22
23 from . import error as _error
24
25
26 class TimeLimitedFunction (_threading.Thread):
27     """Run `function` with a time limit of `timeout` seconds.
28
29     >>> import time
30     >>> def sleeping_return(sleep, x):
31     ...     time.sleep(sleep)
32     ...     return x
33     >>> TimeLimitedFunction(0.5, sleeping_return)(0.1, 'x')
34     'x'
35     >>> TimeLimitedFunction(0.5, sleeping_return)(10, 'y')
36     Traceback (most recent call last):
37       ...
38     rss2email.error.TimeoutError: 0.5 second timeout exceeded
39     >>> TimeLimitedFunction(0.5, time.sleep)('x')
40     Traceback (most recent call last):
41       ...
42     rss2email.error.TimeoutError: error while running time limited function: a float is required
43     """
44     def __init__(self, timeout, target, **kwargs):
45         super(TimeLimitedFunction, self).__init__(target=target, **kwargs)
46         self.setDaemon(True)  # daemon kwarg only added in Python 3.3.
47         self.timeout = timeout
48         self.result = None
49         self.error = None
50
51     def run(self):
52         """Based on Thread.run().
53
54         We add handling for self.result and self.error.
55         """
56         try:
57             if self._target:
58                 self.result = self._target(*self._args, **self._kwargs)
59         except:
60             self.error = _sys.exc_info()
61         finally:
62             # Avoid a refcycle if the thread is running a function with
63             # an argument that has a member that points to the thread.
64             del self._target, self._args, self._kwargs
65
66     def __call__(self, *args, **kwargs):
67         self._args = args
68         self._kwargs = kwargs
69         self.start()
70         self.join(self.timeout)
71         if self.error:
72             raise _error.TimeoutError(
73                 time_limited_function=self) from self.error[1]
74         elif self.isAlive():
75             raise _error.TimeoutError(time_limited_function=self)
76         return self.result