commit:     5d476c4e500248929d6b042de302b1e7c923dc60
Author:     Zac Medico <zmedico <AT> gentoo <DOT> org>
AuthorDate: Fri Mar  6 08:11:05 2020 +0000
Commit:     Zac Medico <zmedico <AT> gentoo <DOT> org>
CommitDate: Fri Mar  6 08:55:17 2020 +0000
URL:        https://gitweb.gentoo.org/proj/portage.git/commit/?id=5d476c4e

AsynchronousTask: handle addExistListener after exit

When addExistListener is called after the task has already exited with
a returncode, immediately schedule the listener to be invoked via
call_soon. This behavior is similar to the Future add_done_callback
method.

Signed-off-by: Zac Medico <zmedico <AT> gentoo.org>

 lib/_emerge/AsynchronousTask.py                    |  2 ++
 .../util/futures/test_done_callback_after_exit.py  | 40 ++++++++++++++++++++++
 2 files changed, 42 insertions(+)

diff --git a/lib/_emerge/AsynchronousTask.py b/lib/_emerge/AsynchronousTask.py
index 799e66a4a..97db02587 100644
--- a/lib/_emerge/AsynchronousTask.py
+++ b/lib/_emerge/AsynchronousTask.py
@@ -176,6 +176,8 @@ class AsynchronousTask(SlotObject):
                if self._exit_listeners is None:
                        self._exit_listeners = []
                self._exit_listeners.append(f)
+               if self.returncode is not None:
+                       self._wait_hook()
 
        def removeExitListener(self, f):
                if self._exit_listeners is not None:

diff --git a/lib/portage/tests/util/futures/test_done_callback_after_exit.py 
b/lib/portage/tests/util/futures/test_done_callback_after_exit.py
new file mode 100644
index 000000000..46a51c271
--- /dev/null
+++ b/lib/portage/tests/util/futures/test_done_callback_after_exit.py
@@ -0,0 +1,40 @@
+# Copyright 2020 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+from _emerge.AsynchronousTask import AsynchronousTask
+from portage.tests import TestCase
+from portage.util.futures import asyncio
+
+
+class DoneCallbackAfterExitTestCase(TestCase):
+
+       def test_done_callback_after_exit(self):
+               """
+               Test that callbacks can be registered via the Future
+               add_done_callback method even after the future is done, and
+               verify that the callbacks are called.
+               """
+               loop = asyncio._wrap_loop()
+               future = loop.create_future()
+               future.set_result(None)
+
+               for i in range(3):
+                       event = loop.create_future()
+                       future.add_done_callback(lambda future: 
event.set_result(None))
+                       loop.run_until_complete(event)
+
+       def test_exit_listener_after_exit(self):
+               """
+               Test that callbacks can be registered via the AsynchronousTask
+               addExitListener method even after the task is done, and
+               verify that the callbacks are called.
+               """
+               loop = asyncio._wrap_loop()
+               task = AsynchronousTask(scheduler=loop)
+               loop.run_until_complete(task.async_start())
+               loop.run_until_complete(task.async_wait())
+
+               for i in range(3):
+                       event = loop.create_future()
+                       task.addExitListener(lambda task: 
event.set_result(None))
+                       loop.run_until_complete(event)

Reply via email to