Improve async queue work
This is something I've been contemplating since before the 41.0 release and something which I want fixed asap and before the 41.1 release (especially the flatpak), to provide an optimal first use experience.
This is a bit of a braindump to clear my thoughts.
Intro
In quite late stages of the 41 cycle I added several async related fixes. I made several blocking functions async and added a queue to lighten the workload. This did great things for Musics responsiveness especially on startup, when Musically literally tries to load almost everything at once, via Grilo, Tracker, MediaArt and GStreamer while doing simultaneously lookups for art not available locally. This should also help reports of too much open file handles, which were frequent on large collections as we limit the amount of concurrent tasks reading/writing files.
Around the same time @jfelder's work on adding artist art to ArtistsView
got merged, which also lead to lookup of all artist art as well. This loading of artist art is in practice quite slow and seems to be heavy on Grilo/Tracker (besides being blocking in Grilo (grilo-plugins!117 (merged))).
Also there seems to be a general slowness around loading playlists, which could use some further investigation. I am not completely sure if it is not just another expression of the async madness.
Problem
All of this together results in long running tasks hogging the AsyncQueue
after a short while in my experience. Which is suboptimal as Music is responsive in feel (no UI blocking), but not in action: it takes a long time to load certain elements like art, but also opening of new albums, artists, searches, et cetera on startup.
Solutions
I prefer to use a minimal amount of additional workarounds to achieve the goal of a quick loading and responsive UI, it should not overcomplicate the whole loading process.
PriorityPool
Use a priority pool as part of the AsyncQueue
, which can be triggered by the widgets when they start loading a specific item. eg. when loading an AlbumWidget
, set the CoreAlbum
in the PriorityPool
. This is checked when the next AsyncQueue
dispatch is called and then a special pool is kept open for priority tasks.
Of course these priority tasks may turn out to be long running as well, so if they take too long pass them to the default active_queue (this may end up bigger than _max_sync
, but that is probably not a big problem as no other tasks should get added to the pool until it falls below the treshhold again).
Adaptive queue size
For non-I/O tasks it should be possible to enlarge/shrink the size of the queue while they get handled fast enough. eg. if the active pool is always (partially) empty the next dispatch the pool size could be increased (and shrunk vice-versa). Up to an upper/lower limit.
For I/O tasks it should be set to a sensible default (the current _max_size
is probably a bit low), but not too much as it might open us again to too much open files issues.
If we could figure out if a task is blocked on local I/O, we could probably be smarter, but currently I see no way to do this and would need another rework of how we handle art.
Cancelling long-running tasks and re-queue them later
This is something that might be possible, but looks quite involved to me. If we encounter long running tasks in the default async queue, cancel them and add them to a backup queue.