Add a terminate method to FetchIterator so that it doesn't
delay termination of emirrordist via SIGINT. Otherwise it
it is possible for the __iter__ method to loop for a very
long time after SIGINT has been delivered. For example,
this could happen if there are many ebuilds with stale
cache and RESTRICT=mirror. This issue was discovered
during testing of changes to the MirrorDistTask.terminate
implementation.
---
 pym/portage/_emirrordist/FetchIterator.py  | 21 +++++++++++++++++++++
 pym/portage/_emirrordist/MirrorDistTask.py |  6 +++++-
 2 files changed, 26 insertions(+), 1 deletion(-)

diff --git a/pym/portage/_emirrordist/FetchIterator.py 
b/pym/portage/_emirrordist/FetchIterator.py
index 16a0b04..3841979 100644
--- a/pym/portage/_emirrordist/FetchIterator.py
+++ b/pym/portage/_emirrordist/FetchIterator.py
@@ -1,6 +1,8 @@
 # Copyright 2013 Gentoo Foundation
 # Distributed under the terms of the GNU General Public License v2
 
+import threading
+
 from portage import os
 from portage.checksum import (_apply_hash_filter,
        _filter_unaccelarated_hashes, _hash_filter)
@@ -13,6 +15,19 @@ class FetchIterator(object):
        def __init__(self, config):
                self._config = config
                self._log_failure = config.log_failure
+               self._terminated = threading.Event()
+
+       def terminate(self):
+               """
+               Schedules early termination of the __iter__ method, which is
+               useful because under some conditions it's possible for __iter__
+               to loop for a long time without yielding to the caller. For
+               example, it's useful when there are many ebuilds with stale
+               cache and RESTRICT=mirror.
+
+               This method is thread-safe (and safe for signal handlers).
+               """
+               self._terminated.set()
 
        def _iter_every_cp(self):
                # List categories individually, in order to start yielding 
quicker,
@@ -37,6 +52,9 @@ class FetchIterator(object):
 
                for cp in self._iter_every_cp():
 
+                       if self._terminated.is_set():
+                               return
+
                        for tree in portdb.porttrees:
 
                                # Reset state so the Manifest is pulled once
@@ -46,6 +64,9 @@ class FetchIterator(object):
 
                                for cpv in portdb.cp_list(cp, mytree=tree):
 
+                                       if self._terminated.is_set():
+                                               return
+
                                        try:
                                                restrict, = portdb.aux_get(cpv, 
("RESTRICT",),
                                                        mytree=tree)
diff --git a/pym/portage/_emirrordist/MirrorDistTask.py 
b/pym/portage/_emirrordist/MirrorDistTask.py
index 0702eb1..8da9f06 100644
--- a/pym/portage/_emirrordist/MirrorDistTask.py
+++ b/pym/portage/_emirrordist/MirrorDistTask.py
@@ -32,9 +32,11 @@ class MirrorDistTask(CompositeTask):
                self._config = config
                self._term_rlock = threading.RLock()
                self._term_callback_handle = None
+               self._fetch_iterator = None
 
        def _start(self):
-               fetch = TaskScheduler(iter(FetchIterator(self._config)),
+               self._fetch_iterator = FetchIterator(self._config)
+               fetch = TaskScheduler(iter(self._fetch_iterator),
                        max_jobs=self._config.options.jobs,
                        max_load=self._config.options.load_average,
                        event_loop=self._config.event_loop)
@@ -226,6 +228,8 @@ class MirrorDistTask(CompositeTask):
                                        self._term_callback)
 
        def _term_callback(self):
+               if self._fetch_iterator is not None:
+                       self._fetch_iterator.terminate()
                self.cancel()
                self.wait()
 
-- 
2.10.2


Reply via email to