Unos grillos escuchados en esta pregunta. Buen 'ol RTFC con algunos documentos de simulación de eventos discretos y Wikipedia:
http://en.wikipedia.org/wiki/Cron#Multi-user_capability
El algoritmo utilizado por este cron es el siguiente:
- Al iniciar, busque un archivo llamado .crontab en los directorios de inicio de todos los titulares de cuentas.
- Para cada archivo crontab encontrado, determine la próxima vez en el futuro que se ejecutará cada comando.
- Coloque esos comandos en la lista de eventos de Franta-Maly con su tiempo correspondiente y su especificador de tiempo de "cinco campos".
- Ingrese al bucle principal:
- Examine la entrada de la tarea al principio de la cola, calcule cuánto tiempo en el futuro debe ejecutarse.
- Dormir durante ese período de tiempo.
- Al despertar y después de verificar la hora correcta, ejecute la tarea al principio de la cola (en segundo plano) con los privilegios del usuario que la creó.
- Determine la próxima vez en el futuro para ejecutar este comando y vuelva a colocarlo en la lista de eventos en ese momento
Escribí una publicación de blog describiéndola.
Citando el texto relevante de allí:
- Podemos tener un grupo de subprocesos finito que ejecutará todas las tareas tomándolas de un
PriorityBlockingQueue
(montón seguro para subprocesos) priorizado enjob.nextExecutionTime()
. - Lo que significa que el elemento superior de este montón siempre será el que se disparará antes.
- Seguiremos el patrón estándar de productor-consumidor de subprocesos.
- Tendremos un subproceso que se ejecutará en un ciclo infinito y enviará nuevos trabajos al grupo de subprocesos después de consumirlos de la cola. Llamémoslo QueueConsumerThread :
void goToSleep(job, jobQueue){
jobQueue.push(job);
sleep(job.nextExecutionTime() - getCurrentTime());
}
void executeJob(job, jobQueue){
threadpool.submit(job); // async call
if (job.isRecurring()) {
job = job.copy().setNextExecutionTime(getCurrentTime() + job.getRecurringInterval());
jobQueue.add(job);
}
}
@Override
void run(){
while(true)
{
job = jobQueue.pop()
if(job.nextExecutionTime() > getCurrentTime()){
// Nothing to do
goToSleep(job, jobQueue)
}
else{
executeJob(job, jobQueue)
}
}
}
- Habrá un subproceso más que monitoreará el archivo crontab en busca de nuevos trabajos agregados y los empujará a la cola.
- Llamémoslo QueueProducerThread :
@Override
void run()
{
while(true)
{
newJob = getNewJobFromCrontabFile() // blocking call
jobQueue.push(newJob)
}
}
- Sin embargo, hay un problema con esto:
- Imagine que Thread1 está durmiendo y se despertará después de una hora.
- Mientras tanto, llega una nueva tarea que se supone que debe ejecutarse cada minuto.
- Esta nueva tarea no podrá comenzar a ejecutarse hasta una hora más tarde.
- Para resolver este problema, podemos hacer que ProducerThread despierte a ConsumerThread de su suspensión con fuerza siempre que la nueva tarea deba ejecutarse antes que la tarea principal en la cola:
@Override
void run()
{
while(true)
{
newJob = getNewJobFromCrontabFile() // blocking call
jobQueue.push(newJob)
if(newJob == jobQueue.peek())
{
// The new job is the one that will be scheduled next.
// So wakeup consumer thread so that it does not oversleep.
consumerThread.interrupt()
}
}
}
Tenga en cuenta que es posible que no sea así como se implementa cron internamente. Sin embargo, esta es la solución más óptima que se me ocurre. No requiere sondeo y todos los subprocesos duermen hasta que necesitan hacer algún trabajo.