8

I did create a small C++ Tracing solution which works very well. Within one process all is well but when I open the output file from diferent processes the data gets not correctly written. I did open the file with FILE_SHARE_WRITE to be able to write to the file when it is already open. Then I did create a named mutex to ensure proper synchronisation between processes. But it appears that this is not enough. According to the MSDN this does work within one process but not between different processes. Next I tried to call FlushFileBuffers after every write while the mutex was still held but data was still distorted like this

The format is time process id/Thread id method enter/leave/severity namespace+method and then the message text.

10:29:42.994 7448/2236       }} Dll2.Test.fndll2 L1 -> Duration: 0.094s
10:29:43.040 7448/2236 {{       Dll2.DllMain L1
10:29:43.134 7448/2236 Info     Dll2.DllMain L1 Process detach
10:29:43.181 7448/2236       }} Dll2.DllMain L1 -> Duration: 0.141s
     }} Dll2.DllMain L1 -10:29:42.681 7448/2236 Info     Dll1.DllMain L1 Process attach
10:29:42.728 7448/2236       }} Dll1.DllMain L1 -10:29:42.744 2216/5510:29:42.775 7448/2236 {{       Dll1.Test.fndll1 10:210:29:42.822 7448/2236 Info     Dll1.Test.fndll1 10:29:42.837 2216/557610:29:42.853 7448/2236       }} Dll1.Test.fndll1 L110:29:42.884 2216/557610:29:43.306 7448/2236 {{       Dll1.DllMain L1
10:29:43.353 7448/2236 Info     Dll1.DllMain L1 Process detach
10:29:43.400 7448/2236       }} Dll1.DllMain L1 -> Duration: 0.094s

I have looked at FILE_FLAG_NO_BUFFERING but it has severe limitations and it seems not easy to use.

Does anybody know the right way to write synchronized to the same file without distoriting the output?

Yours,

Alois Kraus

Hasturkun
  • 35,395
  • 6
  • 71
  • 104
Alois Kraus
  • 231
  • 3
  • 4

5 Answers5

7

I finally got it working. The trick was to Seek at the end of the file before very write. Otherwise I would overwrite about half of my output although I do lock with a cross process mutex before every write.

The code looks like this

__int64 Seek (HANDLE hf, __int64 distance, DWORD MoveMethod)  // from MSDN 
{
   LARGE_INTEGER li;
   li.QuadPart = distance;
   li.LowPart = SetFilePointer (hf, 
                                li.LowPart, 
                                &li.HighPart, 
                                MoveMethod);

   if (li.LowPart == INVALID_SET_FILE_POINTER && GetLastError() != NO_ERROR)
   {
      li.QuadPart = -1;
   }

   return li.QuadPart;
}



void WriteToFile(TCHAR *pData)
{
    DWORD dwWritten = 0;

    if( FALSE == ::WriteFile(_hFile, pData, _tcslen(pData)*sizeof(TCHAR), &dwWritten, NULL) )
    {
        _LastError = ::GetLastError();
        ASSERT__(FALSE);
    }
}

virtual void Write(TCHAR *pStr)
{
    if( _hWriteMutex != NULL )
    {
        DWORD res = ::WaitForSingleObject(_hWriteMutex, 120*1000);
        if( res == WAIT_OBJECT_0 || res == WAIT_ABANDONED ) // another process might have crashed while holding the mutex
        {
            // Ensure that we are really writing at the end of the file 
            __int64 fPos = Seek(_hFile, 0, FILE_END);
            WriteToFile(pStr);
            ::ReleaseMutex(_hWriteMutex);
        }
        else
        {
            ASSERT__(FALSE);
        }
    }
    else
    {
        WriteToFile(pStr);
    }
}
Alois Kraus
  • 231
  • 3
  • 4
2

SQLite uses file locks to ensure this doesn't happen to its database file when accessing it from multiple processes. Have you tried using LockFile? (example). I have, in the past, used an SQLite database for logging from multiple processes, but then that's probably a bit too much in this case.

badgerr
  • 7,802
  • 2
  • 28
  • 43
0

I don't know about the "right" way, but what you're doing already seems as right as it gets to me. The next possible solution that I can think of would be a dedicated process for writing to this file. The rest of the processes would communicate with the log-process through named pipes and (possibly) mutexes.

Perhaps you can even set it up so that there is no explicit process, but one of the running processes (the one which was started first) takes on this role. There would, of course, be further complications when this process ends and needs to pass the ownership of the file to another process. All in all it's not a very pretty solution, but it should work if all else fails.

Although I would suspect that there is still something neither of us has thought about, because there are programs which successfully use files for communication.

Hmm... on second thought - you already have timestamps available. Why not just make a browsing tool which sorts the records by timestamps? That way it would not matter what gets cached where.

Oh, and a third though - have you tried memory-mapped I/O? That's organized differently and it might be able to solve your problem (not to mention be more efficient).

Vilx-
  • 104,512
  • 87
  • 279
  • 422
  • For a simple tracing solution an extra process is way too many overhead. To use a memory mapped file would be an option but it could be tricky especially when you need to remap the file and create a sliding window because otherwise I would eat up the whole address space (32 bit at least) when I keep the whole file mapped. – Alois Kraus Jan 17 '11 at 11:01
  • @Alois Kraus - Well, that's why the suggestion to designate one of the already running processes as "master". But here's something else I just thought of - is the "current location" pointer in a file shared among your processes? Perhaps that's the problem. Maybe it's local for each process and you need to seek to the end before each write. – Vilx- Jan 17 '11 at 11:27
  • The output is not overwritten but the other process inserts in the middle of the line output of the other process which breaks the formatting of the whole file. It even looks like it is possible to get something P11,P12,P13,P14,P21,P22,P1P23,P24,P15,5,P16 where output from one process is written partially and then new output is writen before the rest of the old output is written. Very strange. – Alois Kraus Jan 17 '11 at 14:14
  • @Alois Kraus - a fellow commenter seems to have an idea - are you writing the message with a single WriteFile() call, or multiple? If it's multiple calls, maybe CPU timeslicing interferes. – Vilx- Jan 17 '11 at 14:30
  • Every line will cause a WriteFile call. So this cannot be the problem. But I know that internally Windows will keep a buffer and decides on its own when to flush the buffer. It seems that every process has its own buffer which causes this strange line breaks in the middle of a line. – Alois Kraus Jan 17 '11 at 14:44
0

As a starting point, I'd suggest ditching the shared write mode and making use of your named mutex to manage exclusive access to the file. If everything's working as it should, this should give you correct output. Then do some performance testing to see if you can afford to operate like this - you might find it's sufficient.

antsyawn
  • 971
  • 10
  • 17
0

You have to protect or synchronize concurrent calls to the function that writes into the file with a named mutex. See CreateMutex

pagra
  • 665
  • 4
  • 11
  • I do that already. I would like to know how to make it work. A global mutex is not sufficient because it seems that every process has its own buffer that is flushed to disc when Windows thinks it should. – Alois Kraus Jan 19 '11 at 20:28