views:

254

answers:

6

I have this tool in which a single log-like file is written to by several processes.

What I want to achieve is to have the file truncated when it is first opened, and then have all writes done at the end by the several processes that have it open. All writes are systematically flushed and mutex-protected so that I don't get jumbled output.

First, a process creates the file, then starts a sequence of other processes, one at a time, that then open the file and write to it (the master sometimes chimes in with additional content; the slave process may or may not be open and writing something).

I'd like, as much as possible, not to use more IPC that what already exists (all I'm doing now is writing to a popen-created pipe). I have no access to external libraries other that the CRT and Win32 API, and I would like not to start writing serialization code.

Here is some code that shows where I've gone:

// open the file. Truncate it if we're the 'master', append to it if we're a 'slave'
std::ofstream blah(filename, ios::out | (isClient ? ios:app : 0));

// do stuff...

// write stuff
myMutex.acquire();
blah << "stuff to write" << std::flush;
myMutex.release();

Well, this does not work: although the output of the slave process is ordered as expected, what the master writes is either bunched together or at the wrong place, when it exists at all.

I have two questions: is the flag combination given to the ofstream's constructor the right one ? Am I going the right way anyway ?

A: 

How do you create that mutex?
For this to work this needs to be a named mutex so that both processes actually lock on the same thing.
You can check that your mutex is actually working correctly with a small piece of code that lock it in one process and another process which tries to acquire it.

shoosh
Oh, I actually dropped the creation part out for the sake of brevity. It is a named mutex, and it works correctly.
RaphaelSP
+1  A: 

If you'll be writing a lot of data to the log from multiple threads, you'll need to rethink the design, since all threads will block on trying to acquire the mutex, and in general you don't want your threads blocked from doing work so they can log. In that case, you'd want to write your worker thread to log entries to queue (which just requires moving stuff around in memory), and have a dedicated thread to pull entries off the queue and write them to the output. That way your worker threads are blocked for as short a time as possible.

You can do even better than this by using async I/O, but that gets a bit more tricky.

I guess that would be a sensible approach even for a multi-process design... But I'd need to implement some inter-process communication, and I do not want to do that.
RaphaelSP
A: 

As suggested by reinier, the problem was not in the way I use the files but in the way the programs behave.

The fstreams do just fine.

What I missed out is the synchronization between the master and the slave (the former was assuming a particular operation was synchronous where it was not).

edit: Oh well, there still was a problem with the open flags. The process that opened the file with ios::out did not move the file pointer as needed (erasing text other processes were writing), and using seekp() completely screwed the output when writing to cout as another part of the code uses cerr.

My final solution is to keep the mutex and the flush, and, for the master process, open the file in ios::out mode (to create or truncate the file), close it and reopen it using ios::app.

RaphaelSP
A: 

I suggest blocking such that the text is completely written to the file before releasing the mutex. I've had instances where the text from one task is interrupted by text from a higher priority thread; doesn't look very pretty.

Also, put the format into Comma Separated format, or some format that can be easily loaded into a spreadsheet. Include thread ID and timestamp. The interlacing of the text lines shows how the threads are interacting. The ID parameter allows you to sort by thread. Timestamps can be used to show sequential access as well as duration. Writing in a spreadsheet friendly format will allow you to analyze the log file with an external tool without writing any conversion utilities. This has helped me greatly.

Thomas Matthews
I assume std::flush ensures the write is completed before returning (my quasi-unique use case is to write to a local file) (and it's working so far).Timestamps did indeed show me where the problem was.
RaphaelSP
+1  A: 

I made a 'lil log system that has it's own process and will handle the writing process, the idea is quite simeple. The proccesses that uses the logs just send them to a pending queue which the log process will try to write to a file. It's like batch procesing in any realtime rendering app. This way you'll grt rid of too much open/close file operations. If I can I'll add the sample code.

Moss
Quite simple indeed, definitely an option to explore next time i need it !
RaphaelSP
A: 

One option is to use ACE::logging. It has an efficient implementation of concurrent logging.

Amit Kumar