Forum: PC-Programmierung C/C++, Linux: Veriegelung das ein Prozess nur genau 1 mal läuft


von cppbert (Gast)


Lesenswert?

Hallo,

im Grunde geht es um den simple Windows-Named-Mutex Trick für Linux um 
zu erkennen ob ein Programm schon mal gestartet wurde - nur mit der 
Sicherheit das ein Terminate oder Absturz nicht den erneuten Start 
blockiert (was in unserem Test-Szenario häufiger vorkommt)

eine einfache Datei reicht nicht weil ich die nicht löschen wenn ich 
durch ein Plugin-Fehler terminiert werde

Es geht mir nicht darum die Abstürze zu korrigieren weil es um ein 
Plugin-Loader-Tester geht der Plugins von anderen Entwicklern testet die 
es nicht so genau nehmen mit der Qualität - deswegen auch das Testsystem 
:)

Bisher habe ich folgende gefunden

Memory mapped file - scheinbar auch möglich das es nach dem Prozess-Ende 
definitiv auch wieder vom Betriebssystem entfernt wird

oder mit Shared memory

gibt es da einen verlässlichen Trick wie ich sowas erreichen kann?

Tips?

von Alexander (alecxs)


Lesenswert?

reden wir hier von einem Linux PC? da lass es als cron job laufen

von cppbert (Gast)


Lesenswert?

Alexander schrieb:
> reden wir hier von einem Linux PC? da lass es als cron job laufen

das bringt mir nichts - ich brauche eine verlässliche möglichkeit um 
mehrfachstarts einens Prozesses zu verhindern - der darf nur einmal 
aktiv sein

Problem: drum herum läuft noch ein Watchdog-Prozess dessen "läuft der 
Prozess noch" Mechanismus sehr dürftig ist und hin und wieder laufen 
dann zwei Prozesse, Absicherung per Datei scheitert daran das der 
Prozess selbst auch gerne mal bei der Durchführung seiner Aufgabe 
abstürzt (x Gründe)

ich kann (darf) den Top-Level Watchdog nicht aendern und kann die 
Aufgabe des Prozesses selbst nicht beeinflussen, möchte aber das der 
gesammte Testprozess so "stabil" wie möglich Dauerlauffähig ist um mit 
meinen statistiken zu  zeige  das diese "Probleme" gar nicht so 
unrelevant sind wie manche glauben mögen

Meine Software-Qualität ist hier nicht das Problem sondern die drum 
herum liegenden Systeme

von NichtWichtig (Gast)


Lesenswert?

wir nutzen
1
pids=$(pidof progname)
um einen bestimmten Prozess zu finden.

$pids läßt sich dann je-nach-dem abfragen/verwenden/auswerten.

von cppbert (Gast)


Lesenswert?

NichtWichtig schrieb:
> wir nutzenpids=$(pidof progname)
> um einen bestimmten Prozess zu finden.
>
> $pids läßt sich dann je-nach-dem abfragen/verwenden/auswerten.

wie schon geschrieben - den Watchdog kann ich nicht aendern
der ist Müll - aber ich muss das erstmal "beweisen", weil das andere 
Team denkt Qualität hat was mit Bauchgefühl zu tun


Ich brauche nur eine verlassliche, Terminate sichere Lösung die in dem 
Prozess verhindert das er nochmal hochfährt wenn er ein zweites mal 
gestartet wird
auch wenn der Prozess dann abstürzt sollte er dann wieder starten

unter Windows reicht in der main "gibt es diesen benamten Mutex schon, 
wenn ja raus hier" ansonsten benamten Mutex anlegen - 4 Zeiler, 
Terminate, Absturz-Sicher

von Alexander (alecxs)


Lesenswert?

Du kannst doch einen cron job schreiben der Dateizugriffe überwacht, so 
auch für Zugriff auf dein Programm. Erstellen und löschen von 
Sperrdateien sollte jetzt nicht das Problem sein.

von cppbert (Gast)


Lesenswert?

Alexander schrieb:
> Du kannst doch einen cron job schreiben der Dateizugriffe
> überwacht, so
> auch für Zugriff auf dein Programm. Erstellen und löschen von
> Sperrdateien sollte jetzt nicht das Problem sein.

ich habe auf die Umgebung keinen Einfluss - ich kann das nur in meinem 
C/C++ Program mit Code lösen - und unter Windows ist es wie gesagt ein 
4-Zeiler

von Εrnst B. (ernst)


Lesenswert?

Einen abstrakten Unix-Socket aufmachen, beim zweiten "bind" gibt's einen 
Fehler:
1
int main () {
2
   int sock = socket(PF_UNIX, SOCK_STREAM, 0);
3
   struct sockaddr_un tmp = { .sun_family = AF_UNIX };
4
   snprintf(tmp.sun_path+1, sizeof(tmp.sun_path)-1, "%s", "MeinProgrammName");
5
   tmp.sun_path[0]='\0'; // "abstrakter"-Socket.
6
   int ret=bind(sock, (struct sockaddr *)&tmp, sizeof(tmp));
7
   if (ret) {
8
     perror("Programm läuft schon...");
9
     return 1;
10
   }
11
   sleep(300);
12
   return 0;
13
}

von Pidder (Gast)


Lesenswert?

cppbert schrieb:

> eine einfache Datei reicht nicht weil ich die nicht löschen wenn ich
> durch ein Plugin-Fehler terminiert werde

Ganz grob: In das (Hint: link(2) ist atomar) Lockfile die PID 
reinschreiben, und beim Start wenn das Lockfile existiert überprüfen ob 
es diese PID gibt. In /proc/<PID>/cmdline oder /proc/<PID>/environ kann 
man auch noch etwas magic reinschreiben um zu prüfen ob diese PID 
wirklich dieser Prozess ist.

von cppbert (Gast)


Lesenswert?

Εrnst B. schrieb:
> Einen abstrakten Unix-Socket aufmachen, beim zweiten "bind" gibt's
> einen
> Fehler:

Nice

ist das Terminate/Abbruchsfest? ich denke mal ja weil das OS den socket 
dicht macht, oder?

von Alexander (alecxs)


Lesenswert?

dann schreib einen Wrapper der dein Programm aufruft? leider gibt es 
kein IN_EXEC aber vielleicht reicht auch IN_ACCESS
linux.die.net/man/5/incrontab

von cppbert (Gast)


Lesenswert?

scheint so

https://man7.org/linux/man-pages/man7/unix.7.html
1
       Abstract sockets automatically disappear when all open references
2
       to the socket are closed.
3
4
       The abstract socket namespace is a nonportable Linux extension.

von DPA (Gast)


Lesenswert?

Ich habe schnell mal eine sh und eine c variante erstellt.

sh:
1
#!/bin/sh
2
exec 9<"$0"
3
flock -n 9 || exit 1 # with -n, it won't wait, it'll just return an error if already locked
4
5
( # Remove the ()9<&- subshell if you want the programs to inherit the locked fd
6
  echo doing stuff
7
  sleep 5
8
  echo done
9
) 9<&-

c:
1
#define _DEFAULT_SOURCE
2
#include <sys/file.h>
3
#include <unistd.h>
4
#include <stdio.h>
5
#include <errno.h>
6
7
int main(){
8
  // Remove O_CLOEXEC if other processes shall inherit the locked fd
9
  int exefd = open("/proc/self/exe", O_RDONLY | O_CLOEXEC);
10
  if(exefd < 0){
11
    perror("open");
12
    return 1;
13
  }
14
  int ret;
15
  // Remove LOCK_NB if you just want to wait for it to finish instead
16
  while((ret=flock(exefd, LOCK_EX | LOCK_NB)) == -1 && errno == EINTR);
17
  if(ret == -1 && errno == EWOULDBLOCK){
18
    printf("Nope, it's already running!");
19
    return 2;
20
  }
21
  if(ret == -1){
22
    perror("flock");
23
    return 1;
24
  }
25
26
  puts("doing stuff");
27
  sleep(5);
28
  puts("done");
29
}

von Εrnst B. (ernst)


Lesenswert?

cppbert schrieb:
> ist das Terminate/Abbruchsfest? ich denke mal ja weil das OS den socket
> dicht macht, oder?

musst aber bei fork, exec und co aufpassen, dass der nicht ungewollt an 
Child-Prozesse vererbt wird.
also ggfs. noch ein O_CLOEXEC/SOCK_CLOEXEC flag mit reinbasteln.

von DPA (Gast)


Lesenswert?

Noch was zur flock Lösung: wenn dass Programm / Script ersetzt wird, ist 
es eine andere Datei, die gelockt wird, dann kann es schief gehen. Falls 
fas ein Problem ist, kann man statt dem Program / Script eine beliebige 
andere Datei öffnen. z.B. in /var/lock/.

von Norbert (Gast)


Lesenswert?

cppbert schrieb:
> gibt es da einen verlässlichen Trick wie ich sowas erreichen kann?

Da braucht's keine Tricks.
Man macht's einfach so wie es alle machen.
Stichwort
1
/var/run
Wenn nicht vorhanden: Datei mit eigener PID schreiben
Wenn vorhanden: Prüfen ob PID passt, wenn ja läuft's bereits -> beenden.
Wenn nein, Datei mit eigener PID schreiben.

von DPA (Gast)


Lesenswert?

Ich empfehle wirklich flock zu nutzen. Es ist simpel, man muss sich 
keine Sorgen um Race Conditions usw. machen, und man muss nichts 
aufräumen. Und man kann den Lock sogar an andere Prozesse weitergeben, 
wenn man will. Das hier ist auch eine der Sachen, für die flock da ist. 
Und das ist auch, was Programme wie z.B. apt nutzen, wenn die was 
exklusives machen wollen.

von PittyJ (Gast)


Lesenswert?

Εrnst B. schrieb:
> Einen abstrakten Unix-Socket aufmachen, beim zweiten "bind" gibt's einen
> Fehler:
>

Wollte ich auch gerade vorschlagen. Habe ich schon öfter benutzt.

von Ein T. (ein_typ)


Lesenswert?

cppbert schrieb:
> Alexander schrieb:
>> reden wir hier von einem Linux PC? da lass es als cron job laufen
>
> das bringt mir nichts - ich brauche eine verlässliche möglichkeit um
> mehrfachstarts einens Prozesses zu verhindern - der darf nur einmal
> aktiv sein

Vielleicht einen Socket erzeugen und bind(2)en? Wenn der bind(2) 
fehlschlägt, also -1 zurückgibt, ist die Adresse vermutlich "already in 
use" und errno sollte (IIRC) auf 98 stehen. Wenn der Prozess regulär 
beendet wird, schließt er den Socket, und bei Abstürzen räumt das 
Betriebssystem den Socket automatisch ab. Nur so ne Idee, YMMV.

Ansonsten gibt es für Linux Programme wie run-one(1), das funktioniert 
aber nur, wenn die Aufrufparameter immer gleich sind. Wenn Dein Programm 
mit verschiedenen Parametern aufgerufen wird, hilft vielleicht flock(1).

von Robert Blanko (Gast)


Lesenswert?

cppbert schrieb:
> ich kann (darf) den Top-Level Watchdog nicht aendern und kann die
> Aufgabe des Prozesses selbst nicht beeinflussen, möchte aber das der
> gesammte Testprozess so "stabil" wie möglich Dauerlauffähig ist um mit
> meinen statistiken zu  zeige  das diese "Probleme" gar nicht so
> unrelevant sind wie manche glauben mögen

Entweder hältst du dich für klüger als alle anderen oder eure 
Architektur ist Schrott.

In beiden Fällen wird sich am Problem nichts ändern.

von cppbert (Gast)


Lesenswert?

Ein T. schrieb:
> Vielleicht einen Socket erzeugen und bind(2)en? Wenn der bind(2)
> fehlschlägt, also -1 zurückgibt, ist die Adresse vermutlich "already in
> use" und errno sollte (IIRC) auf 98 stehen. Wenn der Prozess regulär
> beendet wird, schließt er den Socket, und bei Abstürzen räumt das
> Betriebssystem den Socket automatisch ab. Nur so ne Idee, YMMV.

siehe Idee von Εrnst B. ein paar Post vorher

von cppbert (Gast)


Lesenswert?

Robert Blanko schrieb:
> cppbert schrieb:
>> ich kann (darf) den Top-Level Watchdog nicht aendern und kann die
>> Aufgabe des Prozesses selbst nicht beeinflussen, möchte aber das der
>> gesammte Testprozess so "stabil" wie möglich Dauerlauffähig ist um mit
>> meinen statistiken zu  zeige  das diese "Probleme" gar nicht so
>> unrelevant sind wie manche glauben mögen
>
> Entweder hältst du dich für klüger als alle anderen

nicht alle :)

> oder eure Architektur ist Schrott.

Das ist ein Test-System das Schrott erkennen soll
und ich will mehrere Beweise erbringen zur Qualität des ganzen

> In beiden Fällen wird sich am Problem nichts ändern.

zuerst das Problem verständlich machen, mit Fakten und Zahlen
dann aufräumen

von Ein T. (ein_typ)


Lesenswert?

cppbert schrieb:
> Ein T. schrieb:
>> Vielleicht einen Socket erzeugen und bind(2)en? Wenn der bind(2)
>> fehlschlägt, also -1 zurückgibt, ist die Adresse vermutlich "already in
>> use" und errno sollte (IIRC) auf 98 stehen. Wenn der Prozess regulär
>> beendet wird, schließt er den Socket, und bei Abstürzen räumt das
>> Betriebssystem den Socket automatisch ab. Nur so ne Idee, YMMV.
>
> siehe Idee von Εrnst B. ein paar Post vorher

Tja, er war schneller als ich. ;-)

von Rolf M. (rmagnus)


Lesenswert?

Norbert schrieb:
> Stichwort/var/run
> Wenn nicht vorhanden: Datei mit eigener PID schreiben
> Wenn vorhanden: Prüfen ob PID passt, wenn ja läuft's bereits -> beenden.
> Wenn nein, Datei mit eigener PID schreiben.

Das ist eigentlich der klassische Standard-Weg, der nebenbei noch den 
Vorteil hat, dass man erkennt, ob das Programm ordnungsgemäß beendet 
wurde oder abgestürzt ist.

von Εrnst B. (ernst)


Lesenswert?

Rolf M. schrieb:
> Das ist eigentlich der klassische Standard-Weg

ja, und portabel zwischen verschiedenen Unix-Geschmacksrichtungen.
Dafür leicht sabotierbar, "rm /var/run/xxx.pid".

Das abstract unix socket ist Linux-Only.

das File-Lock auf dem executable erfordert ein /proc/-Dateisystem 
(zumindest in dem Beispiel von DPA), und hilft nicht gegen verschiedene 
Versionen oder Kopien vom Programm.

Wie so oft, "it depends", was denn nun die beste Lösung für das Problem 
von cppbert ist.

Das er sich scheinbar gegen andere Entwickler auf derselben Maschine 
(evtl. sogar mit root-Rechten) "verteidigen" will, macht die Sache etwas 
komplizierter.

von Ein T. (ein_typ)


Lesenswert?

Εrnst B. schrieb:
> Rolf M. schrieb:
>> Das ist eigentlich der klassische Standard-Weg
>
> ja, und portabel zwischen verschiedenen Unix-Geschmacksrichtungen.
> Dafür leicht sabotierbar, "rm /var/run/xxx.pid".

Meiner Meinung nach ist dieser klassische Weg ein Relikt von SysV-Init, 
das für einige Daemons eine Möglichkeit haben mußte, den Prozeß 
wiederzufinden. Leider ist diese Methode nicht atomar -- das war bei 
SysV-Init eher irrelevant, könnte es aber für den Anwendungsfall des TO 
sein.

> Das abstract unix socket ist Linux-Only.

Es geht auch mit einem TCP- oder UDP-Socket auf localhost -- zu dem 
Preis natürlich, daß man diesen Port "verschwendet".

> das File-Lock auf dem executable erfordert ein /proc/-Dateisystem
> (zumindest in dem Beispiel von DPA), und hilft nicht gegen verschiedene
> Versionen oder Kopien vom Programm.

Und es braucht die entsprechenden Permissions auf /proc/self/exe, was 
auch nicht immer gegeben sein muß.

> Wie so oft, "it depends", was denn nun die beste Lösung für das Problem
> von cppbert ist.

Ja...

> Das er sich scheinbar gegen andere Entwickler auf derselben Maschine
> (evtl. sogar mit root-Rechten) "verteidigen" will, macht die Sache etwas
> komplizierter.

Naja, wenn die absichtlich etwas Destruktives machen und deswegen etwas 
fehlschlägt, hat er doch seinen gewünschten Beweis.

von db8fs (Gast)


Lesenswert?

cppbert schrieb:
> eine einfache Datei reicht nicht weil ich die nicht löschen wenn ich
> durch ein Plugin-Fehler terminiert werde

Hmm, gibts da nicht sowas wie atexit() unter den Unixen wo man sich 
draufklemmen kann? Halt wie beim Windows-DllMain/PROCESS_DETACH.

Prinzipiell find ich auch lock-Files den richtigen Weg. Die IPC geht 
aber wohl auch über Pipes (unistd.h/pipe()) - macht aber imho nur Sinn, 
wenn Daten zu übertragen sind. Sonst sollte doch eigentlich ein Lockfile 
ausreichen.

von Ein T. (ein_typ)


Lesenswert?

db8fs schrieb:
> cppbert schrieb:
>> eine einfache Datei reicht nicht weil ich die nicht löschen wenn ich
>> durch ein Plugin-Fehler terminiert werde
>
> Hmm, gibts da nicht sowas wie atexit() unter den Unixen wo man sich
> draufklemmen kann? Halt wie beim Windows-DllMain/PROCESS_DETACH.

Das funktioniert nur, wenn das Programm normal beendet wird.

von Moveit (Gast)


Lesenswert?

Ein T. schrieb:
> Meiner Meinung nach ist dieser klassische Weg ein Relikt von SysV-Init,
> das für einige Daemons eine Möglichkeit haben mußte, den Prozeß
> wiederzufinden. Leider ist diese Methode nicht atomar -- das war bei
> SysV-Init eher irrelevant, könnte es aber für den Anwendungsfall des TO
> sein.

link(2) ist atomar.In Shellscripten nimmt dafür mv.

von db8fs (Gast)


Lesenswert?

Ein T. schrieb:
>> Hmm, gibts da nicht sowas wie atexit() unter den Unixen wo man sich
>> draufklemmen kann? Halt wie beim Windows-DllMain/PROCESS_DETACH.
>
> Das funktioniert nur, wenn das Programm normal beendet wird.

Hmm, na ja, die guten alten signals abzufangen sollte aber schon 
eigentlich schon gehen, selbst wenn's irgendeine ultraspeziale 
Spezialkomponente ist.
Muss eben dann der Plugin-Host die Signals weiterverteilen an die 
Komponente.

Bin mir auch noch nicht sicher, ob ich das Problem ganz verstanden hab. 
Was soll der Plugin-Host denn erkennen können: (a) ob ein Subprozess 
wegkachelt oder (b) ob er selber abstürzt, wenn er eine dynamische 
Library ausführt oder (c) um zu vermeiden, dass er selber doppelt 
instanziiert wird.

Für (a) über's PID-Interface braucht man meiner Meinung nach kein 
Lockfile - machste den Prozess auf, kannste auch monitoren/verwalten, ob 
das Ding weggeraucht ist oder nicht (und bei Bedarf den 
Resourcen-Cleanup durchführen)

Für (b) geht auch, aber halt eben über das signals-IPC-Interface - bei 
SIGKILL oder so, halt dann den Cleanup machen und das Lockfile löschen.

Für (c) hilft dann auch ein ganz normales Lockfile, wenn die anderen 
beiden Fälle abgehandelt sind.

von cppbert (Gast)


Lesenswert?

Danke für die vielen Tips - ich hab jetzt den Socket-Vorschlag von Εrnst 
B. in verwendung - macht bisher gute arbeit

db8fs schrieb:
> Bin mir auch noch nicht sicher, ob ich das Problem ganz verstanden hab.
> Was soll der Plugin-Host denn erkennen können: (a) ob ein Subprozess
> wegkachelt oder (b) ob er selber abstürzt, wenn er eine dynamische
> Library ausführt oder (c) um zu vermeiden, dass er selber doppelt
> instanziiert wird.

mehr oder minder b+c

mein Prozess läd dynamische libraries die Abstürzen können - da erkenne 
ich sauber, und mein Prozess wird aber noch von einem Buggy Watchdog 
gestartet dessen implementierung nicht gut ist und mich manchmal zweimal 
startet

Ich möchte Aufzeige das die Plugin-Absturz-Rate nicht gering ist und die 
Watchdog-Doppelstarts auch deutlich keine Einbildung sind :)

von Herbert B. (Gast)


Lesenswert?

Lockfile ist der gängige Weg, machen die meisten Programme unter Linux 
so.

von DPA (Gast)


Lesenswert?

Εrnst B. schrieb:
> das File-Lock auf dem executable erfordert ein /proc/-Dateisystem
> (zumindest in dem Beispiel von DPA), und hilft nicht gegen verschiedene
> Versionen oder Kopien vom Programm.

Die Datei kann man beliebig wählen. Man kann da also auch einfach z.B. 
/var/lock/meinprogram.lock oder /var/run/meinprogram.lock eingeben, oder 
worauf auch immer man genug Berechtigungen hat. Statt /proc/self/exe.
Beim Open dann eventuell noch ein O_CREAT hinzufügen, damit die Datei 
auch automatisch erstellt wird falls noch nicht vorhanden.

Das selbe auch beim shell script, dass da die Datei automatisch erstellt 
wird nimmt man dann halt >> statt <. Und das "$0" durch den Pfad der 
Datei ersetzen.

von Ein T. (ein_typ)


Lesenswert?

Moveit schrieb:
> link(2) ist atomar. In Shellscripten nimmt dafür mv.

oldpath? Dateisystemgrenzen? Atomizität? Zudem: link(1) existiert.

von DPA (Gast)


Lesenswert?

Noch was zur unix socket variante. Ich bin mir nicht sicher, ob es der 
IPC oder der Network Namespace war, aber man kann es mehrfach in 
unterschiedlichen Namespaces starten, z.B. mit "unshare". Systemd kann 
das Zeugs natürlich auch isolieren.

von Ein T. (ein_typ)


Lesenswert?

DPA schrieb:
> Die Datei kann man beliebig wählen. Man kann da also auch einfach z.B.
> /var/lock/meinprogram.lock oder /var/run/meinprogram.lock eingeben, oder
> worauf auch immer man genug Berechtigungen hat. Statt /proc/self/exe.
> Beim Open dann eventuell noch ein O_CREAT hinzufügen, damit die Datei
> auch automatisch erstellt wird falls noch nicht vorhanden.

... und natürlich auch O_EXCL, damit der Prozeß exklusiven Zugriff auf 
die Datei erhält und dadurch die Atomizität sichergestellt wird. Aber 
jede Lösung mit Lock- oder Pidfiles ist nicht atomar und erfordert 
zudem, daß der Prozeß korrekt hinter sich aufräumt, also regulär beendet 
wird. Unter SysV-Init waren solche verwaisten Lock- und Piddateien ja 
kein ganz seltenes Problem, sogar ohne Atomizität.

Wenn eine verwaiste Lock- oder Piddatei einen Neustart des Programms 
unseres TO cppbert verhindert, dann werden nicht die Entwickler des 
Watchdog, sondern der TO höchstpersönlich für Probleme verantwortlich 
gemacht. Das ist aber nicht das Ziel unseres TO, vermute ich mal.

von Ein T. (ein_typ)


Lesenswert?

DPA schrieb:
> Noch was zur unix socket variante. Ich bin mir nicht sicher, ob es der
> IPC oder der Network Namespace war, aber man kann es mehrfach in
> unterschiedlichen Namespaces starten, z.B. mit "unshare". Systemd kann
> das Zeugs natürlich auch isolieren.

Das gilt für Deine dateibasierten Lösungsansätze aber ganz genauso, 
insofern sehe ich darin keine Vorzüge.

von Rolf M. (rmagnus)


Lesenswert?

Ein T. schrieb:
> Aber jede Lösung mit Lock- oder Pidfiles ist nicht atomar und erfordert
> zudem, daß der Prozeß korrekt hinter sich aufräumt, also regulär beendet
> wird.

Nein. Es wird PID-File genannt, weil es als Inhalt die PID des Prozesses 
enthält, der sie erzeugt hat. Damit kann man nachsehen, ob der Prozess 
noch lebt und sicherheitshalber noch, ob das zugehörige Executable das 
selbe ist. Wenn nicht, ist er abgestürzt. Man kann also die drei Fälle 
"Prozess läuft", "Prozess läuft nicht, wurde regulär beendet" und 
"Prozess ist abgestürzt" unterscheiden. Soweit ich den TE verstanden 
habe, braucht er auch alle drei Fälle. Die anderen Lösungen vermögen 
nicht, den Absturz zu erkennen.

von Ein T. (ein_typ)


Lesenswert?

Rolf M. schrieb:
> Nein. Es wird PID-File genannt, weil es als Inhalt die PID des Prozesses
> enthält, der sie erzeugt hat. Damit kann man nachsehen, ob der Prozess
> noch lebt und sicherheitshalber noch, ob das zugehörige Executable das
> selbe ist. Wenn nicht, ist er abgestürzt.

...oder hat eine Funktion aus der execve(2)-Familie aufgerufen, aber das 
gerät jetzt ein

> Man kann also die drei Fälle
> "Prozess läuft", "Prozess läuft nicht, wurde regulär beendet" und
> "Prozess ist abgestürzt" unterscheiden. Soweit ich den TE verstanden
> habe, braucht er auch alle drei Fälle. Die anderen Lösungen vermögen
> nicht, den Absturz zu erkennen.

Mir ist schon klar, warum ein Pidfile so heißt, wie es heißt, und auch, 
wie sowas funktioniert. Trotzdem vielen Dank für die Erklärung. Was ich 
allerdings noch nicht verstanden habe, ist, wie Du die Atomizität 
sicherstellen möchtest. Denn wenn ich das richtig verstanden habe, ist 
das ja kein ohnehin seriell ablaufendes SysV-Init, sondern irgendeine 
Art CI/CD-System, das durchaus auf die Idee kommen könnte, das Programm 
des TO mehrmals gleichzeitig zu starten.

von Norbert (Gast)


Lesenswert?

Ein T. schrieb:
> Was ich
> allerdings noch nicht verstanden habe, ist, wie Du die Atomizität
> sicherstellen möchtest. Denn wenn ich das richtig verstanden habe, ist
> das ja kein ohnehin seriell ablaufendes SysV-Init, sondern irgendeine
> Art CI/CD-System, das durchaus auf die Idee kommen könnte, das Programm
> des TO mehrmals gleichzeitig zu starten.

Wenn ich da mal rein grätschen darf…
Für Program xyz:
Mit tempfile eine Datei mit Zufallsnamen in ›/var/run/xyz/‹ erzeugen.
PID reinschreiben.
Mit link einen Hardlink auf xyz.pid erzeugen. (Atomar)
Wenn das ohne Fehler klappt, war man Erster:
  unlink tempfile
  Alles gut
Wenn das nicht klappt, gibt's xyz.pid schon:
  xyz.pid lesen und die enthaltene PID prüfen.
  …

von DPA (Gast)


Lesenswert?

Ein T. schrieb:
> ... und natürlich auch O_EXCL, damit der Prozeß exklusiven Zugriff auf
> die Datei erhält und dadurch die Atomizität sichergestellt wird.

Nein, es ist beabsichtigt, dass mehrere Prozesse die Datei anlegen oder 
öffnen können. Dass soll nicht verhindert werden. Ich arneite dort mit 
einem echten lock, nicht mit dem O_EXCL hack.

> Aber jede Lösung mit Lock- oder Pidfiles ist nicht atomar und erfordert
> zudem, daß der Prozeß korrekt hinter sich aufräumt, also regulär beendet
> wird.

Nein, auch das ist bei meiner Variante mit flock nicht nötig. Der 
Prozess muss schon weg / beendet werden, aber auch wenn das per kill -9 
oder OOM killer oder sonst wie passiert, ist der Lock automatisch wieder 
freigegeben.

Naja, sollte es aber mal nicht möglich sein, den Prozess abzuschiessen,
weil es im kernel im non-interruptable sleep hängen geblieben ist, kann 
man notfalls noch die Datei löschen. Der lock ist ja auf der Inode, und 
nicht dem link.
Bei der unix socket Variante hingegen wäre man dann vermutlich 
aufgeschmissen, und müsste rebooten.

von Alexander (alecxs)


Lesenswert?

Echt, kann man den selben Dateinamen mehrfach vergeben in verschiedenen 
Mount Namespaces?

von DPA (Gast)


Lesenswert?

Alexander schrieb:
> Echt, kann man den selben Dateinamen mehrfach vergeben in verschiedenen
> Mount Namespaces?

Nicht ganz. Der selbe Pfad im selben Dateisystem ist immernoch die 
selbe Datei. Aber welches Dateisystem wo gemountet ist, sowie welcher 
Teil davon, kann völlig anders sein, der selbe Pfad aus Anwendunssicht 
kann damit zu einem ganz anderen Link und Datei auf einem ganz anderen 
Dateisystem führen. Es ist auch möglich, einzelne Dateien zu mounten, 
oder / selbst zu mounten/ersetzen (das machen container). Ausserdem ist 
das auch als normaler user möglich.

von Alexander (alecxs)


Lesenswert?

Gut das ist klar, aber hat ja mit Namespaces nix zu tun. Aber ich hatte 
das eh mißverstanden. Es reicht ja wenn eine Sperrdatei unsichtbar ist, 
auch wenn sie nur einmal existiert. Man müsste also zum prüfen noch 
versuchen diese anzulegen, wenn es fehl schlägt existiert sie schon.

im shell script mache ich es immer so
1
(( $(lsof -t $0 | wc -l) == 1 )) || exit 2

: Bearbeitet durch User
von Ein T. (ein_typ)


Lesenswert?

DPA schrieb:
> Nein, auch das ist bei meiner Variante mit flock nicht nötig. Der
> Prozess muss schon weg / beendet werden, aber auch wenn das per kill -9
> oder OOM killer oder sonst wie passiert, ist der Lock automatisch wieder
> freigegeben.

Bitte entschuldige, aber dann möchte ich Deine Variante gerne sehen, 
bitte.

Denn mit BSD-Locks (flock(2)) funktioniert das bei mir nicht. Wenn ein 
Prozeß, der ein exklusives Lock (LOCK_EX) hält, irregulär beendet wird 
(Ctrl-c reicht aus), also sein Lock nicht freigibt, bleibt das Lock 
bestehen. Jeder folgende Aufruf bleibt in flock(2) hängen, bis die Datei 
manuell entfernt wird. Vielleicht habe ich aber etwas übersehen, das 
will ich nicht ausschließen.

Bezogen auf die Situation des TO wäre das aber fatal. Immerhin möchte 
er, wenn ich das richtig verstanden habe, Software testen. Solchen Tests 
ist inhärent, daß sie mitunter fehlschlagen können und der Prozeß 
abstürzt, also nicht mehr hinter sich aufräumen kann -- was zu dem oben 
beschriebenen Verhalten führen würde: das Lock bliebe bestehen und 
weitere Aufrufe des Programms blieben im flock(2) hängen. Das würde 
allerdings auf den TO zurückfallen anstatt auf den Watchdog seiner 
Kollegen, dessen Dysfunktionalität der TO doch belegen möchte.

von Ein T. (ein_typ)


Lesenswert?

Norbert schrieb:
> Wenn ich da mal rein grätschen darf…
> Für Program xyz:
> Mit tempfile eine Datei mit Zufallsnamen in ›/var/run/xyz/‹ erzeugen.
> PID reinschreiben.
> Mit link einen Hardlink auf xyz.pid erzeugen. (Atomar)
> Wenn das ohne Fehler klappt, war man Erster:
>   unlink tempfile
>   Alles gut
> Wenn das nicht klappt, gibt's xyz.pid schon:
>   xyz.pid lesen und die enthaltene PID prüfen.
>   …

Stimmt, das geht (auch wenn tempfile(1) ein Shellbefehl ist ich in C/C++ 
natürlich mk(o)stemp(3) benutzen würde), aber das Aufräumproblem sehe 
ich dabei leider immer noch. Was passiert, wenn der Prozeß abraucht und 
nicht hinter sich aufräumen kann?

von DPA (Gast)


Lesenswert?

Ein T. schrieb:
> DPA schrieb:
>> Nein, auch das ist bei meiner Variante mit flock nicht nötig. Der
>> Prozess muss schon weg / beendet werden, aber auch wenn das per kill -9
>> oder OOM killer oder sonst wie passiert, ist der Lock automatisch wieder
>> freigegeben.
>
> Bitte entschuldige, aber dann möchte ich Deine Variante gerne sehen,
> bitte.

Einfach hoch scrollen: 
Beitrag "Re: C/C++, Linux: Veriegelung das ein Prozess nur genau 1 mal läuft"

Ich pass es dir auch noch für ein separates lock file an, damit du da 
auch nichts falsch machen kannst:
1
#define _DEFAULT_SOURCE
2
#include <sys/file.h>
3
#include <unistd.h>
4
#include <stdio.h>
5
#include <errno.h>
6
int main(){
7
  // Remove O_CLOEXEC if other processes shall inherit the locked fd
8
  int exefd = open("/tmp/meinlock.lock", O_RDONLY | O_CLOEXEC | O_CREAT, 0644);
9
  if(exefd < 0){
10
    perror("open");
11
    return 1;
12
  }
13
  int ret;
14
  // Remove LOCK_NB if you just want to wait for it to finish instead
15
  while((ret=flock(exefd, LOCK_EX | LOCK_NB)) == -1 && errno == EINTR);
16
  if(ret == -1 && errno == EWOULDBLOCK){
17
    printf("Nope, it's already running!");
18
    return 2;
19
  }
20
  if(ret == -1){
21
    perror("flock");
22
    return 1;
23
  }
24
  puts("doing stuff");
25
  sleep(5);
26
  puts("done");
27
}

Ein T. schrieb:
> Denn mit BSD-Locks (flock(2)) funktioniert das bei mir nicht. Wenn ein
> Prozeß, der ein exklusives Lock (LOCK_EX) hält, irregulär beendet wird
> (Ctrl-c reicht aus), also sein Lock nicht freigibt, bleibt das Lock
> bestehen. Jeder folgende Aufruf bleibt in flock(2) hängen, bis die Datei
> manuell entfernt wird. Vielleicht habe ich aber etwas übersehen, das
> will ich nicht ausschließen.

Das funktioniert definitiv. Ich habe flock schon oft verwendet und 
Ausgiebig getestet. Wenn der Prozess beendet wird, ganz egal wie, 
schliesst das den File Deskriptor, und nach dem schliessen des letzten 
Filedeskriptors der gelockten File Description, gibt das den Lock frei.

von Norbert (Gast)


Lesenswert?

Ein T. schrieb:
> ist ich in C/C++
> natürlich mk(o)stemp(3) benutzen würde

Da hast du natürlich recht. War wohl gedanklich noch ein Überbleibsel 
der vielen Bash-Diskussionen hier. ;-)

Ein T. schrieb:
> Was passiert, wenn der Prozeß abraucht und
> nicht hinter sich aufräumen kann?

Völlig egal. In den einsamen Fragmenten steht in der Datei eine PID 
drin. Wenn man die beim nächsten Start prüft, wird man sehen das sie 
entweder gar nicht existiert oder aber einem völlig anderen Prozess 
gehört. So kann man feststellen das es das letzte mal wohl geraucht hat.

Dann räumt man kurz den alten Schmutz weg und beginnt einfach erneut.

von Ein T. (ein_typ)


Lesenswert?

DPA schrieb:
> Das funktioniert definitiv.

Tatsache, tut es, wegen mode = S_IWUSR | S_IRUSR. Mit mode = 0 gibt es 
(natürlich) einen ENOPERM, wenn der zweite Prozeß die Datei öffnen will, 
oder ein Prozeß seine Lockdatei nicht abgeräumt hat. Trotzdem finde ich 
es... irgendwie eleganter, diese Angelegenheit auf eine exklusive, 
kernelverwaltete Ressource wie einen abstrakten UNIX Domain- oder 
TCP/IP-Socket zu legen -- ist aber nur so ein Gefühl und sicher auch 
eine Folge davon, daß alleine Linux drei miteinander inkompatible 
Dateilock-Mechanismen hat und jedes UNIX dabei seine eigenen subtilen 
Eigenheiten besitzt.

PS: Ach so, entschuldige: lieben Dank für Deine erhellende Antwort!

Edit: PS hinzugefügt.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.