(SOLUCIONADO) Fuga de memória en una función :(

Iniciado por Eleкtro, 4 Agosto 2013, 15:01 PM

0 Miembros y 1 Visitante están viendo este tema.

Eleкtro

Tengo una fuga de memória grave en una aplicación, y es debido a una Class de terceros que estoy usando...

Este simple código aloja entre 100-200kb de RAM cada segundo :(

Código (vbnet) [Seleccionar]
Public Class Form1

   Dim isbinded As Boolean = False
   Dim Winamp As clsWACC = New clsWACC
   Dim WithEvents mytimer As New Timer With {.Interval = 50, .Enabled = True}

   Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles mytimer.Tick
       isbinded = Winamp.Bind()
       ' Label1.Text = isbinded
       ' isbinded = Nothing
   End Sub

End Class


Necesito usar un Timer rápido de menos de 50 ms, pero simplemente por curiosidad si aumento el Timer a 1000 ms la ram sigue subiendo sin pausa, aunque sube sólo 8 kb cada segundo.

He intentado examinar y corregir la Class pero no encuentro el fallo, esta es la parte de la Class que manipula el code de arriba:

Código (vbnet) [Seleccionar]
       Public Sub New(ByVal handle As IntPtr, ByVal str As String)
           'opens the process
           hWinamp = OpenProcess(DAccess.PROCESS_ALL_ACCESS, False, waPID)
           'If hWinamp.Equals(IntPtr.Zero) Then
           '    'exception
           'End If

           remStr = str
           remoteBuf = AllocWinamp(handle, Convert.ToUInt32(str.Length + 1))
           Dim localBuf As IntPtr = Marshal.StringToHGlobalAnsi(str)

           WriteProcessMemory(hWinamp, remoteBuf, localBuf, str.Length + 1, Nothing)
           Marshal.FreeHGlobal(localBuf)
       End Sub


Código (vbnet) [Seleccionar]
   Dim path As String = Nothing
   Dim regKey As RegistryKey = Registry.CurrentUser.OpenSubKey("Software\Winamp", False)

  'Binds to WinAmp
   Public Function Bind() As Boolean
       Return Bind("")
   End Function

   Public Function Bind(ByVal PathToExecutable As String) As Boolean
       hWnd_Winamp = WinAPI.FindWindow(lpClassName, Nothing)

       'waProcess = New Process
       waProcess.EnableRaisingEvents = True

       'If WinAmp window handle not found, try to launch it
       If hWnd_Winamp.Equals(IntPtr.Zero) Then

           'if path was not specified, try to find it in the Windows registry
           If PathToExecutable = "" Then
               'Dim path As String
               'regKey = Registry.CurrentUser
               'regKey = regKey.OpenSubKey("Software\Winamp", False)
               If regKey IsNot Nothing Then
                   path = Convert.ToString(regKey.GetValue(""))
                   waProcess.StartInfo.FileName = path & "\Winamp.exe"
                   regKey.Close()
               Else
                   Return False
               End If
           Else
               waProcess.StartInfo.FileName = PathToExecutable
           End If

           Try
               waProcess.Start()
           Catch ex As System.ComponentModel.Win32Exception When ex.ErrorCode = -2147467259
               Debug.WriteLine("Executable not found")
               Return False
           Catch ex As Exception
               Debug.WriteLine("unknown exception")
               Return False
           End Try

           waProcess.WaitForInputIdle()
           hWnd_Winamp = waProcess.MainWindowHandle()
           waPID = waProcess.Id
       Else

           'WinAmp handle found
           'now bind to WinAmp process
           'get PID from hWnd

           WinAPI.GetWindowThreadProcessId(hWnd_Winamp, waPID)
           If waPID = 0 Then
               Return False
           End If

           waProcess = Process.GetProcessById(waPID)
           waProcess.EnableRaisingEvents = True

           hWnd_Playlist = GetHWND(WinampWindow.Playlist)
           hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
           hWnd_Video = GetHWND(WinampWindow.Video)
           Return True

       End If

       'if hWnd of the main window is still zero,
       'it was not possible to launch or bind to WinAmp
       If hWnd_Winamp.Equals(IntPtr.Zero) Then
           Return False
       Else
           hWnd_Playlist = GetHWND(WinampWindow.Playlist)
           hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
           hWnd_Video = GetHWND(WinampWindow.Video)
           Return True
       End If
   End Function


Pero la primera condición no se cumple, es decir, solo se manipula esta parte de la función Bind:

Código (vbnet) [Seleccionar]

       If hWnd_Winamp.Equals(IntPtr.Zero) Then
            ' NADA
       Else

           'WinAmp handle found
           'now bind to WinAmp process
           'get PID from hWnd

           WinAPI.GetWindowThreadProcessId(hWnd_Winamp, waPID)
           If waPID = 0 Then
               Return False
           End If

           waProcess = Process.GetProcessById(waPID)
           waProcess.EnableRaisingEvents = True

           hWnd_Playlist = GetHWND(WinampWindow.Playlist)
           hWnd_Equalizer = GetHWND(WinampWindow.Equalizer)
           hWnd_Video = GetHWND(WinampWindow.Video)
           Return True

       End If


hWnd_Winamp es un IntPtr
waProcess es un Process (el cual he probado a liberarlo pero sigue pasando lo mismo...)
waPID es un Integer
hWnd_Playlist es un IntPtr
hWnd_Equalizer es un IntPtr
hWnd_Video es un IntPtr

...y GetWindowThreadProcessId es esta función:

Código (vbnet) [Seleccionar]
       '========================                        =========================
       '======================  GetWindowThreadProcessId  =======================
       '========================                        =========================
       'Retrieves the identifier of the thread that created the specified window
       'and, optionally, the identifier of the process that created the window.
       <DllImport("user32.dll", SetLastError:=True)> _
       Public Shared Function GetWindowThreadProcessId( _
                                       ByVal hwnd As IntPtr, _
                                       ByRef lpdwProcessId As Integer) As Int32
       End Function



y esta la función GetHWND:
Código (vbnet) [Seleccionar]
   Private Shared Function GetHWND(ByVal Window As WinampWindow) As IntPtr
       Return SendWA_IPC(Window, Message.IPC_GETWND)
   End Function

   Private Shared Function SendWA_IPC(ByVal param As Int64, ByVal MessageName As Message) As IntPtr
       Return WinAPI.SendMessage(hWnd_Winamp, WinAPI.Msg.WM_USER, IntPtr.op_Explicit(param), IntPtr.op_Explicit(MessageName))
   End Function

       '===============================           ===============================
       '=============================  SendMessage  =============================
       '===============================           ===============================
       'Sends the specified message to a window or windows. It calls the window
       'procedure for the specified window and does not return until the window
       'procedure has processed the message.
       <DllImport("user32.dll", SetLastError:=True, CharSet:=CharSet.Auto)> _
       Public Shared Function SendMessage(ByVal hWnd As IntPtr, _
                                          ByVal Msg As Msg, _
                                          ByVal wParam As IntPtr, _
                                          ByVal lParam As IntPtr) As IntPtr
       End Function



¿Alguna sugerencia?

Gracias...








Novlucker

Hay algo que no entiendo y es ... ¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso, ¿no alcanza con obtenerlo una vez?

Saludos
Contribuye con la limpieza del foro, reporta los "casos perdidos" a un MOD XD

"Hay dos cosas infinitas: el Universo y la estupidez  humana. Y de la primera no estoy muy seguro."
Albert Einstein

Eleкtro

#2
Cita de: Novlucker en  7 Agosto 2013, 17:17 PM
Hay algo que no entiendo y es ... ¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso

La intención de repetirlo cada 50 ms es para detectar cuando el Winamp está en modo "Pausa" o en "Detenido", o para detectar si el proceso sigue activo o si ya no se está ejecutando el Winamp, si se te ocurre una forma más eficiente plz dímelo.

EDITO:


Para que entiendas lo que hago, tengo algo así:

Código (vbnet) [Seleccionar]
Dim WinampIsRunning = winamp.bind() ' Esto devuelve True si el proceso de Winamp se está ejecutando

If WinampIsRunning then

   if winamp.state = winamp.state.Paused then...
   ' actualizar posición del trackbar, labels, textboxes, etc...

   elseif winamp.state = winamp.state.Paused then...

   elseif winamp.state = winamp.state.Stoped then...

end if


PD: ¿Entonces tu tampoco encuentras el problema en el code de arriba?

Saludos








Novlucker

¿No hay un evento en esa clase para detectar el cambio de estado?

En cuanto al proceso por ejemplo. Lo obtienes todas las veces y seteas EnableRaisingEvents en true, lo que hay que hacer es obtener el proceso, setear EnableRaisingEvents en true, suscribirse al evento Exited, y dejar de buscar el proceso. Cuando se dispara el evento Exited, entonces volvemos a buscar.

Saludos
Contribuye con la limpieza del foro, reporta los "casos perdidos" a un MOD XD

"Hay dos cosas infinitas: el Universo y la estupidez  humana. Y de la primera no estoy muy seguro."
Albert Einstein

Eleкtro

Cita de: Novlucker en  7 Agosto 2013, 17:17 PM¿Por que ese necesario buscar cada 50 ms la ventana y el proceso de Winamp? Está claro que es algo muy costoso

Aún así ese trozo de código de la Class genera memoria sin cesar, aunque se use un Timer que tickee cada 10 segundos que llame a la función Bind, la cantidad de memoria en ese caso será minima pero al fin y al cabo incesante, hay algo que está mal en ese code.

Cita de: Novlucker en  7 Agosto 2013, 17:55 PM¿No hay un evento en esa clase para detectar el cambio de estado?

En cuanto al proceso por ejemplo. Lo obtienes todas las veces y seteas EnableRaisingEvents en true, lo que hay que hacer es obtener el proceso, setear EnableRaisingEvents en true, suscribirse al evento Exited, y dejar de buscar el proceso. Cuando se dispara el evento Exited, entonces volvemos a buscar.

la Class del Winamp son 2000 o 3000 lineas de código, no me lo he mirado todo, eso es lo próximo que haré y ya te cuento.








Novlucker

Pero en principio te puedes fijar si tiene un evento, solo es poner punto en el nombre de instancia de clase y buscar el que tiene un "rayito" :xD

Saludos
Contribuye con la limpieza del foro, reporta los "casos perdidos" a un MOD XD

"Hay dos cosas infinitas: el Universo y la estupidez  humana. Y de la primera no estoy muy seguro."
Albert Einstein

Eleкtro

#6
Cita de: Novlucker en  7 Agosto 2013, 19:39 PMPero en principio te puedes fijar si tiene un evento, solo es poner punto en el nombre de instancia de clase y buscar el que tiene un "rayito" :xD
Si pero si el evento fuese privado por cualquier motivo pues no me iba a salir en el intellisense, es que en la class hay muchos "Tests" como cosas de prueba y cosas por mejorar, no está del todo perfeccionado así que tenía que usar el buscador de la IDE para examinar la class a fondo xD.


Citar¿No hay un evento en esa clase para detectar el cambio de estado?
No, ninguno, hay que crear el evento o detectar el estado a lo cutre, mediante condiciones (if's).


En fín Novlucker he seguido tus indicaciones para lo del proceso:

En la class del winamp:
Código (vbnet) [Seleccionar]
   Private Sub waProcess_Exited(ByVal sender As Object, ByVal e As EventArgs) Handles waProcess.Exited
       RaiseEvent WinampExited()
   End Sub


En la class de mi form:
Código (vbnet) [Seleccionar]
   Private Sub Exited() Handles Winamp.WinampExited
       Winamp_IsRunning = False
   End Sub

   Private Sub Monitor_Timer_Tick(sender As Object, e As EventArgs) Handles Monitor_Timer.Tick
       If Not Winamp_IsRunning Then Winamp_IsRunning = Winamp.Bind()
   End sub


Se nota mucho el cambio, gracias, aunque como ya digo la función Bind de esa class tiene algún problema así que por mucho que yo intente perfeccionar estas minucias no va a dejar de subir la RAM xD, aunque ahora he conseguido que suba reálmente muy poco a poco, el consumo de ram hace subidas y bajadas que no me gustan nada, pero bueno parece estar equilibrado, sube un poco y al rato como que el GC hace su trabajo, lo libera y vuelve a bajar el consumo, y vuelve a subir, en fin xD. (Todo esto dejando el proceso en "StandBy")

Saludos








Novlucker

Yo creo que el problema está no en la función, sino en ejecutar esa función constantemente tan rápido. Se crean más datos de los que se alcanzan a liberar.

Te pongo un ejemplo,
Código (csharp) [Seleccionar]
using System;
using System.Diagnostics;
using System.Timers;

namespace ConsoleApplication6
{
    class Program
    {
        static void Main(string[] args)
        {
            Timer timer = new Timer(50); //Para una aplicacion de consola toca usar este Timer
            //http://msdn.microsoft.com/en-us/magazine/cc164015.aspx
            timer.Elapsed += new ElapsedEventHandler(Run);
            timer.Start();
            Console.ReadLine();
        }

        static void Run(object sender, EventArgs e)
        {
            Console.WriteLine(DateTime.Now);
            for (int i = 0; i < 2000; i++)
            {
                Process.GetProcesses();
            }
        }
    }
}


El código es muy simple, pero si lo ejecutas verás como la RAM va aumentando progresivamente :P

Saludos
Contribuye con la limpieza del foro, reporta los "casos perdidos" a un MOD XD

"Hay dos cosas infinitas: el Universo y la estupidez  humana. Y de la primera no estoy muy seguro."
Albert Einstein

Eleкtro

#8
Cita de: Novlucker en  7 Agosto 2013, 20:31 PMYo creo que el problema está no en la función, sino en ejecutar esa función constantemente tan rápido. Se crean más datos de los que se alcanzan a liberar.

Lo que dices es muy lógico, es lo primero que se llega a pensar, puede que si que quizás en 50 ms se generen más datos de los que se pueden liberar, pero además de eso estoy seguro de que hay una fuga en esa función, y la prueba definitiva la di al principio, con este code:


Código (vbnet) [Seleccionar]
   Public Class Form1
   
      Dim isbinded As Boolean = False
      Dim Winamp As clsWACC = New clsWACC
      Dim WithEvents mytimer As New Timer With {.Interval = 50, .Enabled = True}
   
      Private Sub Timer1_Tick(sender As Object, e As EventArgs) Handles mytimer.Tick
          isbinded = Winamp.Bind()
          ' Label1.Text = isbinded
          ' isbinded = Nothing
      End Sub
   
   End Class


Símplemente el Test consiste en crear una APP con ese código (y adjuntar la dll de la función Bind claro xD).

Repito que si uso un Timer de 10 segundos (o 20, o los que sean) la RAM sigue subiendo progresívamente, solo que se generarán los bytes mucho más lento porque el timer es más lento así que hay que dejar la app un buen rato corriendo para apreciar el aumentado del consumo de RAM, pero en fín yo creo que en 10-20 segundos el GC tiene tiempo de liberar...

Saludos!








Eleкtro

Bueno creo que puedo dar este tema por solucionado...
me ha costado mucho tiempo corregir cada parte del código de la aplicación que tenían fugas de memória pero al final he estabilizado el consumo y ya no aumenta progresívamente, de los 12 MB no pasa nunca el consumo ahora,
ya puedo dejar de colectar manuálmente con el GC y de usar API's para flushear el consumo de memória de la APP xD

Gracias por tu tiempo Novlucker.


Lo único que me gustaría mejorar sobre el tema de la memória es lo que comento aquí:     Tema: Experimento de consumo de memória... ver para creer!  (Leído 58 veces)


Saludos!