beeware / toga

A Python native, OS native GUI toolkit.
https://toga.readthedocs.io/en/latest/
BSD 3-Clause "New" or "Revised" License
4.36k stars 674 forks source link

Background workers aren't cleaned up on Android #1542

Closed freakboy3742 closed 2 years ago

freakboy3742 commented 2 years ago

Reported via #1538.

When a background worker (possibly any async handler?) is invoked, it doesn't appear to be fully cleaned up; as a result, a proliferation of no-op handler operations are performed. This can be observed in the logs as a steady increase in the number of "Native Invocation: run/Native Invocation: done" messages; over time, it leads to degradation in the performance of the app.

To Reproduce Steps to reproduce the behavior:

  1. Add a minimal background worker example to an Android app
    
    async def update_gui(self, widget, **kwargs):
    while True:
        await asyncio.sleep(1)

def startup(self): self.add_background_task(self.update_gui)


2. Every time the app "ticks", a the number of "Native Invocation" log messages output to the console increases by one. If the app is left running, it can lead to a major slowdown in the app.

**Expected behavior**

The number of log messages per tick should be constant (1).

**Environment:**
 - Operating System: Android
 - Python version: Any
 - Software versions:
   - Toga: 0.3.0.dev35
mhsmith commented 2 years ago

I can also reproduce this with the Toga handlers example, which calls add_background_task on startup.

mhsmith commented 2 years ago

This was a bug in android_events.py. Every time call_soon is called, it adds a Runnable to the Android event queue, which repeatedly re-enqueues itself when it runs. However, call_soon is also called internally by the event loop base class as part of processing the coroutine. So this caused each iteration of the couroutine to accumulate one additional Runnable. The actual coroutine was still only executed once: all the other Runnables would find that no tasks were due.

Fixed in the Chaquopy template PR by making the event loop cancel its existing Runnable (if any) before queuing a new one.

freakboy3742 commented 2 years ago

Fixed by beeware/rubicon-java#76