Gentoo Archives: gentoo-commits

From: "Zac Medico (zmedico)" <zmedico@g.o>
To: gentoo-commits@l.g.o
Subject: [gentoo-commits] portage r10855 - in main/trunk/pym: _emerge portage
Date: Mon, 30 Jun 2008 12:08:23
Message-Id: E1KDIB7-0003dd-AU@stork.gentoo.org
1 Author: zmedico
2 Date: 2008-06-30 12:08:16 +0000 (Mon, 30 Jun 2008)
3 New Revision: 10855
4
5 Modified:
6 main/trunk/pym/_emerge/__init__.py
7 main/trunk/pym/portage/__init__.py
8 Log:
9 Reimplement parallel-fetch by spawning the `ebuild fetch` command for each
10 ebuild. The benefit of using this approach is that it can be integrated
11 together with parallel build scheduling that's planned. Parallel-fetch
12 support for binhost is not implemented yet, though it worked previously.
13
14
15 Modified: main/trunk/pym/_emerge/__init__.py
16 ===================================================================
17 --- main/trunk/pym/_emerge/__init__.py 2008-06-30 03:18:07 UTC (rev 10854)
18 +++ main/trunk/pym/_emerge/__init__.py 2008-06-30 12:08:16 UTC (rev 10855)
19 @@ -1433,26 +1433,141 @@
20 v = 0
21 self._pkg.mtime = v
22
23 -class EbuildFetcher(Task):
24 +class EbuildFetcher(SlotObject):
25
26 __slots__ = ("fetch_all", "pkg", "pretend", "settings")
27
28 - def _get_hash_key(self):
29 - hash_key = getattr(self, "_hash_key", None)
30 - if hash_key is None:
31 - self._hash_key = ("EbuildFetcher", self.pkg._get_hash_key())
32 - return self._hash_key
33 -
34 def execute(self):
35 portdb = self.pkg.root_config.trees["porttree"].dbapi
36 ebuild_path = portdb.findname(self.pkg.cpv)
37 debug = self.settings.get("PORTAGE_DEBUG") == "1"
38 +
39 retval = portage.doebuild(ebuild_path, "fetch",
40 - self.settings["ROOT"], self.settings, debug,
41 - self.pretend, fetchonly=1, fetchall=self.fetch_all,
42 + self.settings["ROOT"], self.settings, debug=debug,
43 + listonly=self.pretend, fetchonly=1, fetchall=self.fetch_all,
44 mydbapi=portdb, tree="porttree")
45 return retval
46
47 +class EbuildFetcherAsync(SlotObject):
48 +
49 + __slots__ = ("log_file", "fd_pipes", "pkg",
50 + "register", "unregister",
51 + "pid", "returncode", "files")
52 +
53 + _file_names = ("fetcher", "out")
54 + _files_dict = slot_dict_class(_file_names)
55 + _bufsize = 4096
56 +
57 + def start(self):
58 + # flush any pending output
59 + fd_pipes = self.fd_pipes
60 + if fd_pipes is None:
61 + fd_pipes = {
62 + 0 : sys.stdin.fileno(),
63 + 1 : sys.stdout.fileno(),
64 + 2 : sys.stderr.fileno(),
65 + }
66 +
67 + log_file = self.log_file
68 + self.files = self._files_dict()
69 + files = self.files
70 +
71 + if log_file is not None:
72 + files["out"] = open(log_file, "a")
73 + portage.util.apply_secpass_permissions(log_file,
74 + uid=portage.portage_uid, gid=portage.portage_gid,
75 + mode=0660)
76 + else:
77 + for fd in fd_pipes.itervalues():
78 + if fd == sys.stdout.fileno():
79 + sys.stdout.flush()
80 + if fd == sys.stderr.fileno():
81 + sys.stderr.flush()
82 +
83 + files["out"] = os.fdopen(os.dup(fd_pipes[1]), 'w')
84 +
85 + master_fd, slave_fd = os.pipe()
86 +
87 + import fcntl
88 + fcntl.fcntl(master_fd, fcntl.F_SETFL,
89 + fcntl.fcntl(master_fd, fcntl.F_GETFL) | os.O_NONBLOCK)
90 +
91 + fd_pipes.setdefault(0, sys.stdin.fileno())
92 + fd_pipes_orig = fd_pipes.copy()
93 + fd_pipes[0] = fd_pipes_orig[0]
94 + fd_pipes[1] = slave_fd
95 + fd_pipes[2] = slave_fd
96 +
97 + root_config = self.pkg.root_config
98 + portdb = root_config.trees["porttree"].dbapi
99 + ebuild_path = portdb.findname(self.pkg.cpv)
100 + settings = root_config.settings
101 +
102 + fetch_env = dict((k, settings[k]) for k in settings)
103 + fetch_env["FEATURES"] = fetch_env.get("FEATURES", "") + " -cvs"
104 + fetch_env["PORTAGE_NICENESS"] = "0"
105 + fetch_env["PORTAGE_PARALLEL_FETCHONLY"] = "1"
106 +
107 + ebuild_binary = os.path.join(
108 + settings["EBUILD_BIN_PATH"], "ebuild")
109 +
110 + fetch_args = [ebuild_binary, ebuild_path, "fetch"]
111 + debug = settings.get("PORTAGE_DEBUG") == "1"
112 + if debug:
113 + fetch_args.append("--debug")
114 +
115 + retval = portage.process.spawn(fetch_args, env=fetch_env,
116 + fd_pipes=fd_pipes, returnpid=True)
117 +
118 + self.pid = retval[0]
119 +
120 + os.close(slave_fd)
121 + files["fetcher"] = os.fdopen(master_fd, 'r')
122 + self.register(files["fetcher"].fileno(),
123 + select.POLLIN, self._output_handler)
124 +
125 + def _output_handler(self, fd, event):
126 + files = self.files
127 + buf = array.array('B')
128 + try:
129 + buf.fromfile(files["fetcher"], self._bufsize)
130 + except EOFError:
131 + pass
132 + if buf:
133 + buf.tofile(files["out"])
134 + files["out"].flush()
135 + else:
136 + self.unregister(files["fetcher"].fileno())
137 + for f in files.values():
138 + f.close()
139 +
140 + def poll(self):
141 + if self.returncode is not None:
142 + return self.returncode
143 + retval = os.waitpid(self.pid, os.WNOHANG)
144 + if retval == (0, 0):
145 + return None
146 + self._set_returncode(retval)
147 + return self.returncode
148 +
149 + def wait(self):
150 + if self.returncode is not None:
151 + return self.returncode
152 + self._set_returncode(os.waitpid(self.pid, 0))
153 + return self.returncode
154 +
155 + def _set_returncode(self, wait_retval):
156 +
157 + retval = wait_retval[1]
158 + portage.process.spawned_pids.remove(self.pid)
159 + if retval != os.EX_OK:
160 + if retval & 0xff:
161 + retval = (retval & 0xff) << 8
162 + else:
163 + retval = retval >> 8
164 +
165 + self.returncode = retval
166 +
167 class EbuildBuildDir(SlotObject):
168
169 __slots__ = ("pkg", "settings",
170 @@ -1566,9 +1681,12 @@
171 ebuild_phase = EbuildPhase(fd_pipes=fd_pipes,
172 pkg=self.pkg, phase=mydo, register=self.register,
173 settings=settings, unregister=self.unregister)
174 +
175 ebuild_phase.start()
176 - self.schedule()
177 - retval = ebuild_phase.wait()
178 + retval = None
179 + while retval is None:
180 + self.schedule()
181 + retval = ebuild_phase.poll()
182
183 portage._post_phase_userpriv_perms(settings)
184 if mydo == "install":
185 @@ -1686,10 +1804,25 @@
186 for f in files.values():
187 f.close()
188
189 + def poll(self):
190 + if self.returncode is not None:
191 + return self.returncode
192 + retval = os.waitpid(self.pid, os.WNOHANG)
193 + if retval == (0, 0):
194 + return None
195 + self._set_returncode(retval)
196 + return self.returncode
197 +
198 def wait(self):
199 - pid = self.pid
200 - retval = os.waitpid(pid, 0)[1]
201 - portage.process.spawned_pids.remove(pid)
202 + if self.returncode is not None:
203 + return self.returncode
204 + self._set_returncode(os.waitpid(self.pid, 0))
205 + return self.returncode
206 +
207 + def _set_returncode(self, wait_retval):
208 +
209 + retval = wait_retval[1]
210 + portage.process.spawned_pids.remove(self.pid)
211 if retval != os.EX_OK:
212 if retval & 0xff:
213 retval = (retval & 0xff) << 8
214 @@ -1706,7 +1839,6 @@
215 eerror(l, phase=self.phase, key=self.pkg.cpv)
216
217 self.returncode = retval
218 - return self.returncode
219
220 class EbuildBinpkg(Task):
221 """
222 @@ -6327,6 +6459,8 @@
223 "--fetchonly", "--fetch-all-uri",
224 "--nodeps", "--pretend"])
225
226 + _fetch_log = "/var/log/emerge-fetch.log"
227 +
228 def __init__(self, settings, trees, mtimedb, myopts,
229 spinner, mergelist, favorites, digraph):
230 self.settings = settings
231 @@ -6345,10 +6479,39 @@
232 self.pkgsettings[root] = portage.config(
233 clone=trees[root]["vartree"].settings)
234 self.curval = 0
235 - self._spawned_pids = []
236 self._poll_event_handlers = {}
237 self._poll = select.poll()
238 + from collections import deque
239 + self._task_queue = deque()
240 + self._running_tasks = set()
241 + self._max_jobs = 1
242 + self._parallel_fetch = False
243 + features = self.settings.features
244 + if "parallel-fetch" in features and \
245 + not ("--pretend" in self.myopts or \
246 + "--fetch-all-uri" in self.myopts or \
247 + "--fetchonly" in self.myopts):
248 + if "distlocks" not in features:
249 + portage.writemsg(red("!!!")+"\n", noiselevel=-1)
250 + portage.writemsg(red("!!!")+" parallel-fetching " + \
251 + "requires the distlocks feature enabled"+"\n",
252 + noiselevel=-1)
253 + portage.writemsg(red("!!!")+" you have it disabled, " + \
254 + "thus parallel-fetching is being disabled"+"\n",
255 + noiselevel=-1)
256 + portage.writemsg(red("!!!")+"\n", noiselevel=-1)
257 + elif len(mergelist) > 1:
258 + self._parallel_fetch = True
259
260 + # clear out existing fetch log if it exists
261 + try:
262 + open(self._fetch_log, 'w')
263 + except EnvironmentError:
264 + pass
265 +
266 + def _add_task(self, task):
267 + self._task_queue.append(task)
268 +
269 class _pkg_failure(portage.exception.PortageException):
270 """
271 An instance of this class is raised by unmerge() when
272 @@ -6400,20 +6563,18 @@
273 def merge(self):
274
275 keep_going = "--keep-going" in self.myopts
276 + running_tasks = self._running_tasks
277
278 while True:
279 try:
280 rval = self._merge()
281 finally:
282 - spawned_pids = self._spawned_pids
283 - while spawned_pids:
284 - pid = spawned_pids.pop()
285 - try:
286 - if os.waitpid(pid, os.WNOHANG) == (0, 0):
287 - os.kill(pid, signal.SIGTERM)
288 - os.waitpid(pid, 0)
289 - except OSError:
290 - pass # cleaned up elsewhere.
291 + # clean up child process if necessary
292 + while running_tasks:
293 + task = running_tasks.pop()
294 + if task.poll() is None:
295 + os.kill(task.pid, signal.SIGTERM)
296 + task.wait()
297
298 if rval == os.EX_OK or not keep_going:
299 break
300 @@ -6493,25 +6654,6 @@
301 mydepgraph.break_refs(dropped_tasks)
302 return (mylist, dropped_tasks)
303
304 - def _poll_child_processes(self):
305 - """
306 - After each merge, collect status from child processes
307 - in order to clean up zombies (such as the parallel-fetch
308 - process).
309 - """
310 - spawned_pids = self._spawned_pids
311 - if not spawned_pids:
312 - return
313 - for pid in list(spawned_pids):
314 - try:
315 - if os.waitpid(pid, os.WNOHANG) == (0, 0):
316 - continue
317 - except OSError:
318 - # This pid has been cleaned up elsewhere,
319 - # so remove it from our list.
320 - pass
321 - spawned_pids.remove(pid)
322 -
323 def _register(self, f, eventmask, handler):
324 self._poll_event_handlers[f] = handler
325 self._poll.register(f, eventmask)
326 @@ -6519,12 +6661,44 @@
327 def _unregister(self, f):
328 self._poll.unregister(f)
329 del self._poll_event_handlers[f]
330 + self._schedule_tasks()
331
332 def _schedule(self):
333 - while self._poll_event_handlers:
334 - for f, event in self._poll.poll():
335 - self._poll_event_handlers[f](f, event)
336 + event_handlers = self._poll_event_handlers
337 + running_tasks = self._running_tasks
338 + poll = self._poll.poll
339
340 + self._schedule_tasks()
341 +
342 + while event_handlers:
343 + for f, event in poll():
344 + event_handlers[f](f, event)
345 +
346 + if len(event_handlers) <= len(running_tasks):
347 + # Assuming one handler per task, this
348 + # means the caller has unregistered it's
349 + # handler, so it's time to yield.
350 + break
351 +
352 + def _schedule_tasks(self):
353 + task_queue = self._task_queue
354 + running_tasks = self._running_tasks
355 + max_jobs = self._max_jobs
356 + state_changed = False
357 +
358 + for task in list(running_tasks):
359 + if task.poll() is not None:
360 + running_tasks.remove(task)
361 + state_changed = True
362 +
363 + while task_queue and (len(running_tasks) < max_jobs):
364 + task = task_queue.popleft()
365 + task.start()
366 + running_tasks.add(task)
367 + state_changed = True
368 +
369 + return state_changed
370 +
371 def _merge(self):
372 mylist = self._mergelist
373 favorites = self._favorites
374 @@ -6551,6 +6725,16 @@
375 if isinstance(x, Package) and x.operation == "merge"]
376 mtimedb.commit()
377
378 + if self._parallel_fetch:
379 + for pkg in mylist:
380 + if not isinstance(pkg, Package) or \
381 + not pkg.type_name == "ebuild":
382 + continue
383 +
384 + self._add_task(EbuildFetcherAsync(log_file=self._fetch_log,
385 + pkg=pkg, register=self._register,
386 + unregister=self._unregister))
387 +
388 # Verify all the manifests now so that the user is notified of failure
389 # as soon as possible.
390 if "--fetchonly" not in self.myopts and \
391 @@ -6584,49 +6768,6 @@
392 myfeat = self.settings.features[:]
393 bad_resume_opts = set(["--ask", "--changelog", "--skipfirst",
394 "--resume"])
395 - if "parallel-fetch" in myfeat and \
396 - not ("--pretend" in self.myopts or \
397 - "--fetch-all-uri" in self.myopts or \
398 - "--fetchonly" in self.myopts):
399 - if "distlocks" not in myfeat:
400 - print red("!!!")
401 - print red("!!!")+" parallel-fetching requires the distlocks feature enabled"
402 - print red("!!!")+" you have it disabled, thus parallel-fetching is being disabled"
403 - print red("!!!")
404 - elif len(mymergelist) > 1:
405 - fetch_log = "/var/log/emerge-fetch.log"
406 - logfile = open(fetch_log, "w")
407 - fd_pipes = {1:logfile.fileno(), 2:logfile.fileno()}
408 - portage.util.apply_secpass_permissions(fetch_log,
409 - uid=portage.portage_uid, gid=portage.portage_gid,
410 - mode=0660)
411 - fetch_env = os.environ.copy()
412 - fetch_env["FEATURES"] = fetch_env.get("FEATURES", "") + " -cvs"
413 - fetch_env["PORTAGE_NICENESS"] = "0"
414 - fetch_env["PORTAGE_PARALLEL_FETCHONLY"] = "1"
415 - fetch_args = [sys.argv[0], "--resume",
416 - "--fetchonly", "--nodeps"]
417 - resume_opts = self.myopts.copy()
418 - # For automatic resume, we need to prevent
419 - # any of bad_resume_opts from leaking in
420 - # via EMERGE_DEFAULT_OPTS.
421 - resume_opts["--ignore-default-opts"] = True
422 - for myopt, myarg in resume_opts.iteritems():
423 - if myopt not in bad_resume_opts:
424 - if myarg is True:
425 - fetch_args.append(myopt)
426 - else:
427 - fetch_args.append(myopt +"="+ myarg)
428 - self._spawned_pids.extend(
429 - portage.process.spawn(
430 - fetch_args, env=fetch_env,
431 - fd_pipes=fd_pipes, returnpid=True))
432 - logfile.close() # belongs to the spawned process
433 - del fetch_log, logfile, fd_pipes, fetch_env, fetch_args, \
434 - resume_opts
435 - print ">>> starting parallel fetching pid %d" % \
436 - self._spawned_pids[-1]
437 -
438 metadata_keys = [k for k in portage.auxdbkeys \
439 if not k.startswith("UNUSED_")] + ["USE"]
440
441 @@ -6926,7 +7067,6 @@
442 # due to power failure, SIGKILL, etc...
443 mtimedb.commit()
444 self.curval += 1
445 - self._poll_child_processes()
446
447 def _post_merge(self, mtimedb, xterm_titles, failed_fetches):
448 if "--pretend" not in self.myopts:
449
450 Modified: main/trunk/pym/portage/__init__.py
451 ===================================================================
452 --- main/trunk/pym/portage/__init__.py 2008-06-30 03:18:07 UTC (rev 10854)
453 +++ main/trunk/pym/portage/__init__.py 2008-06-30 12:08:16 UTC (rev 10855)
454 @@ -3274,8 +3274,9 @@
455 # file size. The parent process will verify their checksums prior to
456 # the unpack phase.
457
458 - parallel_fetchonly = fetchonly and \
459 - "PORTAGE_PARALLEL_FETCHONLY" in mysettings
460 + parallel_fetchonly = "PORTAGE_PARALLEL_FETCHONLY" in mysettings
461 + if parallel_fetchonly:
462 + fetchonly = 1
463
464 check_config_instance(mysettings)
465
466
467 --
468 gentoo-commits@l.g.o mailing list