Coder Social home page Coder Social logo

Bug: possible memory leak about coreaudio HOT 11 OPEN

morphx666 avatar morphx666 commented on July 23, 2024
Bug: possible memory leak

from coreaudio.

Comments (11)

morphx666 avatar morphx666 commented on July 23, 2024 2

Consider this code:

internal class Program {
    private static void Main(string[] args) {
        Task.Run(async () => {
            while(true) {
                await Task.Delay(10);
            }
        }).Wait();
    }
}

I just asked CoPilot why this code is leaking memory... the answer is just what one would've expected:

The code you've provided is a simple infinite loop that waits for 10 milliseconds on each iteration. It doesn't seem to be leaking memory directly, as it doesn't allocate any unmanaged resources or create any objects that aren't being cleaned up.

However, if this code is part of a larger program, it could potentially contribute to a memory leak if it's preventing other tasks from completing or if it's keeping some objects alive longer than necessary.

For example, if this loop is running on the main thread and there are other tasks that are waiting to run on the main thread, those tasks won't be able to complete until this loop finishes. If those tasks are holding onto some resources, those resources won't be freed until the tasks complete, which could lead to a memory leak.

Also, if there are any local variables in the Main method that are being captured by the lambda expression, those variables will be kept alive for as long as the loop is running, which could also lead to a memory leak.

Without more context, it's hard to say for sure why your program might be leaking memory. You might want to use a memory profiler to get a better idea of what's going on.

However, it got me thinking: what if we are blocking some internal dotnet process?
After all, the loop lives inside the main program thread/loop.

So, changing the code slightly, running the infinite loop on a separate thread seems to solve the problem:

internal class Program {
    private static void Main(string[] args) {
        Thread t = new(() => {
            while(true) {
                Thread.Sleep(10);
            }
        });
        t.Start();
    }
}

Another thing is that running this in VS still presents the apparent memory leak. But running it with dotnet run -c Release doesn't, so I guess VS is also to blame for this behavior.

from coreaudio.

morphx666 avatar morphx666 commented on July 23, 2024 1

At first glance, your code looks fine... I will run it locally to see if I can figure out what's going on.

from coreaudio.

morphx666 avatar morphx666 commented on July 23, 2024

Yes, continuously calling enumerator.EnumerateAudioEndPoints will cause memory consumption to go up. Not sure if it's a problem with the library or the native code itself.

However, it is not recommended to do so. Instead, in order to keep an updated list of devices you should use MMNotificationClient and listen for notifications (events).

Something like this:

using CoreAudio;

internal class Program {
    private static void Main(string[] args) {
        var devices = new List<MMDevice>();
        var enumerator = new MMDeviceEnumerator(Guid.Empty);

        foreach(var device in enumerator.EnumerateAudioEndPoints(DataFlow.All,DeviceState.Active)) {
            devices.Add(device);
        }

        var nc = new MMNotificationClient(enumerator);
        nc.DeviceAdded += (sender, e) => {
            lock(devices) {
                MMDevice device = enumerator.GetDevice(e.DeviceId);
                devices.Add(device);
            }
        };
        nc.DeviceRemoved += (sender, e) => {
            lock(devices) {
                MMDevice device = enumerator.GetDevice(e.DeviceId);
                devices.Remove(device);
            }
        };
        nc.DeviceStateChanged += (sender, e) => {
            // What to do here is really up to you...
            lock(devices) {
                MMDevice device = enumerator.GetDevice(e.DeviceId);
                if((e.DeviceState & DeviceState.Active) == DeviceState.Active) {
                    devices.Add(device);
                } else {
                    devices.Remove(device);
                }
            }
        };

        Task.Run(async () => {
            while(true) {
                lock(devices) {
                    foreach(var device in devices) {
                        var deviceName = device.DeviceFriendlyName;

                        // Not required because we never called GetAudioEndpointVolume or GetAudioSessionManager2
                        //device.Dispose(); 

                        // Do something with the device's name...
                    }
                }

                await Task.Delay(10);
            }
        }).Wait();
    }
}

Please avoid using lock the way I did. Use a thread-safe collection instead.

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

Thank very much for a quick answer!
I really like the proposed solution with events, didn't know it can be done this (cleaner) way.
I'll play with it, this library is one currently used in the project I'm maintaining so I'd be really glad to keep it.

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

Is session handling supposed to be done in similar fashion - OnSessionCreated? I've created a simple example based on the code above that iterates the sessions and I can still see memory usage creeping up slowly despite disposing "everything".
Code (put instead of the Task from your example above):

        while (true)
        {
            lock (devices)
            {
                foreach (var device in devices)
                {
                    var deviceName = device.DeviceFriendlyName;
                    var sessionManager = device.AudioSessionManager2;
                    if(sessionManager == null)
                        continue;

                    var sessions = sessionManager.Sessions;
                    if (sessions == null)
                        continue;

                    foreach (var session in sessions)
                    {
                        var s = session;
                        s.Dispose();
                    }

                    sessionManager.Dispose();
                    device.Dispose();
                }
            }
        }

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

I haven't confirmed that yet but it looks like even the basic setup with events leak a bit of memory?
Not certain at this point, I'll let it run for a few hours and report back.

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

I'll be honest, I'm confused at this point :D
Code:

internal class Program
{
    private static ConcurrentDictionary<string, MMDevice> _devices = [];


    private static void AddDevice(MMDevice device)
    {
        Console.WriteLine($"adding device: {device.DeviceFriendlyName}");
        _devices[device.DeviceFriendlyName] = device;
    }

    private static void RemoveDevice(MMDevice device)
    {
        Console.WriteLine($"removing device: {device.DeviceFriendlyName}");
        _devices.Remove(device.DeviceFriendlyName, out var removedDevice);
        removedDevice?.Dispose();
        device?.Dispose();
    }


    static void Main(string[] args)
    {
        var enumerator = new MMDeviceEnumerator(Guid.Empty);

        foreach (var device in enumerator.EnumerateAudioEndPoints(DataFlow.All, DeviceState.Active))
        {
            AddDevice(device);
        }

        var nc = new MMNotificationClient(enumerator);
        nc.DeviceAdded += (sender, e) =>
        {
            var device = enumerator.GetDevice(e.DeviceId);
            AddDevice(device);
        };
        nc.DeviceRemoved += (sender, e) =>
        {
            var device = enumerator.GetDevice(e.DeviceId);
            RemoveDevice(device);
        };
        nc.DeviceStateChanged += (sender, e) =>
        {
            var device = enumerator.GetDevice(e.DeviceId);
            if ((e.DeviceState & DeviceState.Active) == DeviceState.Active)
            {
                AddDevice(device);
            }
            else
            {
                RemoveDevice(device);
            }
        };

        Console.WriteLine("---------------------------------------");
        foreach (var device in _devices.Values)
        {
            Console.WriteLine($"device: {device.DeviceFriendlyName}");
        }
        Console.WriteLine("---------------------------------------");
 

        while (true)
        {

        }
    }
}

Information from VS:
UypX4o
jGNycO
7Ag0YK

Opposing information from Task Manager:
MBjQ8g

from coreaudio.

morphx666 avatar morphx666 commented on July 23, 2024

To be honest, I'm not sure what's going on... but I feel that this appears to be normal behavior.

I followed the instructions found here and couldn't find anything useful.

After all, the app sits there, doing nothing in an infinite loop... there's no chance that the CoreAudio library is leaking memory if it's not being used.

Here's one test you can do... remove the code related to CoreAudio and run the app with just the infinite loop and you will see that, although not as fast, the process memory slowly, but steadily, keeps growing.

image

Maybe someone with a deeper understanding of dotnet's inner workings (I'm thinking of @Elfocrash) could shed some light on this issue...

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

Ohhh, that is more than interesting!
Thank you for your tests and investigation - I'll try that right away.

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

You seem to be quite correct (it started at 12MB):
4OD52h
This kind of behaviour is very interesting and confusing at the same time :D

from coreaudio.

amadeo-alex avatar amadeo-alex commented on July 23, 2024

Your observations seems to be again quite correct + looks like abusing GC a little bit causes the memory to be constant (started at 16):
oSPQua

from coreaudio.

Related Issues (18)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.