Pagina 1 di 1

[Risolto][Python] modificare crontab - lock file ?

Inviato: giovedì 10 gennaio 2019, 18:29
da rai
Ciao a tutto il forum.

Lo script1 (che ha i permessi da utente) prima di uscire aggiunge al crontab utente un job che lancerà il comando script2 unCertoParametro.
A volte ci sono istanze, più o meno contemporanee, dello script1 che aggiungono ciascuna il proprio job e quindi il crontab diventa di questo tipo

Codice: Seleziona tutto

# ...
*/15 * * * * script2 parametro 
*/15 * * * * script2 altroParametro 
*/15 * * * * script2 parametroDiverso 
# ...
Lo script2, se non si verifica una certa condizione esce in silenzio per essere rieseguito dal crontab. Altrimenti, fatto il suo lavoro, prima di uscire cancella dal crontab il job che lo ha eseguito.

Ecco il problema:
a volte, senza che si sollevi nessun errore, nel crontab ritrovo delle righe residue, non cancellate.
Credo che questo si verifichi quando due diverse istanze di script2 tentano "contemporaneamente" di modificare il crontab.
Non saprei come riprodurre la cosa ma la posso simulare: apro due diversi terminali, eseguo in ciascuno il comando crontab -e faccio modifiche diverse e salvo. Quello che si verifica è che
# apparentemente non c'è un lock file, quando si salva non c'è alcun avviso di errore
# la versione di crontab che rimane è quella salvata per ultima

Scritta in bash, la cancellazione del job sarebbe:

Codice: Seleziona tutto

crontab -l | grep -vF $suoParametro | crontab -
il codice corrispondente nello script2 è:

Codice: Seleziona tutto

import shlex
import subprocess
import sys

suoParametro = sys.argv[1]

p1 = subprocess.Popen(shlex.split("crontab -l"), stdout=subprocess.PIPE)
p2 = subprocess.Popen(shlex.split('grep -vF "%s"' %suoParametro),
                                  stdin=p1.stdout,
                                  stdout=subprocess.PIPE)
p1.stdout.close()
subprocess.Popen(shlex.split("crontab -"), stdin=p2.stdout)
Lo script2 usa già il modulo fcntl per creare dei File-Lock in altri casi ma qui non vedo come posso fare ad applicarlo
Qualsiasi suggerimento/sovvertimento è benvenuto, considerando comunque che il motivo per usare cron invece di un sistema pure Python è garantire la riesecuzione di script2 anche dopo un eventuale riavvio del pc

Grazie per l'attenzione fino a qui.

Re: [Python]modificare crontab - lock file ?

Inviato: venerdì 11 gennaio 2019, 8:41
da melfnt
rai [url=https://forum.ubuntu-it.org/viewtopic.php?p=5107167#p5107167][img]https://forum.ubuntu-it.org/images/icons/icona-cita.gif[/img][/url] ha scritto:Ciao a tutto il forum.

Lo script1 (che ha i permessi da utente) prima di uscire aggiunge al crontab utente un job che lancerà il comando script2 unCertoParametro.
A volte ci sono istanze, più o meno contemporanee, dello script1 che aggiungono ciascuna il proprio job e quindi il crontab diventa di questo tipo

Codice: Seleziona tutto

# ...
*/15 * * * * script2 parametro 
*/15 * * * * script2 altroParametro 
*/15 * * * * script2 parametroDiverso 
# ...
Lo script2, se non si verifica una certa condizione esce in silenzio per essere rieseguito dal crontab. Altrimenti, fatto il suo lavoro, prima di uscire cancella dal crontab il job che lo ha eseguito.

Ecco il problema:
a volte, senza che si sollevi nessun errore, nel crontab ritrovo delle righe residue, non cancellate.
Credo che questo si verifichi quando due diverse istanze di script2 tentano "contemporaneamente" di modificare il crontab.
È difficile verificare che l'errore sia dovuto proprio al il motivo che hai detto tu, potrebbe essere anche un errore logico nello script2, che si interrompe prima di arrivare al punto in cui modifica il crontab.
Quindi vai pure avanti a con i lock file ma controlla anche come vengono gestiti/riportati gli errori di script2, tanto più che viene eseguito da cron.

Un'altra cosa "strana" è che il crontab non è un file, o meglio lo è ma tu lo modifichi usando il comando crontab - e non scrivendo direttamente sul file. Quindi non so se funzionano i lockfile nel senso classico.

Non saprei come riprodurre la cosa ma la posso simulare: apro due diversi terminali, eseguo in ciascuno il comando crontab -e faccio modifiche diverse e salvo. Quello che si verifica è che
# apparentemente non c'è un lock file, quando si salva non c'è alcun avviso di errore
# la versione di crontab che rimane è quella salvata per ultima

Scritta in bash, la cancellazione del job sarebbe:

Codice: Seleziona tutto

crontab -l | grep -vF $suoParametro | crontab -
il codice corrispondente nello script2 è:

Codice: Seleziona tutto

import shlex
import subprocess
import sys

suoParametro = sys.argv[1]

p1 = subprocess.Popen(shlex.split("crontab -l"), stdout=subprocess.PIPE)
p2 = subprocess.Popen(shlex.split('grep -vF "%s"' %suoParametro),
                                  stdin=p1.stdout,
                                  stdout=subprocess.PIPE)
p1.stdout.close()
subprocess.Popen(shlex.split("crontab -"), stdin=p2.stdout)
Lo script2 usa già il modulo fcntl per creare dei File-Lock in altri casi ma qui non vedo come posso fare ad applicarlo
Ti suggerisco di implementare "a mano" un sistema di lock molto rudimentale, che usa un file (chiamiamolo /tmp/cron.lock) la cui presenza indica che c'è un'istanza di script2 che sta usando il comando crontab.
Quindi, script2 innanzitutto controlla la presenza del suddetto file: se non c'è lo crea, usa cron e poi lo cancella; se invece il file esiste entra in un loop in cui aspetta un po' (diciamo mezzo secondo) e poi controlla nuovamente l'esistenza del file.

PSEUDOCODICE:

Codice: Seleziona tutto

fname = "/tmp/cron.lock"

finché fname esiste:
    aspetta 0.5 secondi
crea fname
usa cron
cancella fname
In alcuni sfortunati casi due istanze di script2 potrebbero controllare l'esistenza del file contemporaneamente e trovare entrambi che non esistono, quindi modificheranno insieme il crontab e la versione che rimarrà sarà quella salvata per ultima.
Sicuramente ci saranno altri mille metodi più efficienti e meno inclini a errori, ma questo è giusto un tentativo per vedere se il problema sono le modifiche concorrenti oppure no.
Qualsiasi suggerimento/sovvertimento è benvenuto, considerando comunque che il motivo per usare cron invece di un sistema pure Python è garantire la riesecuzione di script2 anche dopo un eventuale riavvio del pc

Grazie per l'attenzione fino a qui.
Altrimenti ti consiglio di non usare proprio cron e di gestire "a mano" il reboot, forse è più semplice, oppure ancora di calcolarti l'orario preciso in cui eseguire i comandi e usare la clausola at di crontab: in questo modo i job verranno eseguiti una sola volta e non ci sarà bisogno di cancellarli.

:)

Re: [Python]modificare crontab - lock file ?

Inviato: venerdì 11 gennaio 2019, 19:32
da rai
Prima di tutto grazie per l'interessamento e per la risposta :)
melfnt ha scritto:potrebbe essere anche un errore logico nello script2, che si interrompe prima di arrivare al punto in cui modifica il crontab
Questo no, con certezza: l'ultima azione dello script è una notifica degli esiti delle sue attività e la notifica non manca mai.

melfnt ha scritto:Un'altra cosa "strana" è che il crontab non è un file, o meglio lo è ma tu lo modifichi usando il comando crontab - e non scrivendo direttamente sul file. Quindi non so se funzionano i lockfile nel senso classico.
Esatto. Se si usa il blocco del file /var/spool/cron/crontabs/utente non funziona, viene aggirato. Per provare: eseguire questo script come root e in un altro terminale modificare il proprio crontab con il comando `crontab -e' o il comando `crontab -' in una pipe

Codice: Seleziona tutto

#!/usr/bin/env Python3
import fcntl
import time
utente = 'nomeUtente'                                                           # XXX SETME
crontab = '/var/spool/cron/crontabs/%s'%utente
with open(crontab, 'a') as f_l:
    try:                    # blocca il file se non lo è già
        fcntl.flock(f_l, fcntl.LOCK_EX | fcntl.LOCK_NB)
    except IOError:         # se il file è bloccato
        pass                # non fa niente
    input("Premi Enter per sbloccare il crontab...")
    fcntl.flock(f_l, fcntl.LOCK_UN)   # elimina il blocco
Se invece (con Python) modifico direttamente il file (il che è deprecato o almeno sconsigliato) il blocco del file funziona. Però per bloccarlo bisogna essere root o essere del gruppo crontab. Invece ho già scritto che script2 ha i permessi utente.


melfnt ha scritto:Ti suggerisco di implementare "a mano" un sistema di lock molto rudimentale, che usa un file (chiamiamolo /tmp/cron.lock) la cui presenza indica che c'è un'istanza di script2 che sta usando il comando crontab.
mi pare che avrebbe più o meno la stessa probabilità di fallire del mio codice senza lock
Attualmente, se non sbaglio, l'errore dovrebbe verificarsi solo se una seconda istanza dello script2 esegue la riga
p1 = subprocess.Popen(shlex.split("crontab -l"), stdout=subprocess.PIPE # lettura
prima che la prima istanza di script2 esegua la riga
subprocess.Popen(shlex.split("crontab -"), stdin=p2.stdout) # scrittura

La probabilità di errore è maggiore per il fatto che l'esecuzione di tutti gli script2 programmati parte allo stesso istante, allo scadere dei 15' :muro:
Questa è una cosa che si può aggiustare, devo capire come.
melfnt ha scritto:Altrimenti ti consiglio di non usare proprio cron e di gestire "a mano" il reboot, forse è più semplice, oppure ancora di calcolarti l'orario preciso in cui eseguire i comandi e usare la clausola at di crontab: in questo modo i job verranno eseguiti una sola volta e non ci sarà bisogno di cancellarli.
La situazione è che script2 aspetta una mail il cui arrivo può tardare ore dalla esecuzione di script1. Ecco perchè deve potersi eseguire ripetutamente e perchè prevedo la possibilità di interruzioni di alimentazione, etc.

Come sarebbe, gestire "a mano" il reboot? Potresti entrare nel dettaglio?

Re: [Python]modificare crontab - lock file ?

Inviato: domenica 13 gennaio 2019, 12:08
da melfnt
melfnt ha scritto:Ti suggerisco di implementare "a mano" un sistema di lock molto rudimentale, che usa un file (chiamiamolo /tmp/cron.lock) la cui presenza indica che c'è un'istanza di script2 che sta usando il comando crontab.
mi pare che avrebbe più o meno la stessa probabilità di fallire del mio codice senza lock
Attualmente, se non sbaglio, l'errore dovrebbe verificarsi solo se una seconda istanza dello script2 esegue la riga
p1 = subprocess.Popen(shlex.split("crontab -l"), stdout=subprocess.PIPE # lettura
prima che la prima istanza di script2 esegua la riga
subprocess.Popen(shlex.split("crontab -"), stdin=p2.stdout) # scrittura

La probabilità di errore è maggiore per il fatto che l'esecuzione di tutti gli script2 programmati parte allo stesso istante, allo scadere dei 15' :muro:
Questa è una cosa che si può aggiustare, devo capire come.
No, penso che la probabilità di errore sia bassissima: si verificherebbe un errore solo se due istanze di script2 eseguono contemporaneamente l'istruzione os.path.isfile() (o una simile per controllare che il file esista).
Anche se le due istanze sono state avviate contemporaneamente, è altamente improbabile che ciò si verifichi. Non so quantificare perché le probabilità di errore dipendono da moltissime cose: il sistema su cui girano i programmi, interleaving fra processi, come vengono gestite più chiamate di sistema contemporanee sullo stesso file...

Comunque è chiaro che visto che non è implementata alcuna forma di mutua esclusione una certa probabilità di errore c'è sempre. Se le istanze di script2 fossero thread dello stesso processo oppure processi tutti figli dello stesso padre saprei anche come implementare la mutua esclusione "vera", ma visto che sono processi differenti dovresti usare una named pipe o qualcosa di simile.

Ti consiglio di provare il mio metodo, per valutare le probabilità di errore.
melfnt ha scritto:Altrimenti ti consiglio di non usare proprio cron e di gestire "a mano" il reboot, forse è più semplice, oppure ancora di calcolarti l'orario preciso in cui eseguire i comandi e usare la clausola at di crontab: in questo modo i job verranno eseguiti una sola volta e non ci sarà bisogno di cancellarli.
La situazione è che script2 aspetta una mail il cui arrivo può tardare ore dalla esecuzione di script1. Ecco perchè deve potersi eseguire ripetutamente e perchè prevedo la possibilità di interruzioni di alimentazione, etc.

Come sarebbe, gestire "a mano" il reboot? Potresti entrare nel dettaglio?
Un modo stupido per gestire il reboot: Hai un solo processo che funge da script1.
Ogni volta che script2 deve essere eseguita, crea un file diverso in una specifica cartella (diciamo scheduled_jobs) e ci scrive alcune informazioni utili. Poi schedula un'istanza di script2 da eseguire alla data e ora opportuni (lo scheduling viene fatto usando un meccanismo interno di python, non con crontab).
Alla data specificata, il codice di script2 viene eseguito e il file cancellato.

Questo è il funzionamento "normale", nel caso in cui non ci siano errori. Se capita un'interruzione di alimentazione, al reboot viene rieseguito script1 che per prima cosa controlla se ci sono file presenti nella cartella sheduled_jobs. Per ogni file presente in quella cartella schedula un'instanza di script2 partendo dalle informazioni presenti nel file.

:)

Re: [Python]modificare crontab - lock file ?

Inviato: martedì 15 gennaio 2019, 14:11
da rai
Proverò il tuo metodo, tanto non costa niente :)
ma secondo te, l'evento che citavo come meccanismo di errore del mio attuale sistema senza lock ha una probabilità molto più alta di verificarsi? non è una domenda retorica, lo chiedo per sapere

Grazie anche per questo:
Un modo stupido per gestire il reboot: Hai un solo processo che funge da script1.
Ogni volta che script2 deve essere eseguita, crea un file diverso in una specifica cartella (diciamo scheduled_jobs) e ci scrive alcune informazioni utili. Poi schedula un'istanza di script2 da eseguire alla data e ora opportuni (lo scheduling viene fatto usando un meccanismo interno di python, non con crontab).
Alla data specificata, il codice di script2 viene eseguito e il file cancellato.

Questo è il funzionamento "normale", nel caso in cui non ci siano errori. Se capita un'interruzione di alimentazione, al reboot viene rieseguito script1 che per prima cosa controlla se ci sono file presenti nella cartella sheduled_jobs. Per ogni file presente in quella cartella schedula un'instanza di script2 partendo dalle informazioni presenti nel file.
mmh... perché no?

Re: [Python]modificare crontab - lock file ?

Inviato: martedì 15 gennaio 2019, 15:34
da melfnt
rai [url=https://forum.ubuntu-it.org/viewtopic.php?p=5107996#p5107996][img]https://forum.ubuntu-it.org/images/icons/icona-cita.gif[/img][/url] ha scritto:Proverò il tuo metodo, tanto non costa niente :)
ma secondo te, l'evento che citavo come meccanismo di errore del mio attuale sistema senza lock ha una probabilità molto più alta di verificarsi? non è una domenda retorica, lo chiedo per sapere
Non sono sicuro di aver capito la domanda, ma provo a risponderti lo stesso:
l'evento di cui parli consiste nel fatto che due processi richiamino il comando crontab simultaneamente, o meglio, si verifica l'errore tutte le volte che la seconda istanza di script2 richiama il comando crontab mentre la prima istanza di script2 sta a sua volta usando crontab.
Crontab rimane aperto per leggere il file di input (o la pipe, poco cambia), poi per fare i controlli sulla sintassi dei comandi e poi per scrivere il file crontab, tutto questo richiede un certo tempo.
La probabilità di errore è proporzionale al tempo impiegato dal comando crontab, che è particolarmente lungo se usi crontab -e e modifichi il crontab con l'editor.

Usando il lockfile implementato a mano, l'errore si verifica solo se le due istanze dello script effettuano la chiamata di sistema contemporaneamente, o meglio se la seconda istanza di script2 esegue os.path.isfile mentre la prima sta eseguendo a sua volta os.path.isfile.
Anche qui, la probabilità di errore è proporzionale alla durata di questa chiamata di sistema.
Ora, os.path.isfile è implementato come un wrapper attorno a stat e poco altro: il suo tempo di esecuzione è molto più breve di quello della chiamata a crontab (che richiede forkare il processo, eseguire una shell, eseguire il comando crontab vero e proprio).

Alcuni test senza troppa valenza scientifica ma che almeno dovrebbero rendere l'idea:

Codice: Seleziona tutto

$ cat script.py 
import shlex
import subprocess
import sys
import os

suoParametro = "pippo"
fname = "/tmp/cron.lock"

def test_crontab ():
    p1 = subprocess.Popen(shlex.split("crontab -l"), stdout=subprocess.PIPE)
    p2 = subprocess.Popen(shlex.split('grep -vF "%s"' %suoParametro),stdin=p1.stdout,stdout=subprocess.PIPE)
    p1.stdout.close()
    subprocess.Popen(shlex.split("crontab -"), stdin=p2.stdout)

def test_isfile ():
    os.path.isfile(fname)

$ python
Python 3.6.8 (default, Dec 24 2018, 19:24:27) 
[GCC 5.4.0 20160609] on linux
Type "help", "copyright", "credits" or "license" for more information.
>>> timeit.timeit ("test_crontab()", setup="from script import test_crontab", number=1000)
3.66222156799995
>>> timeit.timeit ("test_isfile()", setup="from script import test_isfile", number=1000)
0.002215838999745756

Sul mio sistema il tempo di esecuzione della syscall è tre ordini di grandezza inferiore a quello del comando crontab, quindi la probabilità di errore è circa un millesimo.

Usando i lockfile di sistema (che in questo caso però non funzionano), la probabilità di errore è zero perché sono implementati in modo che il test sull'esistenza del file di lock venga eseguito sempre in mutua esclusione fra i processi.

Spero di essere stato esauriente :)

P.S. potresti anche usare il lockfile su un file che non usi veramente, solo per garantire la mutua esclusione fra processi mentre usano il crontab.

Codice: Seleziona tutto

fname = '/tmp/crontab.lock'
lock_acquired = False

with open(fname, 'a') as f_l:
    while not lock_acquired:
        try:                    # blocca il file se non lo è già
            fcntl.flock(f_l, fcntl.LOCK_EX | fcntl.LOCK_NB)
            lock_acquired = True
        except IOError:         # se il file è bloccato
            time.sleep(0.5)                # aspetta mezzo secondo e poi riprova
    
    # USA IL CRONTAB IN MUTUA ESCLUSIONE

    fcntl.flock(f_l, fcntl.LOCK_UN)   # elimina il blocco


Re: [Python]modificare crontab - lock file ?

Inviato: martedì 15 gennaio 2019, 20:57
da rai
Ammazza, altro che esauriente! :birra: e per fortuna che non eri sicuro di avere capito la domanda, se no mi facevi una pubblicazione :D
Comunque avevi capito benissimo e grazie molte per i test e per lo spiegone a prova di scemo, non mi era balenato di "quantificare" la cosa con timeit
:ciao:

Re: [Python]modificare crontab - lock file ?

Inviato: mercoledì 16 gennaio 2019, 10:22
da melfnt
rai [url=https://forum.ubuntu-it.org/viewtopic.php?p=5108102#p5108102][img]https://forum.ubuntu-it.org/images/icons/icona-cita.gif[/img][/url] ha scritto:Ammazza, altro che esauriente! :birra: e per fortuna che non eri sicuro di avere capito la domanda, se no mi facevi una pubblicazione :D
Comunque avevi capito benissimo e grazie molte per i test e per lo spiegone a prova di scemo, non mi era balenato di "quantificare" la cosa con timeit
:ciao:
Tranquillo, per approfondire leggi il problema del troppo latte scusa ma non riesco a trovare un link migliore.


E hai risolto così?
P.S. potresti anche usare il lockfile su un file che non usi veramente, solo per garantire la mutua esclusione fra processi mentre usano il crontab.

Codice: Seleziona tutto

    fname = '/tmp/crontab.lock'
    lock_acquired = False

    with open(fname, 'a') as f_l:
        while not lock_acquired:
            try:                    # blocca il file se non lo è già
                fcntl.flock(f_l, fcntl.LOCK_EX | fcntl.LOCK_NB)
                lock_acquired = True
            except IOError:         # se il file è bloccato
                time.sleep(0.5)                # aspetta mezzo secondo e poi riprova
       
        # USA IL CRONTAB IN MUTUA ESCLUSIONE
        fcntl.flock(f_l, fcntl.LOCK_UN)   # elimina il blocco

Re: [Risolto][Python] modificare crontab - lock file ?

Inviato: mercoledì 16 gennaio 2019, 19:14
da rai
Grazie per il link, molto didascalico e chiaro.

Sì eseguo il blocco su un diverso lockfile.
Sto ancora usando crontab e non ho ancora implementato la gestione del riavvio dallo stesso Python perchè in realtà le istanze di script1 vengono avviate manualmente, a partire dal File Manager dell'ambiente Desktop. Quindi mi pare di capire che, al riavvio, cron (o chi per lui) dovrebbe eseguire script3 che si prende carico di rieseguire tutte le istanze necessarie di script2 che erano state schedulate su file dalle corrispondenti istanze di script1.

Questo avrebbe lo svantaggio di avere sempre script3 in "busy-waiting", mentre con il crontab è tutto gestito dal demone crond. È più una resistenza psicologica che altro, eh :D non credo che causerebbe chissà che consumo di risorse