1 |
commit: 5228eb4ce60765cd421a5e363e51a71626739267 |
2 |
Author: Zac Medico <zmedico <AT> gentoo <DOT> org> |
3 |
AuthorDate: Fri May 11 06:32:26 2012 +0000 |
4 |
Commit: Zac Medico <zmedico <AT> gentoo <DOT> org> |
5 |
CommitDate: Fri May 11 06:54:29 2012 +0000 |
6 |
URL: http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=5228eb4c |
7 |
|
8 |
PollScheduler: use local EventLoop (thread safe) |
9 |
|
10 |
For API consumers, this makes the doebuild() function compatible with |
11 |
threads, avoiding a ValueError raised by the signal module, as reported |
12 |
at http://bugs.sabayon.org/show_bug.cgi?id=3305. Classes derived from |
13 |
PollScheduler still use the signal module when possible. |
14 |
|
15 |
--- |
16 |
pym/_emerge/MetadataRegen.py | 2 +- |
17 |
pym/_emerge/PollScheduler.py | 14 +++++++- |
18 |
pym/_emerge/QueueScheduler.py | 4 +- |
19 |
pym/_emerge/Scheduler.py | 2 +- |
20 |
pym/_emerge/TaskScheduler.py | 6 ++-- |
21 |
pym/portage/util/_eventloop/EventLoop.py | 50 +++++++++++++++++++----------- |
22 |
6 files changed, 51 insertions(+), 27 deletions(-) |
23 |
|
24 |
diff --git a/pym/_emerge/MetadataRegen.py b/pym/_emerge/MetadataRegen.py |
25 |
index 79446ee..e82015f 100644 |
26 |
--- a/pym/_emerge/MetadataRegen.py |
27 |
+++ b/pym/_emerge/MetadataRegen.py |
28 |
@@ -11,7 +11,7 @@ class MetadataRegen(PollScheduler): |
29 |
|
30 |
def __init__(self, portdb, cp_iter=None, consumer=None, |
31 |
max_jobs=None, max_load=None): |
32 |
- PollScheduler.__init__(self) |
33 |
+ PollScheduler.__init__(self, main=True) |
34 |
self._portdb = portdb |
35 |
self._global_cleanse = False |
36 |
if cp_iter is None: |
37 |
|
38 |
diff --git a/pym/_emerge/PollScheduler.py b/pym/_emerge/PollScheduler.py |
39 |
index 1c631c3..965dc20 100644 |
40 |
--- a/pym/_emerge/PollScheduler.py |
41 |
+++ b/pym/_emerge/PollScheduler.py |
42 |
@@ -13,6 +13,7 @@ from portage import _encodings |
43 |
from portage import _unicode_encode |
44 |
from portage.util import writemsg_level |
45 |
from portage.util.SlotObject import SlotObject |
46 |
+from portage.util._eventloop.EventLoop import EventLoop |
47 |
from portage.util._eventloop.global_event_loop import global_event_loop |
48 |
|
49 |
from _emerge.getloadavg import getloadavg |
50 |
@@ -26,7 +27,13 @@ class PollScheduler(object): |
51 |
"output", "register", "run", |
52 |
"source_remove", "timeout_add", "unregister") |
53 |
|
54 |
- def __init__(self): |
55 |
+ def __init__(self, main=False): |
56 |
+ """ |
57 |
+ @param main: If True then use global_event_loop(), otherwise use |
58 |
+ a local EventLoop instance (default is False, for safe use in |
59 |
+ a non-main thread) |
60 |
+ @type main: bool |
61 |
+ """ |
62 |
self._terminated = threading.Event() |
63 |
self._terminated_tasks = False |
64 |
self._max_jobs = 1 |
65 |
@@ -34,7 +41,10 @@ class PollScheduler(object): |
66 |
self._jobs = 0 |
67 |
self._scheduling = False |
68 |
self._background = False |
69 |
- self._event_loop = global_event_loop() |
70 |
+ if main: |
71 |
+ self._event_loop = global_event_loop() |
72 |
+ else: |
73 |
+ self._event_loop = EventLoop(main=False) |
74 |
self.sched_iface = self._sched_iface_class( |
75 |
IO_ERR=self._event_loop.IO_ERR, |
76 |
IO_HUP=self._event_loop.IO_HUP, |
77 |
|
78 |
diff --git a/pym/_emerge/QueueScheduler.py b/pym/_emerge/QueueScheduler.py |
79 |
index 9d73b78..206087c 100644 |
80 |
--- a/pym/_emerge/QueueScheduler.py |
81 |
+++ b/pym/_emerge/QueueScheduler.py |
82 |
@@ -10,8 +10,8 @@ class QueueScheduler(PollScheduler): |
83 |
run() method returns when no tasks remain. |
84 |
""" |
85 |
|
86 |
- def __init__(self, max_jobs=None, max_load=None): |
87 |
- PollScheduler.__init__(self) |
88 |
+ def __init__(self, main=True, max_jobs=None, max_load=None): |
89 |
+ PollScheduler.__init__(self, main=main) |
90 |
|
91 |
if max_jobs is None: |
92 |
max_jobs = 1 |
93 |
|
94 |
diff --git a/pym/_emerge/Scheduler.py b/pym/_emerge/Scheduler.py |
95 |
index 5500acf..30a7e10 100644 |
96 |
--- a/pym/_emerge/Scheduler.py |
97 |
+++ b/pym/_emerge/Scheduler.py |
98 |
@@ -137,7 +137,7 @@ class Scheduler(PollScheduler): |
99 |
def __init__(self, settings, trees, mtimedb, myopts, |
100 |
spinner, mergelist=None, favorites=None, graph_config=None, |
101 |
uninstall_only=False): |
102 |
- PollScheduler.__init__(self) |
103 |
+ PollScheduler.__init__(self, main=True) |
104 |
|
105 |
if mergelist is not None: |
106 |
warnings.warn("The mergelist parameter of the " + \ |
107 |
|
108 |
diff --git a/pym/_emerge/TaskScheduler.py b/pym/_emerge/TaskScheduler.py |
109 |
index 71ac80f..583bfe3 100644 |
110 |
--- a/pym/_emerge/TaskScheduler.py |
111 |
+++ b/pym/_emerge/TaskScheduler.py |
112 |
@@ -1,4 +1,4 @@ |
113 |
-# Copyright 1999-2009 Gentoo Foundation |
114 |
+# Copyright 1999-2012 Gentoo Foundation |
115 |
# Distributed under the terms of the GNU General Public License v2 |
116 |
|
117 |
from _emerge.QueueScheduler import QueueScheduler |
118 |
@@ -11,9 +11,9 @@ class TaskScheduler(object): |
119 |
add tasks and call run(). The run() method returns when no tasks remain. |
120 |
""" |
121 |
|
122 |
- def __init__(self, max_jobs=None, max_load=None): |
123 |
+ def __init__(self, main=True, max_jobs=None, max_load=None): |
124 |
self._queue = SequentialTaskQueue(max_jobs=max_jobs) |
125 |
- self._scheduler = QueueScheduler( |
126 |
+ self._scheduler = QueueScheduler(main=main, |
127 |
max_jobs=max_jobs, max_load=max_load) |
128 |
self.sched_iface = self._scheduler.sched_iface |
129 |
self.run = self._scheduler.run |
130 |
|
131 |
diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py |
132 |
index ef20ce4..bbbce52 100644 |
133 |
--- a/pym/portage/util/_eventloop/EventLoop.py |
134 |
+++ b/pym/portage/util/_eventloop/EventLoop.py |
135 |
@@ -35,7 +35,15 @@ class EventLoop(object): |
136 |
__slots__ = ("args", "function", "calling", "interval", "source_id", |
137 |
"timestamp") |
138 |
|
139 |
- def __init__(self): |
140 |
+ def __init__(self, main=True): |
141 |
+ """ |
142 |
+ @param main: If True then this is a singleton instance for use |
143 |
+ in the main thread, otherwise it is a local instance which |
144 |
+ can safely be use in a non-main thread (default is True, so |
145 |
+ that global_event_loop does not need constructor arguments) |
146 |
+ @type main: bool |
147 |
+ """ |
148 |
+ self._use_signal = main |
149 |
self._poll_event_queue = [] |
150 |
self._poll_event_handlers = {} |
151 |
self._poll_event_handler_ids = {} |
152 |
@@ -198,20 +206,22 @@ class EventLoop(object): |
153 |
self._child_handlers[source_id] = self._child_callback_class( |
154 |
callback=callback, data=data, pid=pid, source_id=source_id) |
155 |
|
156 |
- if self._sigchld_read is None: |
157 |
- self._sigchld_read, self._sigchld_write = os.pipe() |
158 |
- fcntl.fcntl(self._sigchld_read, fcntl.F_SETFL, |
159 |
- fcntl.fcntl(self._sigchld_read, fcntl.F_GETFL) | os.O_NONBLOCK) |
160 |
- |
161 |
- # The IO watch is dynamically registered and unregistered as |
162 |
- # needed, since we don't want to consider it as a valid source |
163 |
- # of events when there are no child listeners. It's important |
164 |
- # to distinguish when there are no valid sources of IO events, |
165 |
- # in order to avoid an endless poll call if there's no timeout. |
166 |
- if self._sigchld_src_id is None: |
167 |
- self._sigchld_src_id = self.io_add_watch( |
168 |
- self._sigchld_read, self.IO_IN, self._sigchld_io_cb) |
169 |
- signal.signal(signal.SIGCHLD, self._sigchld_sig_cb) |
170 |
+ if self._use_signal: |
171 |
+ if self._sigchld_read is None: |
172 |
+ self._sigchld_read, self._sigchld_write = os.pipe() |
173 |
+ fcntl.fcntl(self._sigchld_read, fcntl.F_SETFL, |
174 |
+ fcntl.fcntl(self._sigchld_read, |
175 |
+ fcntl.F_GETFL) | os.O_NONBLOCK) |
176 |
+ |
177 |
+ # The IO watch is dynamically registered and unregistered as |
178 |
+ # needed, since we don't want to consider it as a valid source |
179 |
+ # of events when there are no child listeners. It's important |
180 |
+ # to distinguish when there are no valid sources of IO events, |
181 |
+ # in order to avoid an endless poll call if there's no timeout. |
182 |
+ if self._sigchld_src_id is None: |
183 |
+ self._sigchld_src_id = self.io_add_watch( |
184 |
+ self._sigchld_read, self.IO_IN, self._sigchld_io_cb) |
185 |
+ signal.signal(signal.SIGCHLD, self._sigchld_sig_cb) |
186 |
|
187 |
# poll now, in case the SIGCHLD has already arrived |
188 |
self._poll_child_processes() |
189 |
@@ -318,10 +328,15 @@ class EventLoop(object): |
190 |
|
191 |
def _run_timeouts(self): |
192 |
|
193 |
+ calls = 0 |
194 |
+ if not self._use_signal: |
195 |
+ if self._poll_child_processes(): |
196 |
+ calls += 1 |
197 |
+ |
198 |
self._run_idle_callbacks() |
199 |
|
200 |
if not self._timeout_handlers: |
201 |
- return False |
202 |
+ return bool(calls) |
203 |
|
204 |
ready_timeouts = [] |
205 |
current_time = time.time() |
206 |
@@ -334,7 +349,6 @@ class EventLoop(object): |
207 |
|
208 |
# Iterate of our local list, since self._timeout_handlers can be |
209 |
# modified during the exection of these callbacks. |
210 |
- calls = 0 |
211 |
for x in ready_timeouts: |
212 |
if x.source_id not in self._timeout_handlers: |
213 |
# it got cancelled while executing another timeout |
214 |
@@ -387,7 +401,7 @@ class EventLoop(object): |
215 |
""" |
216 |
x = self._child_handlers.pop(reg_id, None) |
217 |
if x is not None: |
218 |
- if not self._child_handlers: |
219 |
+ if not self._child_handlers and self._use_signal: |
220 |
signal.signal(signal.SIGCHLD, signal.SIG_DFL) |
221 |
self.source_remove(self._sigchld_src_id) |
222 |
self._sigchld_src_id = None |