Thread: Mutlithreaded file handling

  1. #1
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130

    Mutlithreaded file handling

    We have a client server application that is writing files to a mapped storage system. Sometimes renaming or removing a file failes, because despite fclose'ing it, the OS shows it as opened by that process.

    I wrote a test tool that mimics our servers multithreaded filehandling behaviour. However, I probably have some stupid bug in it, because it fails even on my local harddisc frequently with either a "bad file descriptor" or "permission denied". I don't know how my own disc can find out I don't have permissions after the fact that I already opened and wrote almost half the file ( flushing after each record included ).

    Anyway, here's the code. It's as ANSI as possible, threading is windows specific. I compiled it on VC6 and will try on VC7 today.

    Run it with C:\Test or something as parameter ( make sure the folder exists ). Depending on the number of threads it will crash after about 10-50, 100-200 ( 3,2 threads ). With only one thread it did not yet fail, so maybe it's MT related.

    PHP Code:
    #include <stdio.h>
    #include <windows.h>

    volatile int        g_nThreadsRunning    0;
    const 
    char*            g_lpszPath            NULL;
    CRITICAL_SECTION    g_CriticalSection;    

    #define RECORD_COUNT            (1024 * 10)
    #define RECORD_SIZE                (1024)
    #define PATH_SIZE                (1024 + 1)
    #define CONCURRENT_THREADS        3

    // thread:
    // open file a
    // write to a
    // close file a
    // open a for reading
    // open b for writing
    // write a to b
    // close a
    // close b
    // remove a
    // rename b to a
    // ( remove a to clean up )
    DWORD WINAPI ThreadFunctionvoidpThreadnumber )
    {
        
    char szFileInputPATH_SIZE ];
        
    char szFileOutputPATH_SIZE ];
        
    char szBufferRECORD_SIZE ];
        
    int  nBlock;

        
    ZeroMemoryszBufferRECORD_SIZE );

        
    // zero data to be written

        
    sprintfszFileInput,  "%s\\%d.a"g_lpszPath, (int)pThreadnumber );
        
    sprintfszFileOutput"%s\\%d.b"g_lpszPath, (int)pThreadnumber );

        
    printf"%d: %s -> %s.\n", (int)pThreadnumberszFileInputszFileOutput );

        
    FILEfpInput  NULL;
        
    FILEfpOutput NULL;

        
    fpInput fopenszFileInput"w" );

        if( 
    fpInput == NULL )
        {
            
    printf"Thread %d: error opening input file for initial writing: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }

        for( 
    nBlock nBlock RECORD_COUNT nBlock++ )
        {
            if( 
    fwriteszBufferRECORD_SIZE1fpInput ) != )
            {
                
    printf"Thread %d: error writing initial input file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
                exit( 
    );
            }

            if( 
    fflushfpInput ) != )
            {
                
    printf"Thread %d: error flushing input file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
                exit( 
    );
            }
        }

        
    fclosefpInput );

        
    fpInput fopenszFileInput"r" );

        if( 
    fpInput == NULL )
        {
            
    printf"Thread %d: error opening input file for reading: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }

        
    fpOutput fopenszFileOutput"w" );

        if( 
    fpOutput == NULL )
        {
            
    printf"Thread %d: error opening output file for writing: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }

        for( 
    nBlock nBlock RECORD_COUNT nBlock++ )
        {
            if( 
    freadszBufferRECORD_SIZE1fpInput ) != )
            {
                
    printf"Thread %d: error reading input file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
                exit( 
    );
            }

            if( 
    fwriteszBufferRECORD_SIZE1fpOutput ) != )
            {
                
    printf"Thread %d: error writing output file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
                exit( 
    );
            }

            if( 
    fflushfpOutput ) != )
            {
                
    printf"Thread %d: error flushing output file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
                exit( 
    );
            }
        }

        
    fclosefpInput );
        
    fclosefpOutput );

        if( 
    removeszFileInput ) != )
        {
            
    printf"Thread %d: error removing input file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }

        if( 
    renameszFileOutputszFileInput ) != )
        {
            
    printf"Thread %d: error renaming files: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }

        if( 
    removeszFileInput ) != )
        {
            
    printf"Thread %d: error removing renamed file: \n%s\n%s", (int)pThreadnumberstrerrorerrno ),strerror_doserrno ) );
            exit( 
    );
        }
                
        
    EnterCriticalSection( &g_CriticalSection );

        
    g_nThreadsRunning--;

        
    LeaveCriticalSection( &g_CriticalSection );

        return 
    0;
    }

    int mainint argccharargv[] )
    {
        
    // Initialize the critical section one time only.
        
    InitializeCriticalSection( &g_CriticalSection ); 
        
        if( 
    argc != )
        {
            
    printf"Usage: %s Path"argv[0] );
            return 
    0;
        }

        
    g_lpszPath argv[1];

        
    int nCount 0;

        while( 
    true )
        {
            
    EnterCriticalSection( &g_CriticalSection );

            if( 
    g_nThreadsRunning CONCURRENT_THREADS )
            {
                
    DWORD dwThreadID;
                
                
    g_nThreadsRunning++;
                
    nCount++;
                
                
    CreateThreadNULL0ThreadFunction, (void*)(nCount), 0, &dwThreadID );
            }

            
    LeaveCriticalSection( &g_CriticalSection );

            
    Sleep100 );
        }

        
    // Release resources used by the critical section object.
        
    DeleteCriticalSection( &g_CriticalSection );
        
        return 
    0;

    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  2. #2
    Skunkmeister Stoned_Coder's Avatar
    Join Date
    Aug 2001
    Posts
    2,572
    Would it fail if thread1 removes the input file just before thread 2 tries to open it? You used fopen with "r" so the file must exist or the fopen call fails. Or is it not getting that far?
    Free the weed!! Class B to class C is not good enough!!
    And the FAQ is here :- http://faq.cprogramming.com/cgi-bin/smartfaq.cgi

  3. #3
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    The threads don't share any data or files. Each thread has two files of it's own ( in this case threadnumber.a and threadnumber.b ).

    It does run for a certain time, sometimes it crashes earlier, sometimes later. It can run a whole thread or even 5 or ten or fifty without crashing. Sometimes. Sometimes not.

    Except for bugs I may have put it, the number of threads should not matter as they only share a single common variable, the number of threads running.

    Each thread does something like this:

    // create the source from scratch
    create a file.
    write to it.
    close it.

    // copy it once
    open source
    open a new file as destination
    copy file
    close both

    // remove one, put the other in it's place
    remove first
    rename second to first

    // final cleanup ( not in original server application )
    remove first
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  4. #4
    Skunkmeister Stoned_Coder's Avatar
    Join Date
    Aug 2001
    Posts
    2,572
    yeah should have read the sprintf more closely. As you are using c runtime funcs you should be using _beginthread rather than CreateThread. Try changing the thread spinning. Does it still fail? It really looks fine to me apart from that.
    Free the weed!! Class B to class C is not good enough!!
    And the FAQ is here :- http://faq.cprogramming.com/cgi-bin/smartfaq.cgi

  5. #5
    Skunkmeister Stoned_Coder's Avatar
    Join Date
    Aug 2001
    Posts
    2,572
    well i have tested this now using CreateThread and _beginthread linking to MTd on xp pro sp2. It runs fine and as expected in all cases on MSVC.net and MSVC.net 7.1. Maybe the problem is MSVC6 specific or OS specific? I let it run until 500-600 files had been moved.I even increases concurrent threads to 10 and it still performed as expected in all cases. Tested on as suggested c:\test with default folder permissions. Ran as an administrative user.
    Free the weed!! Class B to class C is not good enough!!
    And the FAQ is here :- http://faq.cprogramming.com/cgi-bin/smartfaq.cgi

  6. #6
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    I changed it to use _beginthreadex and it's at 400 and counting. I guess that did the trick. I'm on XPSP2 with administrative rights btw. I did not yet test it on VC7 because the original application is compiled with VC6.

    Anyway, I think that's it, thanks a lot
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  7. #7
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    How were you linking with the CRT when you were using CreateThread()?
    Statically or dynamically?

    gg

  8. #8
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    Uhm... no clue, where do I look that up ?
    I was not using ( and not linking ) the MFC btw.

    I left it running when I left yesterday evening and it's at 50K+ right now Will try on the storage system later, where errors are to be expected
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  9. #9
    Skunkmeister Stoned_Coder's Avatar
    Join Date
    Aug 2001
    Posts
    2,572
    You can link to MTd thats static debug multithreaded library
    You can link to MT thats the release multithreaded library
    You can link to MLd thats the multithreaded debug dynamic lib.
    You can link to ML and thats the multithreaded release dynamic lib.

    I tested your code as I said statically linking to the debug. i.e. /MTd
    Free the weed!! Class B to class C is not good enough!!
    And the FAQ is here :- http://faq.cprogramming.com/cgi-bin/smartfaq.cgi

  10. #10
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    I was just curious if you were linking with a multithreaded version of the CRT.
    If switching over to _beginthreadex didn't cause any linker errors, then you were linking with a MT version of the CRT.

    I've always used CreateThread() (at the risk of having a memory leak as detailed here), but it looks like there are some bugs in the CRT that require the use of _beginthread[ex] as a work-around.

    gg

  11. #11
    the hat of redundancy hat nvoigt's Avatar
    Join Date
    Aug 2001
    Location
    Hannover, Germany
    Posts
    3,130
    I had compiler errors until I linked with /MT(d) so I guess I forgot to link with /MT earlier. Maybe that had been the only problem.

    Why I had compiler errors from a linker configuration when using _beginthread(ex) is beyond me though.
    hth
    -nv

    She was so Blonde, she spent 20 minutes looking at the orange juice can because it said "Concentrate."

    When in doubt, read the FAQ.
    Then ask a smart question.

  12. #12
    Registered User Codeplug's Avatar
    Join Date
    Mar 2003
    Posts
    4,981
    There is no _beginthread[ex] symbols for the linker to resolve when linking against a non-MT version of the CRT - because it's non-MT

    That's one of the nice things about using _beginthread[ex] - if you're not linking with an MT CRT then it won't link. If you're not using _beginthread[ex], you can accomplish the same thing with this:
    Code:
    #ifndef _MT
    #error "You must be crazy, cuz we're MT up in here deeeep!!"
    #endif
    gg

Popular pages Recent additions subscribe to a feed

Similar Threads

  1. Creating File Handling Functions
    By td4nos in forum C Programming
    Replies: 6
    Last Post: 06-26-2009, 11:43 AM
  2. basic file handling problem
    By georgen1 in forum C Programming
    Replies: 4
    Last Post: 03-05-2009, 06:21 AM
  3. gcc link external library
    By spank in forum C Programming
    Replies: 6
    Last Post: 08-08-2007, 03:44 PM
  4. Batch file programming
    By year2038bug in forum Tech Board
    Replies: 10
    Last Post: 09-05-2005, 03:30 PM
  5. Need a suggestion on a school project..
    By Screwz Luse in forum C Programming
    Replies: 5
    Last Post: 11-27-2001, 02:58 AM