Search code examples
cconcurrencylockingforkmutex

C - Multiple processes writing to the same log file


I have a very simple C socket server: the main process is listening on a specific port. When a new request arrives, fork() is called: the child process enters a function called dosomething(), returns a response to the client, logs everything in a text file, then dies. This is a simplified view:

void dosomething(int socketFd)
{
    /* ... */
    //Reads the request and sends a response
    writetolog("[INFO] Request accepted. Sent response message -> ", buffer);
    /* ... */
}

This is the logging function:

void writetolog(char* logString1, char* logString2)
{
    /* ... */
    //Prepends date and time, and formats everything nicely into a single char[]
    if ((logFd = open("SocketServer.log", O_CREAT | O_WRONLY | O_APPEND, 0644)) >= 0) {
        write(logFd, logBuffer, strlen(logBuffer));
        close(logFd);
    }
}

Now, my question: since this server is (theoretically) able to process multiple requests simultaneously (so, multiple process may want to write something in the log), do I have to introduce any synchronization (locking) logic for the log file?

Given that the "critical section" is that single write() call: can multiple write() calls on the same file descriptor be "mixed" because of the OS scheduling? Is that a real risk? For example:

Process1 wants to write "ABC"
Process2 wants to write "123"
Result: "AB1C23"

I tried sending thousands of requests from three different clients in the same time window. The log file was correctly written every single time, no "mixing" at all. Can I conclude that the write() system call is atomic, at least in POSIX-compliant systems?

Bonus question: let's say that the logging function uses two write() calls instead of one. The synchronization mechanism should not be optional anymore, because I want to make sure the two calls are executed without being interrupted by another process. What's the simplest locking object I should use in this case? Mutex would be enough?


Solution

  • Q: "Do I have to introduce any synchronization (locking) logic for the log file?"

    A: Yes. Writing simultaneously to the same file can produce race conditions and undesired behaviour.

    Q: "Given that the "critical section" is that single write() call: can multiple write() calls on the same file descriptor be "mixed" because of the OS scheduling? Is that a real risk?"

    A: Yes it is, and your example can happen.

    To improve your code, open the log file once, and keep track of the file descriptor. Use a mutex lock inside writetolog.

    I wrote a new version of writetolog with multiple parameter support (like printf):

    Check Share condition variable & mutex between processes: does mutex have to locked before? for pthread_mutex_t _mutex_log_file initialization

    #MAX_LEN_LOG_ENTRY 1024
    
    // _log_fd is a file descriptor previously opened
    
    void writetolog (char *fmt, ...)
    {
        va_list ap;
        char   msg[MAX_LEN_LOG_ENTRY];
    
        va_start(ap, fmt);
        vsnprintf(msg, MAX_LEN_LOG_ENTRY - 1, fmt, ap);
        va_end(ap);
    
        pthread_mutex_lock (&_mutex_log_file);
    
        fprintf (_log_fd, "[ LOG ] %s\n", msg);
        fflush(_log_fd);
    
        pthread_mutex_unlock (&_mutex_log_file);
    }
    

    An example call of writetolog:

    writetolog("Testing log function: %s %s %s", "hello", "world", "good");