PollScheduler: timeouts regardless of IO events
authorZac Medico <zmedico@gentoo.org>
Tue, 7 Feb 2012 19:40:55 +0000 (11:40 -0800)
committerZac Medico <zmedico@gentoo.org>
Tue, 7 Feb 2012 23:23:59 +0000 (15:23 -0800)
Now PollScheduler will execute timeouts predictably, even when there
no IO events being generated. This allows the Scheduler's display
updates to be handled via timeout_add.

pym/_emerge/JobStatusDisplay.py
pym/_emerge/PollScheduler.py
pym/_emerge/Scheduler.py

index 877a0c96363793ffff982ebe75a1aa4db2dd3867..d84d1b060aa59486b729150c244feb8c532f7565 100644 (file)
@@ -209,24 +209,26 @@ class JobStatusDisplay(object):
        def display(self):
                """
                Display status on stdout, but only if something has
-               changed since the last call.
+               changed since the last call. This always returns True,
+               for continuous scheduling via timeout_add.
                """
 
                if self.quiet:
-                       return
+                       return True
 
                current_time = time.time()
                time_delta = current_time - self._last_display_time
                if self._displayed and \
                        not self._changed:
                        if not self._isatty:
-                               return
+                               return True
                        if time_delta < self._min_display_latency:
-                               return
+                               return True
 
                self._last_display_time = current_time
                self._changed = False
                self._display_status()
+               return True
 
        def _display_status(self):
                # Don't use len(self._completed_tasks) here since that also
index fd573592a43c64d8271d69137be276f6c9378d82..519a3700d6cccf14bffd44f13a3ad494f73d4997 100644 (file)
@@ -43,6 +43,7 @@ class PollScheduler(object):
                # Increment id for each new handler.
                self._event_handler_id = 0
                self._timeout_handlers = {}
+               self._timeout_interval = None
                self._poll_obj = create_poll_instance()
                self._polling = False
                self._scheduling = False
@@ -142,18 +143,51 @@ class PollScheduler(object):
                return True
 
        def _poll(self, timeout=None):
-               """
-               All poll() calls pass through here. The poll events
-               are added directly to self._poll_event_queue.
-               In order to avoid endless blocking, this raises
-               StopIteration if timeout is None and there are
-               no file descriptors to poll.
-               """
                if self._polling:
                        return
                self._polling = True
                try:
-                       self._do_poll(timeout=timeout)
+                       if self._timeout_interval is None:
+                               self._run_timeouts()
+                               self._do_poll(timeout=timeout)
+
+                       elif timeout is None:
+                               while True:
+                                       self._run_timeouts()
+                                       previous_count = len(self._poll_event_queue)
+                                       self._do_poll(timeout=self._timeout_interval)
+                                       if previous_count != len(self._poll_event_queue):
+                                               break
+
+                       elif timeout <= self._timeout_interval:
+                               self._run_timeouts()
+                               self._do_poll(timeout=timeout)
+
+                       else:
+                               remaining_timeout = timeout
+                               start_time = time.time()
+                               while True:
+                                       self._run_timeouts()
+                                       # _timeout_interval can change each time
+                                       # _run_timeouts is called
+                                       min_timeout = remaining_timeout
+                                       if self._timeout_interval is not None and \
+                                               self._timeout_interval < min_timeout:
+                                               min_timeout = self._timeout_interval
+
+                                       previous_count = len(self._poll_event_queue)
+                                       self._do_poll(timeout=min_timeout)
+                                       if previous_count != len(self._poll_event_queue):
+                                               break
+                                       elapsed_time = time.time() - start_time
+                                       if elapsed_time < 0:
+                                               # The system clock has changed such that start_time
+                                               # is now in the future, so just assume that the
+                                               # timeout has already elapsed.
+                                               break
+                                       remaining_timeout = timeout - 1000 * elapsed_time
+                                       if remaining_timeout <= 0:
+                                               break
                finally:
                        self._polling = False
 
@@ -165,7 +199,6 @@ class PollScheduler(object):
                StopIteration if timeout is None and there are
                no file descriptors to poll.
                """
-               self._run_timeouts()
                if not self._poll_event_handlers:
                        self._schedule()
                        if timeout is None and \
@@ -273,6 +306,8 @@ class PollScheduler(object):
                        self._timeout_handler_class(
                                interval=interval, function=function, args=args,
                                source_id=source_id, timestamp=time.time())
+               if self._timeout_interval is None or self._timeout_interval < interval:
+                       self._timeout_interval = interval
                return source_id
 
        def _run_timeouts(self):
@@ -320,6 +355,12 @@ class PollScheduler(object):
                """
                timeout_handler = self._timeout_handlers.pop(reg_id, None)
                if timeout_handler is not None:
+                       if timeout_handler.interval == self._timeout_interval:
+                               if self._timeout_handlers:
+                                       self._timeout_interval = \
+                                               min(x.interval for x in self._timeout_handlers.values())
+                               else:
+                                       self._timeout_interval = None
                        return True
                f = self._poll_event_handler_ids.pop(reg_id, None)
                if f is None:
index 5b56650f321cb768e6c3a30c2d4639e73bfd9b62..e6f3e0e5c4e840603aac0f9bdd822a2fcdc6b9e5 100644 (file)
@@ -196,6 +196,8 @@ class Scheduler(PollScheduler):
 
                self._status_display = JobStatusDisplay(
                        xterm_titles=('notitles' not in settings.features))
+               self._timeout_add(self._max_display_latency,
+                       self._status_display.display)
                self._max_load = myopts.get("--load-average")
                max_jobs = myopts.get("--jobs")
                if max_jobs is None:
@@ -352,50 +354,8 @@ class Scheduler(PollScheduler):
                gc.collect()
 
        def _poll(self, timeout=None):
-
                self._schedule()
-
-               if timeout is None:
-                       while True:
-                               if not self._poll_event_handlers:
-                                       self._schedule()
-                                       if not self._poll_event_handlers:
-                                               raise StopIteration(
-                                                       "timeout is None and there are no poll() event handlers")
-                               previous_count = len(self._poll_event_queue)
-                               PollScheduler._poll(self, timeout=self._max_display_latency)
-                               self._status_display.display()
-                               if previous_count != len(self._poll_event_queue):
-                                       break
-
-               elif timeout <= self._max_display_latency:
-                       PollScheduler._poll(self, timeout=timeout)
-                       if timeout == 0:
-                               # The display is updated by _schedule() above, so it would be
-                               # redundant to update it here when timeout is 0.
-                               pass
-                       else:
-                               self._status_display.display()
-
-               else:
-                       remaining_timeout = timeout
-                       start_time = time.time()
-                       while True:
-                               previous_count = len(self._poll_event_queue)
-                               PollScheduler._poll(self,
-                                       timeout=min(self._max_display_latency, remaining_timeout))
-                               self._status_display.display()
-                               if previous_count != len(self._poll_event_queue):
-                                       break
-                               elapsed_time = time.time() - start_time
-                               if elapsed_time < 0:
-                                       # The system clock has changed such that start_time
-                                       # is now in the future, so just assume that the
-                                       # timeout has already elapsed.
-                                       break
-                               remaining_timeout = timeout - 1000 * elapsed_time
-                               if remaining_timeout <= 0:
-                                       break
+               PollScheduler._poll(self, timeout=timeout)
 
        def _set_max_jobs(self, max_jobs):
                self._max_jobs = max_jobs