locked
Garbage collection conflicts with USB isochronous receiving thread?

    Question

  • I restart here a thread I opened with different ideas about the problem.
    In my application I have a thread performing USB data reception in isocronous mode, employing Usbsamp driver.
    Everything works fine as I simply store samples to HD. Problems arise if I try to receive data and I try to perform a periodical graphical action whatsoever.
    If the graphical action is performed without reception, I see memory usage decrease and increase periodically, I guess with a correct intervention of the garbage collector. If I use GC.Collect the memory usage remains constant.
    If I have simultaneously reception and graphics, memory usage increases until the application hangs; if I use GC.Collect the application hangs immediately.
    Apparently my reception thread blocks the garbage collector.
    Isochronous mode is very demanding in band allocation and can't wait for other tasks(though in my case the bandwidth requested - 50kBytes/sec- looks quite normal).
    Could it create problems with garbage collector?
    Tuesday, January 13, 2009 7:09 PM

Answers

  • Eureka (I found It).
    Apparently my problem was using WaitForSingleObject in a thread performing graphical operations.
    The trick was employing MsgWaitForMultipleObjects with bWaitAll=false and dwWakeMask=QS_PAINT.
    The program I had adapted performed I/O and not graphical operations; my program, I guess, messes with the WM_PAINT message and needs this modification.
    I never found such a malicious bug.
    But is this the real solution?
    Anyway now the program works.
    • Marked as answer by stefanopod Friday, May 15, 2009 6:13 AM
    Friday, May 15, 2009 6:13 AM

All replies

  • Garbage collection will work incorrectly if you have many small managed objects that hold references to large unmanaged blocks of memory. If this is your problem, then the small managed wrappers need to use GC.AddMemoryPressure and its inverse.

            -Steve
    Wednesday, January 14, 2009 2:38 AM
  • I'm afraid that this isn't my case; my receiving thread (array version) doesn't allocate memory or in the collection version simply adds a byte to a collection (periodically resetting it).
    The main thread graphical routine doesn't bother with unmanaged memory; simply allocates graphical items (that shouldn't have problems in disallocation, and allocates a graphics object whose dispose is regularly called. And all behaves in a normal way as long as reception isn't activated.
    Thanks anyway; i'll try to dig more in depth, in the direction of your tip.
    Wednesday, January 14, 2009 5:28 PM
  • I have patched the problem of memory leak, calling periodically GC.Collect().

    Now reception and plotting can last as long as I want.

    But this calling of GC.Collect() inhibits some features of my libraries, for example the use of a data-binding in a textbox, or the resizing of the components on the resizing of the form.

    I can't see why Garbage collector doesn't work other than called manually,and I can't see why its calling inhibits the working of libraries otherwise  working regularily.

    It's disappointing to have problems on the issue of memory disallocation.

    C# hallmark is to make life easy about this point.

    It' s very hard for me to track this kind of bug and its effects are erratic.

    And my application is a translation of a CBuilder version that worked without  problems.

    Sunday, January 18, 2009 4:53 PM
  • Using task manager, my application is reported to have a lot of paging errors.
    Could it be a clue?
    Sunday, January 18, 2009 5:45 PM
  • Garbage collection will work fine. It is not aware of unmanaged memory, so you need to handle that with AddMemoryPressure and friends. GC can also be disabled using GCHandle, so check anything that uses it.

    Regardless, GC.Collect cannot affect data binding or resizing. There must be some other problem.

    I also come from a C++ Builder background; the visual designers are similar, but keep in mind that managed code is a very different platform.

           -Steve

    P.S. Paging errors only indicate your app uses a lot of memory at once; it doesn't necessarily mean that the app has memory leaks.

    Sunday, January 18, 2009 8:40 PM
  • I'm aware tha GC.Collect cannot affect data binding or resizing.

    It's a fact that *without* GC.Collect my components featuring data binding and resizing of child components never had problems; *with* GC.Collect the program hangs immediately.

    In this particular case *without* GC.Collect  my application hangs after some minutes.

    *with* GC.Collect and without data binding and resizing doesn't hang at all.

    I agree that the problem is elsewhere, but where?

    I haven't clear ideas about "unmanaged memory".

    The receiving thread uses periodically
     
    WriteFile
    WaitForSingleObject 
    GetOverlappedResult

    with IntPtr arguments to perform asynchronous I/O.

    For example, I have the function

     

    [DllImport("kernel32.dll", SetLastError = true)]

    public static extern Boolean WriteFile(IntPtr hFile, ref byte lpBuffer, Int32 nNumberOfBytesToWrite, out Int32 lpNumberOfBytesWritten, ref OVERLAPPED lpOverlapped);


    and the call

    bool success=WriteFile(pStrInf.PipeHandle,ref pStreamBuffInf.buffer[0],bufSize,out BytesTransf,ref pStreamBuffInf.ov);


    Has this something to do with the  "unmanaged memory" issue you are referring to?

    Anyway,in my opinion  these memory chunks are allocated only once at startup and wouldn't be culprit of the memory leak.
    Monday, January 19, 2009 8:04 AM
  • You dont need to be worried about calling GC.Collect() manually. What GC.Collect() can do will automatically be done by the CLR when there is a reall need to do that.

    Best Regards,

    Rizwan
    WPF Learning Series @ http://geekswithblogs.net/RizwanSharp :: Microsoft MVP - Visual Developer, C#
    Monday, January 19, 2009 1:33 PM
  •  But unfortunately Garbage Collector apparently isn't called, memory employed gets larger and larger and program hangs.
    Calling it manually memory is steady, program doesn't hang, but I have annoying side effects.
    If it were as you say my thread wouldn't have existed.
    I'm looking for the way to have GC working properly.
    Obviously GC isn't the culprit, my application is.
    But what of its thousands of lines?
    Monday, January 19, 2009 5:43 PM
  • Well, I can see one thing horribly wrong: "ref byte lpBuffer". This isn't C. If you're not marshalling it as a parameter-sized array, then you need to pass a pointer, and manually pin the respective buffer. IntPtr also isn't recommended for file handles - use SafeHandle instead.

    Actually, I recommend you scrap all that part (since there are multiple problems just in these few lines of interop code), and just use FileStream, which does support overlapped I/O. Either use a FileStream constructor that takes a FileOptions parameter, passing FileOptions.Asynchronous; or use the FileStream constructor that takes the "bool useAsync" parameter. This will actually open the file for asynchronous access. If you don't do this, then the FileStream will just fake the asynchronous access using ThreadPool, but the underlying file I/O will be synchronous.

           -Steve
    Monday, January 19, 2009 11:58 PM
  • As you well understand I am a C# newbie, and when translating to C# the CBuilder APIs I was often glad of seeing it compile ond of seeing it sucessfully transmiting  bytes, without understanding in depth what was I doing.
    Of course side effects could be bad.
    Thanks for your tips, I'll try to look better into all the stuff, that I find rather difficult also as a CBuilder programmer.
    Tuesday, January 20, 2009 6:54 AM
  • I copyied the horrible lines

    [ DllImport( "kernel32.dll", SetLastError=true ) ]

    public static extern Boolean WriteFile( SafeFileHandle hFile, ref Byte lpBuffer, Int32 nNumberOfBytesToWrite, ref Int32 lpNumberOfBytesWritten, Int32 lpOverlapped );

    from the site of the USB-guru  Jan Axelson.

    As you say, here I find SafeFileHandle in place of IntPtr, but the "not C"  ref Byte lpBuffer is there.

    If I remember well, all the asynchronous I/O stuff  comes from that site.

    Ignorant people like me need an authority to believe in.

    I'll try to understand your suggestions (a bit too concise for my limited knowledge), but I'm not so confident in reinventing everything of my own.

    Tuesday, January 20, 2009 7:24 AM
  • I'll try to analize your suggestion one by one.

     "just use FileStream"

    I  employ WriteFile having as a parameter a handle created with the CreateFile API, that
    has as a parameter the path to the communication pipe.

    How could I scrap all that part and use FileStream instead, I can't understand.

    I'm not writing on a file whatsoever that I can declare and instantiate as I want.

    Rewriting from scratch all the procedure for opening a communication pipe and for accessing it (async or sync) is a desperate idea.

    And the procedure I employ works fine both in recording and in playing, transmitting bytes up and down the USB isochronous pipe.

    Probably, I would say *for sure*, this code is horribly wrong , but the side effects of this issue are rather subtle.

    Tuesday, January 20, 2009 7:10 PM
  • Oh - pipes! In that case, I recommend NamedPipeServerStream and NamedPipeClientStream. Although FileStream can be used for pipes as well (passing in the handle from CreateFile or just the pipe name).

    Sorry I don't have time to go into more detail. See if the .NET pipe classes will work for you.

           -Steve
    Wednesday, January 21, 2009 8:58 PM
  • Thanks a lot; I'll search in this direction.
    Thursday, January 22, 2009 7:11 AM
  • I tried to eliminate some problems of my application.

    I removed IntPtr and used SafeFileHandle;

    I modified the call to WriteFile that appeared horrible:

     (I had remarked in the past - strange enough to me - that calling WriteFile or calling ReadFile over my USB Pipe had the identical result,  the only important thing is setting correctly the pipe direction when opening it),

    so I adapted the code suggested in MSDN to call ReadFile as unsafe function employing the "fixed" syntax.

    But all these modification didn't change at all the weird behaviour of the application, with the strange correlation between graphics, pipes, garbage collection and other features of the application.

    the correlation is still this:

     

    graphics         GC.Collect      DataBinding & Resizing

        N                X                                     X                     works
        Y                N                                     X                     memory leak
        Y                Y                                     N                     works
        Y                Y                                     Y                     hangs immediately

     

    Next modification I have to try: employing FileStream or NamedPipeServerStream and NamedPipeClientStream instead of the API ReadFile.

    But I have problems in attaching to asyncronous I/O the ovelapped events of my application : my streaming is performed over at least 2 buffers,  and each of them has its ovelapped event.

     I would appreciate any help in retrieving an example about this issue.

    MSDN documentation about OVERLAPPED class apparently isn't very rich.

     

     

    Thursday, January 29, 2009 6:40 PM
  • At last, after an annoying work,  I could isolate better the problems that bring my application to collapse.
    I abandoned the idea of calling manually Garbage Collector (problems appeared here and there in varoius parts of my application, but I think it was a problem of memory corruption and the suspected code was quite innocent).
    So, removing bit by bit parts of my application I could arrive to this point: my receiving thead works fine in itself, I could store to HD gigabites of data, but if I add a timer with a handler whatsoever, even completely empty, the application hangs after a couple of minutes.
    Now I have to detect what, in my receiving thread, makes impossible the parallel employ of a simple timer.
    I'll try to simplify as I can the receiving thread to find where the problem is.
    Meanwhile, if anybody has an idea about the incompatibility between  a thread (working fine if alone), and a timer that is doing nothing,  hints will be greatly appreciated.
    Tuesday, April 14, 2009 7:36 PM
  • I think your problem is to do with passing a pointer to a managed buffer to a P/Invoked API function which retains that pointer for later use. Then if the managed buffer is moved in memory (for example, as a side effect of a garbage collection) then the pointer retained by the API function will be pointing to invalid memory, and the program will crash when it is used.

    I had exactly this problem with using an overlapped file read when accessing a USB pipe via a file handle (opened using a special device file name). Are you doing something like that?

    Here's an excerpt of the kind of code I had to write:

    byte[] buffer = new byte[bufferSize];
    
    unsafe
    {
        fixed (NativeOverlapped* pOverlapped = &_overlapped)
        fixed (byte*             pBuffer     = buffer)
        {
            _fileIoEvent.Reset();
    
            if (isRead)
                isSuccessful = Win32.ReadFile(hPipe, new IntPtr(pBuffer), (uint)bufferSize, out bytesRead, pOverlapped);
            else
                isSuccessful = Win32.WriteFile(hPipe, new IntPtr(pBuffer), (uint)bufferSize, out bytesRead, pOverlapped);
    
            int readWriteLastError = Marshal.GetLastWin32Error();
    
            Dbg.WriteLine("lastError: " + readWriteLastError + " (" + isSuccessful + ")", "TayInterface.communicateWithDevice()");
    
            if (isSuccessful)
            {
                ... etc
            }
            
            ... etc
        }
    }
    
    Elsewhere:
    
    NativeOverlapped _overlapped = new NativeOverlapped();
    
    EventWaitHandle _fileIoEvent = new EventWaitHandle(false, EventResetMode.AutoReset, Guid.NewGuid().ToString("N"));
    

    Wednesday, April 15, 2009 12:12 AM
  • My scenario is exactly the same as yours. Maybe we are on the right track.
    I'll test as soon as possible to implement your tip.
    Many thanks.
    Wednesday, April 15, 2009 5:54 AM
  • There may also be an alternative to using unsafe code and fixed pointers: GCHandle. I haven't tried that for the overlapped stuff (but I've used it for other API calls).

    Sample code:

    using System;
    using System.Runtime.InteropServices;
    
    namespace ConsoleApplication
    {
        class Program
        {
            static void Main(string[] args)
            {
                byte[] buffer = new byte[1024];
    
                GCHandle handle = GCHandle.Alloc(buffer, GCHandleType.Pinned);
    
                try
                {
                    // Do stuff with handle.
    
                    IntPtr ptr = handle.AddrOfPinnedObject();
    
                    // Do stuff with ptr.
    
                    Console.WriteLine("Address = " + ptr.ToString("X"));
                }
    
                finally
                {
                    handle.Free();
                }
            }
        }
    }
    
    Wednesday, April 15, 2009 9:28 AM
  • My code has some differeces from yours, but even making them identical my application hangs.
    My ReadFile had a different signature:

    [

    DllImport("kernel32.dll", SetLastError = true)]

     

    public static extern unsafe bool ReadFile(

     

    IntPtr hFile,

     

    void* lpBuffer,

     

    Int32 nNumberOfBytesToRead,

     

    int* lpNumberOfBytesRead,

     

    ref OVERLAPPED lpOverlapped);

    (I had it from somewhere, not an idea of mine), and I didn't handle all the EventWitHandle stuff (which I hardly understand).

    But even adapting my code to yours, its behaviour remains unchanged.

    I would have been suprprised if the problem you pointed wouldn't have affected my receiving operations, that in fact are prefect....as far as I don't mess with timers.

    I looked for the bug everywhere in my code. I even suspected the USB driver.

    I employ USBSamp, a driver of WDF foundation that when was submitted to developers had some bugs related to isochronous transfer.

    The version I employ is not widely employed (but has the avantage to be free) and was patched sucessfully by the French developer Crevars.

    I could try another driver, but that means a lot of work.

    This bug is the most malicious I met in my life (and it isn't so short)

     

    Wednesday, April 15, 2009 5:29 PM
  • It could well be a driver bug!
    Wednesday, April 15, 2009 11:32 PM
  • Eureka (I found It).
    Apparently my problem was using WaitForSingleObject in a thread performing graphical operations.
    The trick was employing MsgWaitForMultipleObjects with bWaitAll=false and dwWakeMask=QS_PAINT.
    The program I had adapted performed I/O and not graphical operations; my program, I guess, messes with the WM_PAINT message and needs this modification.
    I never found such a malicious bug.
    But is this the real solution?
    Anyway now the program works.
    • Marked as answer by stefanopod Friday, May 15, 2009 6:13 AM
    Friday, May 15, 2009 6:13 AM
  • Good news. :)
    Friday, May 15, 2009 8:58 AM
  • I could add that the assumptions that directed my research of the bug were, as I can see now, completely wrong.
    I assumed that when allocating an array, task manager would report a simultaneous increasing of the memory required from the application.
    Now I see, on the contrary, tha task manager reports a memory request when the array is written and not when is allocated.
    My wrong assumptions brought me to look for a memory leak that din't exist. Simply task manager reported that I was writing on a previously allocated array.
    This misunderstanding brought me to find problems in garbage collection, while garbage collection was ok.
    Friday, May 15, 2009 7:43 PM