flock (): ¿eliminando el archivo bloqueado sin condición de carrera?

7 minutos de lectura

Estoy usando flock () para mutexes con nombre entre procesos (es decir, algún proceso puede decidir mantener un bloqueo en “some_name”, que se implementa bloqueando un archivo llamado “some_name” en un directorio temporal:

lockfile = "/tmp/some_name.lock";
fd = open(lockfile, O_CREAT);
flock(fd, LOCK_EX);

do_something();

unlink(lockfile);
flock(fd, LOCK_UN);

El archivo de bloqueo debe eliminarse en algún momento para evitar llenar el directorio temporal con cientos de archivos.

Sin embargo, hay una condición de carrera obvia en este código; ejemplo con los procesos A, B y C:

A opens file
A locks file
B opens file
A unlinks file
A unlocks file
B locks file (B holds a lock on the deleted file)
C opens file (a new file one is created)
C locks file (two processes hold the same named mutex !)

¿Hay alguna manera de eliminar el archivo de bloqueo en algún momento sin introducir esta condición de carrera?

  • El problema es que está tratando de implementar una estrategia de bloqueo de grano fino (los archivos representan recursos), pero tiene una disputa sobre un recurso de grano grueso compartido (el sistema de archivos). O necesita un bloqueo global en el directorio de archivos de bloqueo antes de actualizar sus bloqueos detallados o rediseñar su estrategia de bloqueo por completo.

    – jxh

    17 de julio de 2013 a las 19:57


  • ¿Puedes elaborar tu necesidad? Por ejemplo, ¿por qué no hacer que los programas usen 1 nombre de archivo conocido si todos están bloqueando el mismo recurso conceptual?

    – Arte Swri

    17/07/2013 a las 20:51

avatar de usuario
usuario2769258

Lo siento si respondo a una pregunta muerta:

Después de bloquear el archivo, abra otra copia del mismo, haga clic en ambas copias y verifique el número de inodo, así:

lockfile = "/tmp/some_name.lock";

    while(1) {
        fd = open(lockfile, O_CREAT);
        flock(fd, LOCK_EX);

        fstat(fd, &st0);
        stat(lockfile, &st1);
        if(st0.st_ino == st1.st_ino) break;

        close(fd);
    }

    do_something();

    unlink(lockfile);
    flock(fd, LOCK_UN);

Esto evita la condición de carrera, porque si un programa mantiene un bloqueo en un archivo que todavía está en el sistema de archivos, cualquier otro programa que tenga un archivo sobrante tendrá un número de inodo incorrecto.

De hecho, lo probé en el modelo de máquina de estado, usando las siguientes propiedades:

Si P_i tiene un descriptor bloqueado en el sistema de archivos, entonces no hay ningún otro proceso en la sección crítica.

Si P_i está después de la estadística con el inodo correcto o en la sección crítica, tiene el descriptor bloqueado en el sistema de archivos.

  • ¿Puede desbloquear el archivo después de desvincularlo y cerrarlo? El manual dice que el rebaño “Aplica o elimina un bloqueo de aviso en el archivo abierto especificado por fd”.

    – sheerun

    12 de noviembre de 2013 a las 22:44


  • ¿No debería cerrar también el fd al final para evitar una fuga?

    -Amro Younes

    14/10/2014 a las 16:12

  • ¡No portátil! Por ejemplo, en Windows st_ino siempre es 0

    – oxidado

    8 de noviembre de 2017 a las 18:35

  • ¿Qué es el modelo de máquina de estado?

    – piotr

    11 de julio de 2018 a las 12:49

  • esta solución no funciona, la pregunta es sobre 3 procesos A, B, C.

    – Muayyad Alsadi

    27 de agosto de 2018 a las 12:46

avatar de usuario
Guido U Draheim

  1. En Unix, es posible eliminar un archivo mientras está abierto: el inodo se mantendrá hasta que todos los procesos hayan finalizado y lo tengan en su lista de descriptores de archivos.
  2. En Unix, es posible verificar que un archivo se eliminó de todos los directorios al verificar el recuento de enlaces a medida que se vuelve cero

Entonces, en lugar de comparar el valor ino de las rutas de archivo antiguas/nuevas, simplemente puede verificar el recuento de nlink en el archivo que ya está abierto. Asume que es solo un archivo de bloqueo efímero y no un recurso o dispositivo mutex real.

lockfile = "/tmp/some_name.lock";

for(int attempt; attempt < timeout; ++attempt) {
    int fd = open(lockfile, O_CREAT, 0444);
    int done = flock(fd, LOCK_EX | LOCK_NB);
    if (done != 0) { 
        close(fd);
        sleep(1);     // lock held by another proc
        continue;
    }
    struct stat st0;
    fstat(fd, &st0);
    if(st0.st_nlink == 0) {
       close(fd);     // lockfile deleted, create a new one
       continue;
    }
    do_something();
    unlink(lockfile); // nlink :=0 before releasing the lock
    flock(fd, LOCK_UN);
    close(fd);        // release the ino if no other proc 
    return true;
}
return false;

  • @Bob ¿cómo es eso? Este código nunca unlinksa archivo que no tiene un bloqueo exclusivo activado, y cuando se realiza esa verificación, ningún otro proceso puede tener un bloqueo exclusivo, ya que tiene el bloqueo mismo.

    – Joseph Sible-Reincorporar a Mónica

    26 de enero de 2020 a las 3:51

avatar de usuario
MvG

Si usa estos archivos solo para bloquear y en realidad no escribe en ellos, le sugiero que trate la existencia de la entrada del directorio como una indicación de un bloqueo retenido y evite usar flock en total.

Para hacerlo, debe construir una operación que cree una entrada de directorio e informe un error si ya existe. En Linux y con más sistemas de archivos, pasando O_EXCL para open funcionará para esto. Pero algunas plataformas y algunos sistemas de archivos (en particular, NFS más antiguos) no admiten esto. los página man para open por lo que sugiere una alternativa:

Programas portátiles que desean realizar un bloqueo atómico de archivos mediante un archivo de bloqueo y necesitan evitar la dependencia de la compatibilidad con NFS para O_EXCLpuede crear un archivo único en el mismo sistema de archivos (por ejemplo, incorporando nombre de host y PID) y usar link(2) para hacer un enlace al archivo de bloqueo. Si link(2) devuelve 0, el bloqueo es exitoso. De lo contrario, utilice stat(2) en el archivo único para verificar si su recuento de enlaces ha aumentado a 2, en cuyo caso el bloqueo también es exitoso.

Esto parece un esquema de bloqueo que está documentado oficialmente y, por lo tanto, indica un cierto nivel de soporte y sugerencia de mejores prácticas. Pero también he visto otros enfoques. bzr por ejemplo, utiliza directorios en lugar de enlaces simbólicos en la mayoría de los lugares. citando de su código fuente:

Un bloqueo está representado en el disco por un directorio de un nombre particular, que contiene un archivo de información. La toma de un bloqueo se realiza cambiando el nombre de un directorio temporal en su lugar. Usamos directorios temporales porque para todos los transportes y sistemas de archivos conocidos, creemos que exactamente un intento de reclamar el bloqueo tendrá éxito y los demás fallarán. (Los archivos no funcionarán porque algunos sistemas de archivos o transportes solo tienen renombrar y sobrescribir, lo que dificulta saber quién ganó).

Una desventaja de los enfoques anteriores es que no se bloquearán: un intento fallido de bloqueo dará como resultado un error, pero no espere hasta que el bloqueo esté disponible. Tendrá que sondear el bloqueo, lo que podría ser problemático a la luz de la contención de bloqueo. En ese caso, es posible que desee alejarse aún más de su enfoque basado en el sistema de archivos y utilizar implementaciones de terceros en su lugar. Pero ya se han hecho preguntas generales sobre cómo hacer mutexes de ipc, por lo que le sugiero que busque [ipc] [mutex] y echa un vistazo a los resultados, este en particular. Por cierto, estas etiquetas también pueden ser útiles para tu publicación.

  • El problema que sufre este enfoque es que si un proceso muere mientras mantiene un bloqueo, no hay una forma sólida de recuperar automáticamente el bloqueo. En cambio, en el flock enfoque, si un proceso muere mientras mantiene el bloqueo, el archivo permanecerá en el sistema de archivos, pero ya no será flocked; otros procesos ahora pueden adquirir el bloqueo.

    – David

    20 de marzo de 2014 a las 10:42

  • @davidg: Ese es un punto válido. Algunas implementaciones escriben una marca de tiempo y/o el pid del proceso de bloqueo en el archivo que se vincula al nombre del archivo de bloqueo, o a un archivo conocido en el directorio de bloqueo. De esa manera, puede verificar si un proceso con esa ID todavía está vivo y también puede hacer que caduquen los bloqueos después de un tiempo determinado.

    – MvG

    20 de marzo de 2014 a las 10:53

¿Ha sido útil esta solución?

Esta web utiliza cookies propias y de terceros para su correcto funcionamiento y para fines analíticos y para mostrarte publicidad relacionada con sus preferencias en base a un perfil elaborado a partir de tus hábitos de navegación. Al hacer clic en el botón Aceptar, acepta el uso de estas tecnologías y el procesamiento de tus datos para estos propósitos. Configurar y más información
Privacidad