Arrêter un script Python lancé dans rc.local

Python est le langage de prédilection du Raspberry Pi

Modérateurs : Francois, Manfraid

Avatar du membre
zeb
Raspinaute
Messages : 280
Enregistré le : ven. 19 sept. 2014 11:04

Re: Arrêter un script Python lancé dans rc.local

Message par zeb » mer. 19 juil. 2017 17:09

Plop,

Je voudrais revenir sur le côté propre de l'affaire.

Voilà quelques posts maintenant, ici ou ailleurs, que je vois opposées à l'utilisation de kill d'autres techniques jurées plus propres. Je m'inscris en faux contre cette idée. kill n'est pas sale. En revanche, je l'admets son nom peut être trompeur.

Sous UNIX, ne partez pas en courant, on croise des [processus] zombies, des [services] démons et des kill[ers] :twisted: Sous ces noms aux consonances négatives se cachent de bien bonnes choses en fait, très positives. Jugez plutôt : respectivement, un père qui attend tous ses fils, un serviteur dévoué ou un lanceur d'alerte.

kill est un lanceur d'alerte de signal, pas un tueur. Et un signal est une très bonne chose pour converser avec un service. (Bon, il se trouve que si le signal est SIGKILL, le système ne le transmettra pas au processus : il tuera le processus :? Mais c'est un détail.)
SIGTERM est la bonne façon de demander à un service de bien vouloir se terminer.

Voilà le code académique d'un service :

Code : Tout sélectionner

trap "quitter_proprement" 1 2 3 15
while true ; do
    rendre_service
done
J'irai même plus loin : après un changement de configuration d'un service, on ne fait pas un redémarrage de celui-ci. On le lui signale, et il ira tout seul faire le nécessaire.

Allez visiter la page de gestion des signaux par un service très connu et osez revenir me dire que les signaux, c'est mal :ugeek: (http://httpd.apache.org/docs/2.2/fr/stopping.html)

_____________
EDIT: J'ai ajouté SIGINT (2) dans le code. Oubli repéré par Bud ;)
Modifié en dernier par zeb le jeu. 20 juil. 2017 12:25, modifié 1 fois.
Dans mon panier : rpi1A+ : »:: »:: | rpi1B : »:: »:: | rpi1B+ : »:: »:: | rpi2B : »:: »:: | rpi3B : »:: »:: | rpi0 : »::

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: Arrêter un script Python lancé dans rc.local

Message par Bud Spencer » mer. 19 juil. 2017 22:35

Ce n’est pas le fait d’utiliser un signal qui est ‘sale’, c’est le fait de le faire pour arrêter un programme qui n’a pas forcement de procédure pour le traiter. On ne sait pas qu’elles sont toutes les ressources initialisées avant sa boucle infernale et ce qui se fait dans cette boucle. Fatalement aucun niveau de signal ne peut garantir que toutes les ressources seront correctement libérées, que les différents buffers seront bien ‘flusher’ et j’en passe … En plus, on ne parle pas d’un service mais d’un programme sans doute lancé au démarrage par un cron ou une autre fainéantise du genre rc.local, ce qui fait une énorme différence (notamment dans le choix des signaux à prendre en compte pour une sortie ‘contrôlée’)

Pour moi, ce n’est ni un problème d’os (pour rassurer Sylvain) ni un problème de ‘propreté’ de kill (pour rassurer zeb). C’est juste un problème de programme mal conçu et mal démarré.

Pour revenir au PO :
passionrando a écrit :On trouve beaucoup de questions pour lancer un script Python au boot du RPI…
Dans ton cas d’usage, ce n’est juste pas la bonne question à se poser. ‘Comment daemoniser un script python’ serait beaucoup plus près de ton besoin.

La bonne méthode, serait d’utiliser la lib signal dans ton script (voir mon post précédent) pour procedurer les différents cas de signaux (1 3 15 pour un service comme l’a écrit zeb et ajouter 2 si le script est susceptible d’être lancé dans un terminal et stoppé par un ctrl+c (genre phase de test ou de debug)), puis daemoniser le script puisque cela semble plus s’apparenter à un service qu’a un programme. Ca répond au besoin de démarrage automatique, au besoin de pouvoir l’arrêter par une simple commande et ça laisse la possibilité de sortir correctement de cette boucle infernale pour libérer et remettre en état tout ce qui doit l’être avant de quitter. Bien sûr il y a d’autres méthodes, mais celle-ci me semble la plus adaptée et après tout, ce n’est qu’une suggestion …
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: Arrêter un script Python lancé dans rc.local

Message par Bud Spencer » jeu. 20 juil. 2017 17:02

Infernale les boucles avec des sockets ouvert ou des subprocess dedans hein ;-)

Essais d’ajouter ça en bonne place dans ton code, ca devrait répondre a un ctrl+c (je dis bien devrait. Je ne sais pas ce que tu as codé).

Code : Tout sélectionner

import signal
import sys

def sig_exit(s,f)
	sys.exit(0)

signal.signal(signal.SIGINT,sig_exit)
Si j’ai le temps ce soir, je te ferais un petit exemple de sortie propre d’une boucle infernal en utilisant un signal
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

passionrando
Messages : 7
Enregistré le : dim. 28 déc. 2014 22:57

Re: Arrêter un script Python lancé dans rc.local

Message par passionrando » jeu. 20 juil. 2017 17:10

Les gars...

Vous qui êtes apparement des experts puisque Python c'est de la m.... Les scripts lancés au boot via rc.local sont de la fainéantise et je vais m'arrêter là.... Vous aurez certainement la solution à mon probleme, plutôt que de me montrer mon ignorance.
Le but d'un forum c'est d'aider... Alors je sais que je n'y connais rien en Linux/UNIX hormis LS-l et CAT mais j'ai besoin de lancer un programme Python au boot (suite à coupure secteur) et de pouvoir le stopper pour avoir avec au login.
Je suis sure de maîtriser des sujet dans lesquels vous serez novices... A chacun ses trucs. La j'ai besoin de votre aide et je vous en remercie d'avance.

Donc qu'elle est la solution ?
Dans ma boucle de débutant je viens écouter un socket et afficher ce qui est vu à l'écran, c''est tout.

Pour le PID, j'avais déjà essayé, mais je ne vois rien qui corresponde à Python ou autre...

Michael

destroyedlolo
Raspinaute
Messages : 1585
Enregistré le : dim. 10 mai 2015 18:44
Localisation : Dans la campagne à côté d'Annecy
Contact :

Re: Arrêter un script Python lancé dans rc.local

Message par destroyedlolo » jeu. 20 juil. 2017 17:12

passionrando a écrit :J'ai un problème pour arreter un script lancé au démarrage via le fichier rc.local. CTRL+C n'a pas d'effet et je ne peux faire de sortie propre...
Si, faire un

Code : Tout sélectionner

kill [i]numéro_du_process[/i]
revient exactement au même que faire un CTRL-C si tu le lançais depuis un terminal. Les échanges ci dessus sont fait pour t'indiquer qu'il peut être bien (pas obligatoire, ca dépend de ce que fait ton appli) d'y rajouter un trap pour que signal déclenche une procédure de ménage avant l’arrêt, à nouveau si nécessaire.

Pour aller plus loin :
  • un kill tout court fait un arrêt "propre" et passe donc par le trap qui tu aurais pu mettre, mais aussi ceux implicites dans les librairies (hooks) ... en d'autres termes, tu n'as pas a te soucier des caches, des buffers et autres joyeusetés.
  • un kill -9 arrète brutalement le process, sans passer par les traps ou le hooks et donc là, ca peut tout péter !
passionrando a écrit :Passionrando
Ou les rando ?

A+
  • BananaPI : Gentoo, disque SATA de 2 To
  • Domotique : 1-wire, TéléInfo, Tablette passée sous Gentoo, ESP8266
  • Multimedia par DNLA
  • Et pleins d'idées ... et bien sûr, pas assez de temps.
Un descriptif de ma domotique 100% fait maison.

passionrando
Messages : 7
Enregistré le : dim. 28 déc. 2014 22:57

Re: Arrêter un script Python lancé dans rc.local

Message par passionrando » jeu. 20 juil. 2017 17:47

Par exemple...

Sinon merci.
Je reviens sur le PID car j'ai effectivement un numero j'avais fait un ps sans le -et
Je vais donc pouvoir faire... Je vais tester.

Merci.

Mais si quelqu'un peut m'expliquer pourquoi Ctrl C marche quand je suis logué et pas quand ca demarre tout seul.. J'aurai alors encore appris quelque chose.

destroyedlolo
Raspinaute
Messages : 1585
Enregistré le : dim. 10 mai 2015 18:44
Localisation : Dans la campagne à côté d'Annecy
Contact :

Re: Arrêter un script Python lancé dans rc.local

Message par destroyedlolo » jeu. 20 juil. 2017 17:58

passionrando a écrit :Mais si quelqu'un peut m'expliquer pourquoi Ctrl C marche quand je suis logué et pas quand ca demarre tout seul.. J'aurai alors encore appris quelque chose.
Parce que ca dépend du process pere du tiens (aucun rapport avec Luke Skywalker).

Si tu le lance depuis un terminal, le process père du tien est le dit terminal, donc un CTRL_C fait dans le terminal est propagé à son fils, à savoir ton process. Donc il s’arrêtera.
Si tu le lance depuis le rc.local, le process père est le process d'init, celui qui a lancé le rc.local. Comme un CTRL_C ne signale que le terminal ou la console actif (qui a le focus donc), il n'affecte pas le process init et donc par ricochet, n'arrive jamais à ton process ... qui ne s'arretera pas.
Modifié en dernier par destroyedlolo le ven. 21 juil. 2017 00:37, modifié 1 fois.
  • BananaPI : Gentoo, disque SATA de 2 To
  • Domotique : 1-wire, TéléInfo, Tablette passée sous Gentoo, ESP8266
  • Multimedia par DNLA
  • Et pleins d'idées ... et bien sûr, pas assez de temps.
Un descriptif de ma domotique 100% fait maison.

Bud Spencer
Raspinaute
Messages : 1089
Enregistré le : lun. 15 août 2016 21:38

Re: Arrêter un script Python lancé dans rc.local

Message par Bud Spencer » jeu. 20 juil. 2017 23:04

passionrando a écrit :Les gars...
Vous qui êtes apparement des experts puisque Python c'est de la m.... Les scripts lancés au boot via rc.local sont de la fainéantise et je vais m'arrêter là....
Tu ne devrais pas être susceptible comme ça. Tous les gens qui viennent répondre à ta demande le font avec leurs compétences (quelle qu’elles soient) mais généralement le font de bon cœur (ou parfois juste pour se faire mousser en écrivant n’importe quoi sur des sujets non maitrisés mais les cas sont rares et connus). En ce qui me concerne, quand je parle de fainéantise d’un rc.local ou d’un cron pour se substituer à la bonne méthode de démarrage d’un service, c’est tout simplement parce que je le fais aussi quand je fais de l’expérimental et que j’ai la flemme de m’infuser un fichier de service init.d et aussi la flemme de coder la gestion des différents signaux indispensables aux bonnes pratiques qui vont avec. Dis-toi bien que comme beaucoup je suis ici totalement anonyme, que je ne te connais pas et par conséquent, je ne vois pas ce que j’aurais à gagner à te traiter de fainéant ou d’incompétent. Par contre, si ce que j’écris peut t’aider à avancer ou à comprendre quelque chose, j’aurais au moins la satisfaction de ne pas avoir perdu mon temps.

Trêve de courbettes et d’excuses, revenons-en aux signaux pour en finir une bonne fois pour toute avec ça (ce qui ne t’exemptera pas de te documenter dessus pour mieux les comprendre et les maitriser). Pas de baratin incompréhensible pour le débutant que tu es (ce n’est pas une insulte) mais juste de l’exemple à la fois simple, concret et explicite qui je l’espère te servira. Tu noteras que même si j'ai ce langage en horreur (comparé à d'autre), je n'hésite a me faire violence en l'utilisant pour essayer de rendre service ;)

Voilà un premier petit code python tout bête que tu devrais comprendre facilement. Il ne fait que lancer une boucle infernale (j’insiste sur le infernale). On a ouvert un fichier txt et à chaque tour de la boucle, on incrémente la variable i et on l’écrit dans notre fichier. Un cas d’école en quelque sorte.
sig_sample1.png
sig_sample1.png (24.79 Kio) Vu 5964 fois
La procédure de test est simple, il suffit de lancer le programme, de le laisser tourner 20 ou 30 secondes et de stopper le processus en envoyant un simple kill n°Pid depuis un autre terminal (ici pas besoin de chercher le n° de pid, il s’affiche au lancement du programme). Dans la fenêtre de terminal qui faisait tourner le programme, tu vas voir qu’il a bien reçu le signal et qu’il s’est bien arrêté comme prévu. Maintenant allons voir le contenu du fichier text.txt … Dans 99.9% des cas il sera vide ou incomplet :o .

Second test. Cette fois ci on reprend le même programme mais on va ajouter un peu de code juste pour utiliser le signal afin de sortir proprement de cette boucle infernale.
sig_sample2.png
sig_sample2.png (45.04 Kio) Vu 5964 fois
Lancons le nouveau test jusqu’à compter toujours jusqu’à 20 ou 30 et renvoyons un même kill n°Pid (attention, le pid a forcément changé). Cette fois ci aussi, le programme s’est arrêté toujours comme prévu. Allons maintenant voir ce que contient notre fichier text.txt et … hoooo miracle, il contient l’intégralité des données :D .

Explication : Comme tous les langages et tous les systèmes, python a ses petites manies. Un truc de bien, c’est qu’il ferme automatiquement les fichiers ouvert quand il s’arrête. Par contre, il ne pousse les données qui sont encore dans le buffer d’écriture que si le fichier est fermé proprement par une instruction fichier.close(). Des cas d’anomalies comme ça (qui en fait n’en sont pas), je pourrais t’en citer des dizaines dans une dizaine de langages différents. Ici, rien de bien grave, on ne perd que des données, mais dans d’autre cas, cela peut être des sockets ou des fichiers qui deviennent inaccessible parce que pas fermés correctement, des connections à des bases de données qui persistent, du hardware qui reste occupé ect ect ect …

Dans le premier exemple, le kill arrête le programme forcement l’intérieur de la boucle, donc l’instruction f.close() n’est jamais atteinte. D’ailleurs comment le serait-elle puisque aucune interruption ou instruction ne permet de sortir de la boucle (ce que je traduis par infernale).Dans le second exemple, c’est différent. On a conditionné la boucle sur l’état de la variable wk et on modifie cette var quand on reçoit une demande d’arrêt SIGTERM (ou kill ou kill -15, tout ça c’est pareil). Une fois cette var passée à False, on sort normalement de la boucle et le programme peut poursuivre. En l’occurrence il ferme correctement le fichier par l’instruction f.close() puis retourne à la proc main pour se terminer normalement.

Donc non, un signal n’est pas une façon sale de sortir d’un programme. Ici on a utilisé le même signal et pourtant il y a une sortie douteuse et une sortie correcte. En fait, si le programme n’est pas conçu pour se terminer normalement quand il reçoit un signal d’arrêt alors il aura beaucoup de chance de s’arrêter ‘salement’. C’est toute la différence entre une programme qui s’arrête et un programme qui se termine (et la nuance n’est pas un détail).

Voila, je ne sais pas te dire quoi de plus. J’ai essayé d’être simple dans le code et en évitant les termes trop technique qui finalement font plus de mousse que de flamme. Quoi qu’il en soit, te voilà avec une méthode toute simple qui va te permettre de sortir proprement de tes boucles infernale et laisser tes programmes se terminer tout aussi 'proprement'. Ici l’exemple utilisait un simple kill (equivalent SIGTERM) , mais si tu veux que cette sortie soit tout aussi élégante en faisant un ctrl+c (équivalent SIGINT ou kill -2), il te suffit de rajouter la ligne suivante :
signal.signal(signal.SIGINT,sig_exit).

PS : une info au cas où tu ne l’aurais pas saisie ou mal compris. Tu n’es pas obligé d’envoyer ta commande kill depuis le terminal qui affiche les sorties de ton programme. Tu peux le faire depuis une autre session terminale locale ou depuis une connexion ssh distante. Une autre combine si tu ne veux pas avoir à chercher le pid du programme à chaque fois, c’est de l’écrire dans un fichier temporaire au démarrage du programme et écrire un petit batch qui le récupèrerait et lancerait ta bonne commande kill pour arrêter ton programme. Quand tu en seras au stade de la daemonisation, tu verras que ce n’est qu’une idée simplifiée de ce qui se fait normalement. (l’instruction pour récupèrer le pid dans un prog python est dans les exemple : ‘os.getpid()’ )
Le premier ennemi de la connaissance n’est pas l’ignorance, c’est l’illusion de la connaissance (S. Hawking).

passionrando
Messages : 7
Enregistré le : dim. 28 déc. 2014 22:57

Re: Arrêter un script Python lancé dans rc.local

Message par passionrando » ven. 21 juil. 2017 10:23

Merci Bud,

Oui j'avoue que sur ce coup... fin de journée faisant, j'ai été très succeptible.
La raison est que je ne comprends pas pourquoi globalement sur les Forum on pose une question toute simple et ca part toujours en vrie ! Sans forcément donner une réponse claire au problème, En tous cas clair pour le (les) novices qui posent la questions. Ils ont envie de comprendre et de progresser et personne n'a la science infuse... (comme personne d'ailleurs) donc oui, c'est vrai que certains commentaires peuvent agasser... (Je parle toujours en général et ne vise personne)

Ceci dit... merci pour ta longue explication. Je pense avoir compris malgré mon faible niveau Linux/Python :)
Je vais tenter de l'adapter à mon cas, Mon problème étant toujours que le CTRL+C ne fonctionne pas.
Mais si je peux arreter le processus depuis une autre console ou même depuis le RPI qui heberge le prog. avec un KILL ou autre méthode, même avec pertes de données (car peu d'importance) me voilà sauvé ! Ca je ne l'avais pas compris. Merci de l'avoir précisé en fin de Post :P

En fait pour aller au fond des choses et expliquer pourquoi je veux faire ceci, si déjà je pose la question et que vous m'aidez à comprendre...
J'ai une installation domotique, avec une boucle qualifiée d'infernale j'écoute tout ce qui passe par le LAN (ce sont des trames OpenWebNet)
J'intercepte une trame bien précise et selon les données qu'elle contient, je m'envoie un mail d'avertissement. Tout ceci fonctionne très bien.

Le problème est que dans mon secteur j'ai souvent des coupures de courant. Du coup, lors du reboot du RPI, je dois relancer automatiquement mon programme.
Et le fait de le mettre dans rc.local ne me permet pas de le couper (si besoin) via CTRL+C. Pourquoi ? Ca ???
La solution que j'avais envisagée est de faire qu'une trame précise m'arrete la boucle et sorte proprement du prog. (style quand j'allume la lumière des WC et que je monte les volets de la chambre 1 alors j'arrete ma boucle :lol: ) pas super, mais au moins ce serait propre ! :lol:
Sinon: Plan B : si appui sur un BP via un port GPIO, je quitte le prog. Mais comme mon RPI est juste sur le LAN... ça ne va pas non plus...

Je vais donc voir la solution qui me convient le mieux dans tout ce qui m'a été proposé.

PassionRando

spourre
Raspinaute
Messages : 735
Enregistré le : lun. 22 déc. 2014 16:50
Localisation : 67380 LINGOLSHEIM

Re: Arrêter un script Python lancé dans rc.local

Message par spourre » ven. 21 juil. 2017 11:18

passionrando a écrit : ....
Le problème est que dans mon secteur j'ai souvent des coupures de courant. Du coup, lors du reboot du RPI, je dois relancer automatiquement mon programme.
Et le fait de le mettre dans rc.local ne me permet pas de le couper (si besoin) via CTRL+C. Pourquoi ? Ca ???
....
Je vais donc voir la solution qui me convient le mieux dans tout ce qui m'a été proposé.
AMHA, le premier problème à résoudre est de sécuriser l'alimentation et l'arrêt propre du Pi en cas de panne secteur.
A force d'arrêt brutal et de reboot sauvage, vous n'aurez bientôt (quasi certitude), plus de process lancé par rc.local à tuer car vous n'aurez plus aucun process du tout.
Tôt ou tard, votre carte SD va vous lâcher.
Je ne développe pas davantage car ce n'est pas éxactement l'objet de votre question initiale et je ne voudrais pas déclencher votre irre :evil:
Sylvain

Répondre

Retourner vers « Python »