El 19 de agosto de 2013, Randal L. Schwartz publicó este script de shell, que tenía la intención de garantizar, en Linux, "que solo se ejecute una instancia de [el] script, sin condiciones de carrera o tener que limpiar los archivos de bloqueo":
#!/bin/sh
# randal_l_schwartz_001.sh
(
if ! flock -n -x 0
then
echo "$$ cannot get flock"
exit 0
fi
echo "$$ start"
sleep 10 # for testing. put the real task here
echo "$$ end"
) < $0
Parece funcionar como se anuncia:
$ ./randal_l_schwartz_001.sh & ./randal_l_schwartz_001.sh
[1] 11863
11863 start
11864 cannot get flock
$ 11863 end
[1]+ Done ./randal_l_schwartz_001.sh
$
Esto es lo que entiendo:
- El script redirige (
<
) una copia de sus propios contenidos (es decir, de$0
) al STDIN (es decir, el descriptor de archivo0
) de una subshell. - Dentro de la subshell, el script intenta obtener un lock (
flock -n -x
) exclusivo y no bloqueador en el descriptor de archivo0
.- Si ese intento falla, la subshell sale (y también lo hace el script principal, ya que no hay nada más que pueda hacer).
- Si el intento tiene éxito, la subshell ejecuta la tarea deseada.
Aquí están mis preguntas:
- ¿Por qué el script necesita redirigir, a un descriptor de archivo heredado por la subshell, una copia de su propio contenido en lugar de, digamos, el contenido de algún otro archivo? (Intenté redirigir desde un archivo diferente y volver a ejecutar como se indicó anteriormente, y el orden de ejecución cambió: la tarea no en segundo plano ganó el bloqueo antes que la de fondo. Entonces, tal vez usar el contenido del archivo evite las condiciones de carrera; pero ¿cómo?)
- ¿Por qué el script necesita redirigir, a un descriptor de archivo heredado por la subshell, una copia del contenido de un archivo, de todos modos?
- ¿Por qué mantener un bloqueo exclusivo en el descriptor de archivo
0
en un shell impide que una copia del mismo script, que se ejecuta en un shell diferente, obtenga un bloqueo exclusivo en el descriptor de archivo0
? No conchas tienen sus propias copias independientes de los descriptores de archivos estándar (0
,1
y2
, es decir, stdin, stdout y stderr)?
linux
shell-script
io-redirection
subshell
lock
sampablokuper
fuente
fuente
Respuestas:
Puede usar cualquier archivo, siempre y cuando todas las copias del script usen el mismo. El uso
$0
solo vincula el bloqueo al script en sí: si copia el script y lo modifica para algún otro uso, no necesita encontrar un nuevo nombre para el archivo de bloqueo. Esto es convenienteSi se llama al script a través de un enlace simbólico, el bloqueo está en el archivo real y no en el enlace.
(Por supuesto, si algún proceso ejecuta el script y le da un valor inventado como argumento cero en lugar de la ruta real, entonces esto se rompe. Pero eso rara vez se hace).
¿Estás seguro de que se debió al archivo utilizado y no solo a una variación aleatoria? Al igual que con una canalización, realmente no hay forma de estar seguros de en qué orden se ejecutan los comandos
cmd1 & cmd
. Depende principalmente del planificador del sistema operativo. Obtengo una variación aleatoria en mi sistema.Parece que es así que el propio shell contiene una copia de la descripción del archivo que contiene el bloqueo, en lugar de solo la
flock
utilidad que lo contiene. Un bloqueo hecho conflock(2)
se libera cuando los descriptores de archivo que lo tienen están cerrados.flock
tiene dos modos, ya sea tomar un bloqueo basado en un nombre de archivo y ejecutar un comando externo (en cuyo casoflock
contiene el descriptor de archivo abierto requerido), o tomar un descriptor de archivo desde el exterior, por lo que un proceso externo es responsable de mantener eso.Tenga en cuenta que el contenido del archivo no es relevante aquí, y no se realizan copias. La redirección a la subshell no copia ningún dato en sí mismo, solo abre un identificador para el archivo.
Sí, pero el bloqueo está en el archivo , no en el descriptor de archivo. Solo una instancia abierta del archivo puede retener el bloqueo a la vez.
Creo que debería poder hacer lo mismo sin la subshell, utilizando
exec
para abrir un identificador para el archivo de bloqueo:fuente
{ }
lugar de( )
también funcionaría y evitaría la subshell.exec
.Se adjunta un bloqueo de archivo a un archivo a través de una descripción de archivo . En un nivel alto, la secuencia de operaciones en una instancia del script es:
Sostener el bloqueo evita que se ejecute otra copia del mismo script porque eso es lo que hacen los bloqueos. Mientras exista un bloqueo exclusivo en un archivo en algún lugar del sistema, es imposible crear una segunda instancia del mismo bloqueo, incluso a través de una descripción de archivo diferente.
Abrir un archivo crea una descripción del archivo . Este es un objeto kernel que no tiene mucha visibilidad directa en las interfaces de programación. Accede a una descripción de archivo indirectamente a través de descriptores de archivo, pero normalmente piensa que accede al archivo (leyendo o escribiendo su contenido o metadatos). Un bloqueo es uno de los atributos que son propiedad de la descripción del archivo en lugar de un archivo o un descriptor.
Al principio, cuando se abre un archivo, la descripción del archivo tiene un solo descriptor de archivo, pero se pueden crear más descriptores ya sea creando otro descriptor (la
dup
familia de las llamadas al sistema) o bifurcando un subproceso (después del cual tanto el padre como el padre) el niño tiene acceso a la misma descripción del archivo). Un descriptor de archivo puede cerrarse explícitamente o cuando el proceso en el que se encuentra muere. Cuando se cierra el último descriptor de archivo adjunto a un archivo, se cierra la descripción del archivo.Así es como la secuencia de operaciones anterior afecta la descripción del archivo.
<$0
abre el archivo de script en el subshell, creando una descripción del archivo. En este punto hay un descriptor de archivo único adjunto a la descripción: descriptor número 0 en la subshell.flock
y espera a que salga. Mientras se ejecuta el lote, hay dos descriptores adjuntos a la descripción: el número 0 en el subshell y el número 0 en el proceso del lote. Cuando flock toma el bloqueo, eso establece una propiedad de la descripción del archivo. Si otra descripción de archivo ya tiene un bloqueo en el archivo, el lote no puede tomar el bloqueo, ya que es un bloqueo exclusivo.La razón por la cual el script usa una redirección
$0
es que la redirección es la única forma de abrir un archivo en el shell, y mantener una redirección activa es la única manera de mantener abierto un descriptor de archivo. El subshell nunca lee desde su entrada estándar, solo necesita mantenerlo abierto. Puede usarEn realidad, puede obtener la misma secuencia de operaciones en el shell si realiza la redirección con el
exec
incorporado.El script podría usar un descriptor de archivo diferente si quisiera seguir accediendo a la entrada estándar original.
o con una subshell:
El bloqueo no tiene que estar en el archivo de script. Podría estar en cualquier archivo que se pueda abrir para leer (por lo tanto, debe existir, debe ser un tipo de archivo que se pueda leer, como un archivo normal o una tubería con nombre, pero no un directorio, y el proceso del script debe tener El permiso para leerlo). El archivo de secuencia de comandos tiene la ventaja de que está garantizado para estar presente y ser legible (excepto en el caso de borde donde se eliminó externamente entre el momento en que se invocó el script y el momento en que el script llega a la
<$0
redirección).Mientras
flock
tenga éxito, y el script esté en un sistema de archivos donde los bloqueos no tengan errores (algunos sistemas de archivos de red como NFS pueden tener errores), no veo cómo el uso de un archivo de bloqueo diferente podría permitir una condición de carrera. Sospecho un error de manipulación de tu parte.fuente
El archivo utilizado para bloquear no es importante, el script lo utiliza
$0
porque se sabe que existe.El orden en que se obtienen los bloqueos será más o menos aleatorio, dependiendo de qué tan rápido su máquina pueda iniciar las dos tareas.
Puede usar cualquier descriptor de archivo, no necesariamente 0. El bloqueo se mantiene en el archivo abierto al descriptor de archivo, no en el descriptor en sí.
fuente