[Pkg-mpd-commits] [python-mpd] 63/91: asyncio: Proper idle implementation

Simon McVittie smcv at debian.org
Sat Feb 24 14:55:37 UTC 2018


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to branch debian/master
in repository python-mpd.

commit 1efb003fdd1b56024a4065d978f8d5d17170f7c3
Author: chrysn <chrysn at fsfe.org>
Date:   Fri Apr 21 14:07:42 2017 +0200

    asyncio: Proper idle implementation
    
    This is Python 3.6 again and will be fixed similar to the desugaring of
    _parse_objects_direct
---
 examples/asyncio_example.py |  8 +++++-
 mpd/asyncio.py              | 67 ++++++++++++++++++++++++++++++++++-----------
 2 files changed, 58 insertions(+), 17 deletions(-)

diff --git a/examples/asyncio_example.py b/examples/asyncio_example.py
index 4ea8fac..b57a782 100644
--- a/examples/asyncio_example.py
+++ b/examples/asyncio_example.py
@@ -47,7 +47,13 @@ async def main():
     except Exception as e:
         print("An erroneous asynchronously looped command, as expected, raised:", e)
 
-    print("Idle result", await client.idle().get())
+    i = 0
+    async for subsystem in client.idle():
+        print("Idle change in", subsystem)
+        i += 1
+        if i > 5:
+            print("Enough changes, quitting")
+            break
 
 if __name__ == '__main__':
     asyncio.get_event_loop().run_until_complete(main())
diff --git a/mpd/asyncio.py b/mpd/asyncio.py
index 98c5843..388b889 100644
--- a/mpd/asyncio.py
+++ b/mpd/asyncio.py
@@ -7,11 +7,10 @@ Some commands (eg. listall) additionally support the asynchronous iteration
 (aiter, `async for`) interface; using it allows the library user to obtain
 items of result as soon as they arrive.
 
-The .idle() method works as expected, but there .noidle() method is not
-implemented pending a notifying (and automatically idling on demand) interface.
-The asynchronous .idle() method is thus only suitable for clients which only
-want to send commands after an idle returned (eg. current song notification
-pushers).
+The .idle() method works differently here: It is an asynchronous iterator that
+produces a list of changed subsystems whenever a new one is available. The
+MPDClient object automatically switches in and out of idle mode depending on
+which subsystems there is currently interest in.
 
 Command lists are currently not supported.
 
@@ -117,7 +116,7 @@ class MPDClient(MPDClientBase):
 
         self.__commandqueue = asyncio.Queue(loop=loop)
         self.__idle_results = asyncio.Queue(loop=loop) #: a queue of CommandResult("idle") futures
-        self.__idle_events = asyncio.Queue(loop=loop) #: a temporary dispatch mechanism, fed with subsystem strings
+        self.__idle_consumers = [] #: list of (subsystem-list, callbacks) tuples
 
         try:
             helloline = await asyncio.wait_for(self.__readline(), timeout=5)
@@ -138,7 +137,26 @@ class MPDClient(MPDClientBase):
         self.__rfile = self.__wfile = None
         self.__run_task = self.__idle_task = None
         self.__commandqueue = self.__command_enqueued = None
-        self.__idle_results = self.__idle_events = None
+        self.__idle_results = self.__idle_consumers = None
+
+    def _get_idle_interests(self):
+        """Accumulate a set of interests from the current __idle_consumers.
+        Returns the union of their subscribed subjects, [] if at least one of
+        them is the empty catch-all set, or None if there are no interests at
+        all."""
+
+        if not self.__idle_consumers:
+            return None
+        if any(len(s) == 0 for (s, c) in self.__idle_consumers):
+            return []
+        return set.union(*(set(s) for (s, c) in self.__idle_consumers))
+
+    def _nudge_idle(self):
+        """If the main task is currently idling, make it leave idle and process
+        the next command (if one is present) or just restart idle"""
+
+        if self.__command_enqueued is not None and not self.__command_enqueued.done():
+            self.__command_enqueued.set_result(None)
 
     async def __run(self):
         result = None
@@ -156,7 +174,13 @@ class MPDClient(MPDClientBase):
                     # in this case is intended, and is just what asyncio.Queue
                     # suggests for "get with timeout".
 
-                    result = CommandResult("idle", [], self._parse_list)
+                    subsystems = self._get_idle_interests()
+                    if subsystems is None:
+                        # the presumably most quiet subsystem -- in this case,
+                        # idle is only used to keep the connection alive
+                        subsystems = ["database"]
+
+                    result = CommandResult("idle", subsystems, self._parse_list)
                     self.__idle_results.put_nowait(result)
 
                     self.__command_enqueued = asyncio.Future()
@@ -203,9 +227,12 @@ class MPDClient(MPDClientBase):
         # unhandled task exception and that's probably the best we can do
         while True:
             result = await self.__idle_results.get()
-            idle_changes = await result
-            for change in idle_changes:
-                self.__idle_events.put_nowait(change)
+            idle_changes = list(await result)
+            if not idle_changes:
+                continue
+            for subsystems, callback in self.__idle_consumers:
+                if not subsystems or any(s in subsystems for s in idle_changes):
+                    callback(idle_changes)
 
     # helper methods
 
@@ -326,8 +353,7 @@ class MPDClient(MPDClientBase):
             if self.__run_task is None:
                 raise ConnectionError("Can not send command to disconnected client")
             self.__commandqueue.put_nowait(result)
-            if self.__command_enqueued is not None and not self.__command_enqueued.done():
-                self.__command_enqueued.set_result(None)
+            self._nudge_idle()
             return result
         escaped_name = name.replace(" ", "_")
         f.__name__ = escaped_name
@@ -335,9 +361,18 @@ class MPDClient(MPDClientBase):
 
     # commands that just work differently
 
-    def idle(self, subsystems=[]):
-        # FIXME this is not the final interface
-        return self.__idle_events
+    async def idle(self, subsystems=()):
+        interests_before = self._get_idle_interests()
+        changes = asyncio.Queue()
+        try:
+            entry = (subsystems, changes.put_nowait)
+            self.__idle_consumers.append(entry)
+            if self._get_idle_interests != interests_before:
+                self._nudge_idle()
+            while True:
+                yield await changes.get()
+        finally:
+            self.__idle_consumers.remove(entry)
 
     def noidle(self):
         raise AttributeError("noidle is not supported / required in mpd.asyncio")

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-mpd/python-mpd.git



More information about the Pkg-mpd-commits mailing list