public inbox for gentoo-commits@lists.gentoo.org
 help / color / mirror / Atom feed
From: "Zac Medico" <zmedico@gentoo.org>
To: gentoo-commits@lists.gentoo.org
Subject: [gentoo-commits] proj/portage:master commit in: pym/portage/util/_eventloop/
Date: Fri, 28 Dec 2012 01:44:40 +0000 (UTC)	[thread overview]
Message-ID: <1356659067.a0f22daa7cf359aac776a45bbc60d22dcd947034.zmedico@gentoo> (raw)

commit:     a0f22daa7cf359aac776a45bbc60d22dcd947034
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Fri Dec 28 01:35:49 2012 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri Dec 28 01:44:27 2012 +0000
URL:        http://git.overlays.gentoo.org/gitweb/?p=proj/portage.git;a=commit;h=a0f22daa

EventLoop.iteration(): avoid busy waiting

In order to avoid blocking forever when may_block is True (the
default), callers must be careful to ensure that at least one of the
following conditions is met:
	1) An event source or timeout is registered which is guaranteed
		to trigger at least on event (a call to an idle function
		only counts as an event if it returns a False value which
		causes it to be stop being called)
	2) Another thread is guaranteed to call one of the thread-safe
		methods which notify iteration to stop waiting (such as
		idle_add or timeout_add).
These rules ensure that iteration is able to block until an event
arrives, without doing any busy waiting that would waste CPU time.

This will fix busy waiting which would be triggered by
PopenPipeBlockingIOTestCase when waiting for the thread from
PipeReaderBlockingIO to call idle_add.

---
 pym/portage/util/_eventloop/EventLoop.py |   54 ++++++++++++++++++++++--------
 1 files changed, 40 insertions(+), 14 deletions(-)

diff --git a/pym/portage/util/_eventloop/EventLoop.py b/pym/portage/util/_eventloop/EventLoop.py
index efd1f13..b77e819 100644
--- a/pym/portage/util/_eventloop/EventLoop.py
+++ b/pym/portage/util/_eventloop/EventLoop.py
@@ -61,6 +61,7 @@ class EventLoop(object):
 		"""
 		self._use_signal = main and fcntl is not None
 		self._thread_rlock = threading.RLock()
+		self._thread_condition = threading.Condition(self._thread_rlock)
 		self._poll_event_queue = []
 		self._poll_event_handlers = {}
 		self._poll_event_handler_ids = {}
@@ -150,7 +151,19 @@ class EventLoop(object):
 
 	def iteration(self, *args):
 		"""
-		Like glib.MainContext.iteration(), runs a single iteration.
+		Like glib.MainContext.iteration(), runs a single iteration. In order
+		to avoid blocking forever when may_block is True (the default),
+		callers must be careful to ensure that at least one of the following
+		conditions is met:
+			1) An event source or timeout is registered which is guaranteed
+				to trigger at least on event (a call to an idle function
+				only counts as an event if it returns a False value which
+				causes it to stop being called)
+			2) Another thread is guaranteed to call one of the thread-safe
+				methods which notify iteration to stop waiting (such as
+				idle_add or timeout_add).
+		These rules ensure that iteration is able to block until an event
+		arrives, without doing any busy waiting that would waste CPU time.
 		@type may_block: bool
 		@param may_block: if True the call may block waiting for an event
 			(default is True).
@@ -171,19 +184,25 @@ class EventLoop(object):
 		events_handled = 0
 
 		if not event_handlers:
-			if self._run_timeouts():
-				events_handled += 1
-			if not event_handlers and not events_handled and may_block:
-				timeout = self._get_poll_timeout()
-				if timeout is not None:
+			with self._thread_condition:
+				if self._run_timeouts():
+					events_handled += 1
+				if not event_handlers and not events_handled and may_block:
 					# Block so that we don't waste cpu time by looping too
 					# quickly. This makes EventLoop useful for code that needs
 					# to wait for timeout callbacks regardless of whether or
 					# not any IO handlers are currently registered.
-					try:
-						self._poll(timeout=timeout)
-					except StopIteration:
-						pass
+					timeout = self._get_poll_timeout()
+					if timeout is None:
+						wait_timeout = None
+					else:
+						wait_timeout = float(timeout) / 1000
+					# NOTE: In order to avoid a possible infinite wait when
+					# wait_timeout is None, the previous _run_timeouts()
+					# call must have returned False *with* _thread_condition
+					# acquired. Otherwise, we would risk going to sleep after
+					# our only notify event has already passed.
+					self._thread_condition.wait(wait_timeout)
 					if self._run_timeouts():
 						events_handled += 1
 
@@ -338,16 +357,18 @@ class EventLoop(object):
 		@rtype: int
 		@return: an integer ID
 		"""
-		with self._thread_rlock:
+		with self._thread_condition:
 			source_id = self._new_source_id()
 			self._idle_callbacks[source_id] = self._idle_callback_class(
 				args=args, callback=callback, source_id=source_id)
+			self._thread_condition.notify()
 		return source_id
 
 	def _run_idle_callbacks(self):
 		# assumes caller has acquired self._thread_rlock
 		if not self._idle_callbacks:
-			return
+			return False
+		state_change = 0
 		# Iterate of our local list, since self._idle_callbacks can be
 		# modified during the exection of these callbacks.
 		for x in list(self._idle_callbacks.values()):
@@ -360,10 +381,13 @@ class EventLoop(object):
 			x.calling = True
 			try:
 				if not x.callback(*x.args):
+					state_change += 1
 					self.source_remove(x.source_id)
 			finally:
 				x.calling = False
 
+		return bool(state_change)
+
 	def timeout_add(self, interval, function, *args):
 		"""
 		Like glib.timeout_add(), interval argument is the number of
@@ -373,7 +397,7 @@ class EventLoop(object):
 		are passed to your function when it's called. This method is
 		thread-safe.
 		"""
-		with self._thread_rlock:
+		with self._thread_condition:
 			source_id = self._new_source_id()
 			self._timeout_handlers[source_id] = \
 				self._timeout_handler_class(
@@ -382,6 +406,7 @@ class EventLoop(object):
 			if self._timeout_interval is None or \
 				self._timeout_interval > interval:
 				self._timeout_interval = interval
+			self._thread_condition.notify()
 		return source_id
 
 	def _run_timeouts(self):
@@ -393,7 +418,8 @@ class EventLoop(object):
 
 		with self._thread_rlock:
 
-			self._run_idle_callbacks()
+			if self._run_idle_callbacks():
+				calls += 1
 
 			if not self._timeout_handlers:
 				return bool(calls)


             reply	other threads:[~2012-12-28  1:44 UTC|newest]

Thread overview: 61+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2012-12-28  1:44 Zac Medico [this message]
  -- strict thread matches above, loose matches on Subject: below --
2018-07-18  3:41 [gentoo-commits] proj/portage:master commit in: pym/portage/util/_eventloop/ Zac Medico
2018-07-17 19:27 Zac Medico
2018-07-17 19:07 Zac Medico
2018-06-27  3:05 Zac Medico
2018-05-26 20:22 Zac Medico
2018-05-25  3:15 Zac Medico
2018-05-13 16:58 Zac Medico
2018-05-07  0:27 Zac Medico
2018-04-19  6:41 Zac Medico
2018-04-19  6:16 Zac Medico
2018-04-18 21:52 Zac Medico
2018-04-17 18:24 Zac Medico
2018-04-17 18:02 Zac Medico
2018-04-17  6:24 Zac Medico
2018-04-17  0:53 Zac Medico
2018-04-16  8:48 Zac Medico
2018-04-16  6:43 Zac Medico
2018-04-16  0:03 Zac Medico
2018-04-15 22:32 Zac Medico
2018-04-09  1:27 Zac Medico
2018-04-08 22:08 Zac Medico
2018-04-08 21:37 Zac Medico
2018-04-08 21:18 Zac Medico
2018-04-07 20:20 Zac Medico
2017-05-05 18:47 Zac Medico
2013-02-25 23:53 Zac Medico
2013-01-04  3:39 Zac Medico
2012-12-31  3:16 Zac Medico
2012-12-28  1:36 Zac Medico
2012-12-27  2:39 Zac Medico
2012-12-27  2:31 Zac Medico
2012-11-22 12:23 Zac Medico
2012-09-26  2:26 Zac Medico
2012-08-22 16:23 Zac Medico
2012-08-22  5:39 Zac Medico
2012-02-18  4:04 Zac Medico
2012-02-17 23:23 Zac Medico
2012-02-17 22:17 Zac Medico
2012-02-17 10:31 Zac Medico
2012-02-17 10:20 Zac Medico
2012-02-17  9:29 Zac Medico
2012-02-17  6:29 Zac Medico
2012-02-17  6:04 Zac Medico
2012-02-17  3:08 Zac Medico
2012-02-16 21:43 Zac Medico
2012-02-16 21:35 Zac Medico
2012-02-16  4:58 Zac Medico
2012-02-16  4:41 Zac Medico
2012-02-14 16:27 Zac Medico
2012-02-14  3:06 Zac Medico
2012-02-14  1:23 Zac Medico
2012-02-11 23:56 Zac Medico
2012-02-11 21:41 Zac Medico
2012-02-11 21:11 Zac Medico
2012-02-11 19:35 Zac Medico
2012-02-11  2:59 Zac Medico
2012-02-10  0:42 Zac Medico
2012-02-10  0:20 Zac Medico
2012-02-10  0:17 Zac Medico
2012-02-09  7:25 Zac Medico

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=1356659067.a0f22daa7cf359aac776a45bbc60d22dcd947034.zmedico@gentoo \
    --to=zmedico@gentoo.org \
    --cc=gentoo-commits@lists.gentoo.org \
    --cc=gentoo-dev@lists.gentoo.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox