1 |
commit: e9d1125f6730c85c4b384a580da55da68338acf1 |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Tue Feb 7 19:11:50 2012 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Tue Feb 7 19:11:50 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=e9d1125f |
7 |
|
8 |
PollScheduler: add timeout_add like glib's |
9 |
|
10 |
This will be useful as a substitute for recursion, in order to avoid |
11 |
hitting the recursion limit for bug #402335. |
12 |
|
13 |
--- |
14 |
pym/_emerge/PollScheduler.py | 89 ++++++++++++++++++++++++++++++++++++++++-- |
15 |
pym/_emerge/Scheduler.py | 8 ++-- |
16 |
2 files changed, 89 insertions(+), 8 deletions(-) |
17 |
|
18 |
diff --git a/pym/_emerge/PollScheduler.py b/pym/_emerge/PollScheduler.py |
19 |
index fd9dfc0..fd57359 100644 |
20 |
--- a/pym/_emerge/PollScheduler.py |
21 |
+++ b/pym/_emerge/PollScheduler.py |
22 |
@@ -24,7 +24,12 @@ from _emerge.PollSelectAdapter import PollSelectAdapter |
23 |
class PollScheduler(object): |
24 |
|
25 |
class _sched_iface_class(SlotObject): |
26 |
- __slots__ = ("output", "register", "schedule", "unregister") |
27 |
+ __slots__ = ("output", "register", "schedule", |
28 |
+ "source_remove", "timeout_add", "unregister") |
29 |
+ |
30 |
+ class _timeout_handler_class(SlotObject): |
31 |
+ __slots__ = ("args", "function", "interval", "source_id", |
32 |
+ "timestamp") |
33 |
|
34 |
def __init__(self): |
35 |
self._terminated = threading.Event() |
36 |
@@ -37,13 +42,17 @@ class PollScheduler(object): |
37 |
self._poll_event_handler_ids = {} |
38 |
# Increment id for each new handler. |
39 |
self._event_handler_id = 0 |
40 |
+ self._timeout_handlers = {} |
41 |
self._poll_obj = create_poll_instance() |
42 |
+ self._polling = False |
43 |
self._scheduling = False |
44 |
self._background = False |
45 |
self.sched_iface = self._sched_iface_class( |
46 |
output=self._task_output, |
47 |
register=self._register, |
48 |
schedule=self._schedule_wait, |
49 |
+ source_remove=self._unregister, |
50 |
+ timeout_add=self._timeout_add, |
51 |
unregister=self._unregister) |
52 |
|
53 |
def terminate(self): |
54 |
@@ -82,7 +91,7 @@ class PollScheduler(object): |
55 |
should return False immediately (since there's no need to |
56 |
schedule anything after _terminate_tasks() has been called). |
57 |
""" |
58 |
- raise NotImplementedError() |
59 |
+ pass |
60 |
|
61 |
def _schedule(self): |
62 |
""" |
63 |
@@ -140,6 +149,23 @@ class PollScheduler(object): |
64 |
StopIteration if timeout is None and there are |
65 |
no file descriptors to poll. |
66 |
""" |
67 |
+ if self._polling: |
68 |
+ return |
69 |
+ self._polling = True |
70 |
+ try: |
71 |
+ self._do_poll(timeout=timeout) |
72 |
+ finally: |
73 |
+ self._polling = False |
74 |
+ |
75 |
+ def _do_poll(self, timeout=None): |
76 |
+ """ |
77 |
+ All poll() calls pass through here. The poll events |
78 |
+ are added directly to self._poll_event_queue. |
79 |
+ In order to avoid endless blocking, this raises |
80 |
+ StopIteration if timeout is None and there are |
81 |
+ no file descriptors to poll. |
82 |
+ """ |
83 |
+ self._run_timeouts() |
84 |
if not self._poll_event_handlers: |
85 |
self._schedule() |
86 |
if timeout is None and \ |
87 |
@@ -226,6 +252,51 @@ class PollScheduler(object): |
88 |
|
89 |
return bool(events_handled) |
90 |
|
91 |
+ def _timeout_add(self, interval, function, *args): |
92 |
+ """ |
93 |
+ Like glib.timeout_add(), interval argument is the number of |
94 |
+ milliseconds between calls to your function, and your function |
95 |
+ should return False to stop being called, or True to continue |
96 |
+ being called. Any additional positional arguments given here |
97 |
+ are passed to your function when it's called. |
98 |
+ |
99 |
+ NOTE: Timeouts registered by this function currently do not |
100 |
+ keep the main loop running when there are no remaining callbacks |
101 |
+ registered for IO events. This is not an issue if the purpose of |
102 |
+ the timeout is to place an upper limit on the time allowed for |
103 |
+ a particular IO event to occur, since the handler associated with |
104 |
+ the IO event will serve to keep the main loop running. |
105 |
+ """ |
106 |
+ self._event_handler_id += 1 |
107 |
+ source_id = self._event_handler_id |
108 |
+ self._timeout_handlers[source_id] = \ |
109 |
+ self._timeout_handler_class( |
110 |
+ interval=interval, function=function, args=args, |
111 |
+ source_id=source_id, timestamp=time.time()) |
112 |
+ return source_id |
113 |
+ |
114 |
+ def _run_timeouts(self): |
115 |
+ ready_timeouts = [] |
116 |
+ current_time = time.time() |
117 |
+ for x in self._timeout_handlers.values(): |
118 |
+ elapsed_seconds = current_time - x.timestamp |
119 |
+ # elapsed_seconds < 0 means the system clock has been adjusted |
120 |
+ if elapsed_seconds < 0 or \ |
121 |
+ (x.interval - 1000 * elapsed_seconds) <= 0: |
122 |
+ ready_timeouts.append(x) |
123 |
+ |
124 |
+ # Iterate of our local list, since self._timeout_handlers can be |
125 |
+ # modified during the exection of these callbacks. |
126 |
+ for x in ready_timeouts: |
127 |
+ if x.source_id not in self._timeout_handlers: |
128 |
+ # it got cancelled while executing another timeout |
129 |
+ continue |
130 |
+ x.timestamp = time.time() |
131 |
+ if not x.function(*x.args): |
132 |
+ self._unregister(x.source_id) |
133 |
+ |
134 |
+ return bool(ready_timeouts) |
135 |
+ |
136 |
def _register(self, f, eventmask, handler): |
137 |
""" |
138 |
@rtype: Integer |
139 |
@@ -242,7 +313,17 @@ class PollScheduler(object): |
140 |
return reg_id |
141 |
|
142 |
def _unregister(self, reg_id): |
143 |
- f = self._poll_event_handler_ids[reg_id] |
144 |
+ """ |
145 |
+ Like glib.source_remove(), this returns True if the given reg_id |
146 |
+ is found and removed, and False if the reg_id is invalid or has |
147 |
+ already been removed. |
148 |
+ """ |
149 |
+ timeout_handler = self._timeout_handlers.pop(reg_id, None) |
150 |
+ if timeout_handler is not None: |
151 |
+ return True |
152 |
+ f = self._poll_event_handler_ids.pop(reg_id, None) |
153 |
+ if f is None: |
154 |
+ return False |
155 |
self._poll_obj.unregister(f) |
156 |
if self._poll_event_queue: |
157 |
# Discard any unhandled events that belong to this file, |
158 |
@@ -262,7 +343,7 @@ class PollScheduler(object): |
159 |
self._poll_event_queue[:] = remaining_events |
160 |
|
161 |
del self._poll_event_handlers[f] |
162 |
- del self._poll_event_handler_ids[reg_id] |
163 |
+ return True |
164 |
|
165 |
def _schedule_wait(self, wait_ids=None, timeout=None, condition=None): |
166 |
""" |
167 |
|
168 |
diff --git a/pym/_emerge/Scheduler.py b/pym/_emerge/Scheduler.py |
169 |
index d09b474..5b56650 100644 |
170 |
--- a/pym/_emerge/Scheduler.py |
171 |
+++ b/pym/_emerge/Scheduler.py |
172 |
@@ -79,11 +79,9 @@ class Scheduler(PollScheduler): |
173 |
_opts_no_self_update = frozenset(["--buildpkgonly", |
174 |
"--fetchonly", "--fetch-all-uri", "--pretend"]) |
175 |
|
176 |
- class _iface_class(SlotObject): |
177 |
+ class _iface_class(PollScheduler._sched_iface_class): |
178 |
__slots__ = ("fetch", |
179 |
- "output", "register", "schedule", |
180 |
- "scheduleSetup", "scheduleUnpack", "scheduleYield", |
181 |
- "unregister") |
182 |
+ "scheduleSetup", "scheduleUnpack", "scheduleYield") |
183 |
|
184 |
class _fetch_iface_class(SlotObject): |
185 |
__slots__ = ("log_file", "schedule") |
186 |
@@ -223,6 +221,8 @@ class Scheduler(PollScheduler): |
187 |
scheduleSetup=self._schedule_setup, |
188 |
scheduleUnpack=self._schedule_unpack, |
189 |
scheduleYield=self._schedule_yield, |
190 |
+ source_remove=self._unregister, |
191 |
+ timeout_add=self._timeout_add, |
192 |
unregister=self._unregister) |
193 |
|
194 |
self._prefetchers = weakref.WeakValueDictionary() |