¿Secuencia de comandos cron para actuar como una cola O una cola para cron?
Pregunta
Apuesto a que alguien ya resolvió esto y tal vez estoy usando términos de búsqueda incorrectos para que google me diga la respuesta, pero aquí está mi situación.
Tengo un script que quiero ejecutar, pero quiero que se ejecute solo cuando esté programado y solo uno a la vez.(no se puede ejecutar el script simultáneamente)
Ahora la parte complicada es que tengo una tabla llamada "myhappyschedule" que tiene los datos que necesito y la hora programada.Esta tabla puede tener múltiples horarios programados incluso al mismo tiempo, cada uno ejecutaría este script.Básicamente, necesito una cola cada vez que se activa el script y todos deben esperar a que finalice cada uno de ellos.(a veces el script puede tardar solo un minuto en ejecutarse, a veces son muchos minutos)
Lo que estoy pensando en hacer es crear un script que verifique myhappyschedule cada 5 minutos y recopile los que están programados y los coloque en una cola donde otro script pueda ejecutar cada "trabajo" o ocurrencia en la cola en orden.Todo esto suena complicado.
Para alargar esto, debo decir que estoy permitiendo a los usuarios programar cosas en myhappyschedule y no editar crontab.
¿Qué se puede hacer con esto?¿Bloqueos de archivos y scripts que llaman scripts?
Solución
agregar una columna exec_status
a myhappytable
(tal vez también time_started
y time_finished
, ver pseudocódigo)
ejecute el siguiente script cron cada x minutos
pseudocódigo del script cron:
[create/check pid lock (optional, but see "A potential pitfall" below)]
get number of rows from myhappytable where (exec_status == executing_now)
if it is > 0, exit
begin loop
get one row from myhappytable
where (exec_status == not_yet_run) and (scheduled_time <= now)
order by scheduled_time asc
if no such row, exit
set row exec_status to executing_now (maybe set time_started to now)
execute whatever command the row contains
set row exec_status to completed
(maybe also store the command output/return as well, set time_finished to now)
end loop
[delete pid lock file (complementary to the starting pid lock check)]
De esta manera, el script primero verifica si ninguno de los comandos se está ejecutando, luego ejecuta el primer comando que aún no se ha ejecutado, hasta que no haya más comandos para ejecutar en ese momento.Además, puede ver qué comando se está ejecutando consultando la base de datos.
Un peligro potencial: Si se cancela el script cron, una tarea programada permanecerá en estado "executing_now".Para eso está el bloqueo pid al principio y al final:para ver si el script cron terminó correctamente.pseudocódigo de crear/comprobar pidlock:
if exists pidlockfile then
check if process id given in file exists
if not exists then
update myhappytable set exec_status = error_cronscript_died_while_executing_this
where exec_status == executing_now
delete pidlockfile
else (previous instance still running)
exit
endif
endif
create pidlockfile containing cron script process id
Otros consejos
Puede usar el comando at (1) dentro de su script para programar su próxima ejecución. Antes de salir, puede verificar myhappyschedule para el próximo tiempo de ejecución. No necesitas cron en absoluto, realmente.
Me encontré con esta pregunta mientras buscaba una solución al problema de las colas.Para beneficio de cualquiera que busque aquí, está mi solución.
Combine esto con un cron que inicie los trabajos tal como están programados (incluso si están programados para ejecutarse al mismo tiempo) y eso también resuelve el problema que describió.
Problema
- Como máximo debería estar ejecutándose una instancia del script.
- Queremos preparar las solicitudes para procesarlas lo más rápido posible.
es decir.Necesitamos un canal hacia el guión.
Solución:
Cree una canalización para cualquier script.Hecho usando un pequeño script bash (más abajo).
El script se puede llamar como
./pipeline "<any command and arguments go here>"
Ejemplo:
./pipeline sleep 10 &
./pipeline shabugabu &
./pipeline single_instance_script some arguments &
./pipeline single_instance_script some other_argumnts &
./pipeline "single_instance_script some yet_other_arguments > output.txt" &
..etc
El guión crea un nuevo tubería con nombre para cada comando.Entonces lo anterior creará canalizaciones con nombre: sleep
, shabugabu
, y single_instance_script
En este caso, la llamada inicial iniciará un lector y ejecutará single_instance_script
con some arguments
como argumentos.Una vez que se completa la llamada, el lector tomará la siguiente solicitud del canal y la ejecutará con some other_arguments
, completar, tomar el siguiente, etc.
Este script bloqueará los procesos solicitantes, así que llámelo como un trabajo en segundo plano (& al final) o como un proceso independiente con at
(at now <<< "./pipeline some_script"
)
#!/bin/bash -Eue
# Using command name as the pipeline name
pipeline=$(basename $(expr "$1" : '\(^[^[:space:]]*\)')).pipe
is_reader=false
function _pipeline_cleanup {
if $is_reader; then
rm -f $pipeline
fi
rm -f $pipeline.lock
exit
}
trap _pipeline_cleanup INT TERM EXIT
# Dispatch/initialization section, critical
lockfile $pipeline.lock
if [[ -p $pipeline ]]
then
echo "$*" > $pipeline
exit
fi
is_reader=true
mkfifo $pipeline
echo "$*" > $pipeline &
rm -f $pipeline.lock
# Reader section
while read command < $pipeline
do
echo "$(date) - Executing $command"
($command) &> /dev/null
done