Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why don't I see deadlock (EDEADLK) when several processes lock the same fd with F_SETLKW?

I need to properly handle EDEADLK. In my program I see that both children wait until parent sleep and, and then they apply the lock and right away they leave it. Sorry for my mistakes, I am a Spanish student.

int main(){

    pid_t childpid, childpid2;

    struct flock cerrojo;
    int fd, status;

    if ((fd=open("prueba", O_RDWR)) == -1) 
        perror("apertura fallo");

    cerrojo.l_type =F_WRLCK;
    cerrojo.l_whence =SEEK_SET;
    cerrojo.l_start =0;
    cerrojo.l_len =0;

    if (fcntl(fd, F_SETLK,&cerrojo) == -1) 
        perror("cerrojo fallo");

    if ((childpid= fork()) == -1) {
        printf("Could not create child");
        exit(-1);
    }

    if(childpid){

        if ((childpid2= fork()) == -1) {
            printf("Could not create child");
            exit(-1);
        }

        if(childpid2){

            cerrojo.l_type = F_UNLCK;
            cerrojo.l_whence =SEEK_SET;
            cerrojo.l_start =0;
            cerrojo.l_len =0;

            sleep(2);

            fcntl(fd, F_SETLKW, &cerrojo);

            waitpid(childpid,&status,0);
            waitpid(childpid2,&status,0);

        }


    }

    if(!childpid||!childpid2){

        printf("Soy el hijo %d\n",getpid());

        if(fcntl(fd, F_SETLKW,&cerrojo) == -1){ 
            printf("FCNTL FALLA EN PID: %d\n",getpid());
            sleep(1);
        }

        printf("PID %d va a quitar el cerrojo.\n",getpid());

        cerrojo.l_type = F_UNLCK;
        cerrojo.l_whence =SEEK_SET;
        cerrojo.l_start =0;
        cerrojo.l_len =0;

        fcntl(fd, F_SETLKW, &cerrojo);

        printf("HIJO ACABADO\n");

    }

    return 0;

}
like image 983
Jorge M Avatar asked Dec 11 '25 21:12

Jorge M


1 Answers

For deadlock you need at least two locks. I always imagine myself locked in room A, with the key of room B, and someone else in room B, with the key of room A.

In your example there is only one lock: both children try to lock the same big "door" (the whole file). The second who gets there will block until the first releases the lock again, and then sing the same litle song Lock, sleep,... unlock. No deadlock in sight.

Now, in the example below, the parent locks two "doors" - the first and second byte of the file fd points to (btw, is this really necessary for your example?) and then spawns two children. Both children try to lock both bytes, but each begins with a different one. As soon as the parent releases both bytes the children acquire their locks, 4 lock attempts in all, but the last one would cause deadlock, and duly fails with EDEADLK, so that everybody lives happily ever after - thanks to our wise and just kernel.

#include <stdio.h>
#include <unistd.h>
#include <fcntl.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

int main(){

    pid_t childpid, childpid2;

    struct flock cerrojo;
    int fd, status;

    if ((fd=open("prueba", O_RDWR)) == -1) 
        perror("apertura fallo");

    cerrojo.l_type   = F_WRLCK;
    cerrojo.l_whence = SEEK_SET;
    cerrojo.l_start  = 0;
    cerrojo.l_len    = 2; /* lock "doors" (i.e. bytes)  0 and 1*/

    if (fcntl(fd, F_SETLK,&cerrojo) == -1) 
        perror("cerrojo fallo");


    if((childpid= fork())){ /* parent */
      if ((childpid2= fork())) { /* still parent */

            cerrojo.l_type = F_UNLCK;
            cerrojo.l_len  = 2; /* unlock both doors: let the fun begin :-) */

            sleep(2);
            printf("Tata va a quitar el cerrojo....\n",getpid());
            fcntl(fd, F_SETLKW, &cerrojo); 

            waitpid(childpid,&status,0);
            waitpid(childpid2,&status,0);
        }
    }

    if(!childpid||!childpid2){ /* in child 1 or child 2 */

        printf("Soy el hijo %d\n",getpid());

        int offset0 = (childpid ? 0 : 1);  /* child1 gets 0 and 1, child 2 gets 1 and 0 */
        int offset1 = (childpid ? 1 : 0);
        cerrojo.l_len = 1;


        cerrojo.l_start =  offset0; /* lock door 0 (1) as soon as parent lets us*/
        printf("PID %d locking byte %d\n", getpid(), offset0);
        if(fcntl(fd, F_SETLKW,&cerrojo) == -1){ 
          printf("CERROJO byte %d FALLA EN PID %d (%s)\n",offset0, getpid(),  strerror(errno));
        }

        sleep(1); /* guarantee that the other child has our next door locked ... */
        printf("PID %d locking byte %d\n", getpid(), offset1);
        cerrojo.l_start =  offset1; /* lock door 1 (0). The second of both children who gets here closes the circle and faces deadlock */
        if(fcntl(fd, F_SETLKW,&cerrojo) == -1){
          printf("CERROJO byte %d FALLA EN PID: %d (%s)\n", offset1, getpid(), strerror(errno));
        }

        printf("HIJO %d ACABADO (releasing its lock)\n", getpid()); /* Falling off the end of main() will release the lock anyway */

    }
}

The output:

[hlub@karpaten] ~ > ./test                 
Soy el hijo 29711
PID 29711 locking byte 1
Soy el hijo 29712
PID 29712 locking byte 0
Tata va a quitar el cerrojo....
PID 29711 locking byte 0
PID 29712 locking byte 1
CERROJO byte 1 FALLA EN PID: 29712 (Resource deadlock avoided)
HIJO 29712 ACABADO (releasing its lock)
HIJO 29711 ACABADO (releasing its lock)
like image 191
Hans Lub Avatar answered Dec 14 '25 11:12

Hans Lub