microsoft/xlang

Python/WinRT memory leaks

Willy-JL opened this issue ยท 5 comments

I noticed that using windows.media.control can often lead to memory leaks, seemingly uncontrollable from python's side of things.

Example demonstration:

from winrt.windows.media.control import GlobalSystemMediaTransportControlsSessionManager as MediaManager
from pympler.tracker import SummaryTracker
import asyncio
import gc


async def get_media_info():
    # Memory leaking calls
    sessions = await MediaManager.request_async()
    current_session = sessions.get_current_session()
    await current_session.try_get_media_properties_async()

    # Force garbage collect, does nothing
    gc.collect()

    return


if __name__ == '__main__':

    # Pause to check starting memory usage (I used task manager)
    # ~ 30 MB in my case
    input()
    
    # Track objects and sizes
    tracker = SummaryTracker()

    # 500 iterations is a good example amount
    for _ in range(500):
        # Leak memory
        asyncio.run(get_media_info())

        # Force garbage collect, does nothing
        gc.collect()

    # Print info on object count and size differences
    # Will always show ~ 1 MB of leaks, no matter how many iterations
    tracker.print_diff()

    # Pause to check ending memory usage (I used task manager)
    # ~ 60 MB with 500 iterations
    # ~ 330 MB with 5000 iterations
    # ~ 3.2 GB with 50000 iterations
    input()

Both tracemalloc and pympler do not detect these leaks, and python's built in garbage collector gc does not dispose of these items

dlech commented

I did some debugging. After the Python wrapper releases the IAsyncOperation COM object, the COM object still has a ref count of 1, so the COM object is not freed. I haven't been able to figure out where the extra ref count is coming from yet.

dlech commented

After some more digging, we can see that we first get the IAsyncOperation object in this generated function:

template <typename D> WINRT_IMPL_AUTO(winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionMediaProperties>) consume_Windows_Media_Control_IGlobalSystemMediaTransportControlsSession<D>::TryGetMediaPropertiesAsync() const
    {
        void* operation{};
        check_hresult(WINRT_IMPL_SHIM(winrt::Windows::Media::Control::IGlobalSystemMediaTransportControlsSession)->TryGetMediaPropertiesAsync(&operation));
        return winrt::Windows::Foundation::IAsyncOperation<winrt::Windows::Media::Control::GlobalSystemMediaTransportControlsSessionMediaProperties>{ operation, take_ownership_from_abi };
    }

When TryGetMediaPropertiesAsync (from WinRT) returns, operation already has a ref count of 2. Creating the wrapper with take_ownership_from_abi takes ownership of one reference. This coincides with the previous observation of having 1 ref count left when we release the object.

So as far as I can tell, it is not the bindings fault that it is leaking. Maybe someone from Microsoft with knowledge of WinRT internals can enlighten us?

dlech commented

I found a way to fix this. You can find binaries for testing at https://github.com/dlech/xlang/actions/runs/973127446.

This is a huge improvement! From ~300MB leaked with 5000 iterations it's now down to a negligible ~15MB! In about how long can I expect to have this fix available through pip?

This issue is stale because it has been open 10 days with no activity. Remove stale label or comment or this will be closed in 5 days.