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?
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");