AsynchronousLock: implement _poll() and test
[portage.git] / pym / _emerge / AsynchronousTask.py
1 # Copyright 1999-2011 Gentoo Foundation
2 # Distributed under the terms of the GNU General Public License v2
3
4 from portage import os
5 from _emerge.SlotObject import SlotObject
6 class AsynchronousTask(SlotObject):
7         """
8         Subclasses override _wait() and _poll() so that calls
9         to public methods can be wrapped for implementing
10         hooks such as exit listener notification.
11
12         Sublasses should call self.wait() to notify exit listeners after
13         the task is complete and self.returncode has been set.
14         """
15
16         __slots__ = ("background", "cancelled", "returncode") + \
17                 ("_exit_listeners", "_exit_listener_stack", "_start_listeners")
18
19         def start(self):
20                 """
21                 Start an asynchronous task and then return as soon as possible.
22                 """
23                 self._start_hook()
24                 self._start()
25
26         def _start(self):
27                 self.returncode = os.EX_OK
28                 self.wait()
29
30         def isAlive(self):
31                 return self.returncode is None
32
33         def poll(self):
34                 self._wait_hook()
35                 if self.returncode is not None:
36                         return self.returncode
37                 return self._poll()
38
39         def _poll(self):
40                 return self.returncode
41
42         def wait(self):
43                 if self.returncode is None:
44                         self._wait()
45                 self._wait_hook()
46                 return self.returncode
47
48         def _wait(self):
49                 return self.returncode
50
51         def cancel(self):
52                 if not self.cancelled:
53                         self.cancelled = True
54                         self._cancel()
55                         self.wait()
56
57         def _cancel(self):
58                 """
59                 Subclasses should implement this, as a template method
60                 to be called by AsynchronousTask.cancel().
61                 """
62                 pass
63
64         def addStartListener(self, f):
65                 """
66                 The function will be called with one argument, a reference to self.
67                 """
68                 if self._start_listeners is None:
69                         self._start_listeners = []
70                 self._start_listeners.append(f)
71
72         def removeStartListener(self, f):
73                 if self._start_listeners is None:
74                         return
75                 self._start_listeners.remove(f)
76
77         def _start_hook(self):
78                 if self._start_listeners is not None:
79                         start_listeners = self._start_listeners
80                         self._start_listeners = None
81
82                         for f in start_listeners:
83                                 f(self)
84
85         def addExitListener(self, f):
86                 """
87                 The function will be called with one argument, a reference to self.
88                 """
89                 if self._exit_listeners is None:
90                         self._exit_listeners = []
91                 self._exit_listeners.append(f)
92
93         def removeExitListener(self, f):
94                 if self._exit_listeners is None:
95                         if self._exit_listener_stack is not None:
96                                 self._exit_listener_stack.remove(f)
97                         return
98                 self._exit_listeners.remove(f)
99
100         def _wait_hook(self):
101                 """
102                 Call this method after the task completes, just before returning
103                 the returncode from wait() or poll(). This hook is
104                 used to trigger exit listeners when the returncode first
105                 becomes available.
106                 """
107                 if self.returncode is not None and \
108                         self._exit_listeners is not None:
109
110                         # This prevents recursion, in case one of the
111                         # exit handlers triggers this method again by
112                         # calling wait(). Use a stack that gives
113                         # removeExitListener() an opportunity to consume
114                         # listeners from the stack, before they can get
115                         # called below. This is necessary because a call
116                         # to one exit listener may result in a call to
117                         # removeExitListener() for another listener on
118                         # the stack. That listener needs to be removed
119                         # from the stack since it would be inconsistent
120                         # to call it after it has been been passed into
121                         # removeExitListener().
122                         self._exit_listener_stack = self._exit_listeners
123                         self._exit_listeners = None
124
125                         self._exit_listener_stack.reverse()
126                         while self._exit_listener_stack:
127                                 self._exit_listener_stack.pop()(self)
128