Search code examples
clinuxsynchronizationsemaphorefile-locking

Wrong write to file output order of synchronized processes?


I have the following problem.

I have two processes that are being synchronized with semaphores and the idea is this:

  • process 1 writes something to the txt file
  • process 2 writes something to the txt file
  • process 1 writes something to the test file

I have included this sample code that demonstrates the problem:

// semaphore names 
#define NAME1 "/s1"
#define NAME2 "/s2" 

int main()
{
    /* semaphores for process synchronization */ 
    sem_t *sm1;
    sm1 = sem_open( NAME1, O_CREAT, 0666, 0);

    sem_t *sm2;
    sm2 = sem_open(NAME2, O_CREAT, 0666, 0);

    /* processes*/
    int proc1;
    int proc2;

    /* file lock struct */ 
    struct flock fl = {F_WRLCK, SEEK_SET,   0,      0,     0 };

    /* create a text file */ 
    FILE *fp;
    int fd;

    fp = fopen("output.txt", "w"); // create and close the file 
    fclose(fp);

    if((fd = open("output.txt", O_RDWR)) == -1) { // open the file again to get file descriptor
        perror("open");
        exit(1);
    }

    fp = fdopen(fd, "w");

    /* first process */
    if ((proc1 = fork()) < 0) {
        perror("fork");
        exit(2);
    }
    else if(proc1 == 0) {
        fl.l_type = F_WRLCK; // set the lock type and pid of the forked process 
        fl.l_pid = getpid();

        if (fcntl(fd, F_SETLKW, &fl) == -1) { // lock the file before writing to it 
            perror("fcntl");
            exit(1);
        }

        fprintf(fp, "proc1 - action1\n"); // write to the file

        fl.l_type = F_UNLCK;  

        if (fcntl(fd, F_SETLK, &fl) == -1) {  // unlock the file so other processes can write to it
            perror("fcntl");
            exit(1);
        }

        fprintf(stdout, "proc1 - action1\n"); 

        sem_post(sm1); // let the second process run 
        sem_wait(sm2); // wait till the second process is done 

        // write one more thing the same way to the text file after the second process is done 
        fl.l_type = F_WRLCK;  
        fl.l_pid = getpid();

        if (fcntl(fd, F_SETLKW, &fl) == -1) { 
            perror("fcntl");
            exit(1);
        }

        fprintf(fp, "proc1 - action2\n"); 

        fl.l_type = F_UNLCK;  

        if (fcntl(fd, F_SETLK, &fl) == -1) {  
            perror("fcntl");
            exit(1);
        }

        fprintf(stdout, "proc1 - action2\n"); 

        exit(0);
    }

    /* second process */ 
    if ((proc2 = fork()) < 0) {
        perror("fork");
        exit(2);
    }
    else if(proc2 == 0) {
        sem_wait(sm1); // waits for proc1 to perform it's first action

        // write something to the text file and let proc1 write it's second action 
        fl.l_type = F_WRLCK;  
        fl.l_pid = getpid();

        if (fcntl(fd, F_SETLKW, &fl) == -1) { 
            perror("fcntl");
            exit(1);
        }

        fprintf(fp, "proc2 - action1\n"); 

        fl.l_type = F_UNLCK;  

        if (fcntl(fd, F_SETLK, &fl) == -1) {  
            perror("fcntl");
            exit(1);
        }

        fprintf(stdout, "proc2 - action1\n"); 

        sem_post(sm2);

        exit(0);
    }

    // wait for both processes to finish 
    waitpid(proc1, NULL, 0);
    waitpid(proc2, NULL, 0);

    sem_close(sm1);
    sem_unlink(NAME1);

    sem_close(sm2);
    sem_unlink(NAME2);

    return 0;
}

I have included the fprintf with stdout lines so that you can see that the output in the terminal is correct:

-proc1 - action1
-proc2 - action1
-proc1 - action2

Just like they are being synchronized. However, the output in the output.txt file is this:

-proc2 - action1
-proc1 - action1
-proc1 - action2

Why is this happening?

Also, before the process writes to the file, I always lock it so that no other process can access it and then unlock it again.

I'm not sure if I'm doing this right so I'd appreciate any advice I can get!

Thanks a lot!


Solution

  • You have buffered IO by default - so the buffer isn't actually flushed to the file until you close it if it is smaller output than the buffer size (otherwise you get a buffer's worth at a time when it is full... often 8kb or so). Note also that each process gets its own, separate buffer.

    To fix, flush your output after printing, and before writing, fseek() to SEEK_END to ensure you are at the end of the file.

    fprintf(fp, "proc1 - action1\n"); // write to the file
    fflush(fp);
    

    BTW, i didn't check for the validity of your semaphore code, just noticed you were missing this crucial piece of information. In general, however, if you are using file locking, the semaphore is redundant...