[tor-commits] [stem/master] Investigate an async ainit method

atagar at torproject.org atagar at torproject.org
Thu Jul 16 01:29:00 UTC 2020


commit ef06783b96ee38230b41cc08c14032d7127f867b
Author: Damian Johnson <atagar at torproject.org>
Date:   Tue Jul 7 16:03:11 2020 -0700

    Investigate an async ainit method
    
    Our ainit method should be asynchronous, but guess that's not to be.
    Documenting the issues we encountered.
---
 stem/util/__init__.py | 52 +++++++++++++++++++++++++++++++++++++++++++--------
 1 file changed, 44 insertions(+), 8 deletions(-)

diff --git a/stem/util/__init__.py b/stem/util/__init__.py
index 5a8f95e0..cddce755 100644
--- a/stem/util/__init__.py
+++ b/stem/util/__init__.py
@@ -199,14 +199,11 @@ class Synchronous(object):
   """
 
   def __init__(self) -> None:
-    ainit_func = getattr(self, '__ainit__', None)
-
     if Synchronous.is_asyncio_context():
       self._loop = asyncio.get_running_loop()
       self._loop_thread = None
 
-      if ainit_func:
-        ainit_func()
+      self.__ainit__()
     else:
       self._loop = asyncio.new_event_loop()
       self._loop_thread = threading.Thread(
@@ -231,11 +228,50 @@ class Synchronous(object):
         if inspect.iscoroutinefunction(func):
           setattr(self, method_name, functools.partial(call_async, func))
 
-      if ainit_func:
-        async def call_ainit():
-          ainit_func()
+      asyncio.run_coroutine_threadsafe(asyncio.coroutine(self.__ainit__)(), self._loop).result()
+
+  def __ainit__(self):
+    """
+    Implicitly called during construction. This method is assured to have an
+    asyncio loop during its execution.
+    """
 
-        asyncio.run_coroutine_threadsafe(call_ainit(), self._loop).result()
+    # This method should be async (so 'await' works), but apparently that
+    # is not possible.
+    #
+    # When our object is constructed our __init__() can be called from a
+    # synchronous or asynchronous context. If synchronous, it's trivial to
+    # run an asynchronous variant of this method because we fully control
+    # the execution of our loop...
+    #
+    #   asyncio.run_coroutine_threadsafe(self.__ainit__(), self._loop).result()
+    #
+    # However, when constructed from an asynchronous context the above will
+    # likely hang because our loop is already processing a task (namely,
+    # whatever is constructing us). While we can schedule tasks, we cannot
+    # invoke it during our construction.
+    #
+    # Finally, when this method is simple we could directly invoke it...
+    #
+    #   class Synchronous(object):
+    #     def __init__(self):
+    #       if Synchronous.is_asyncio_context():
+    #         try:
+    #           self.__ainit__().send(None)
+    #         except StopIteration:
+    #           pass
+    #       else:
+    #         asyncio.run_coroutine_threadsafe(self.__ainit__(), self._loop).result()
+    #
+    #     async def __ainit__(self):
+    #       # asynchronous construction
+    #
+    # However, this breaks if any 'await' suspends our execution. For more
+    # information see...
+    #
+    #   https://stackoverflow.com/questions/52783605/how-to-run-a-coroutine-outside-of-an-event-loop/52829325#52829325
+
+    pass
 
   def close(self) -> None:
     """





More information about the tor-commits mailing list