Seite 1 von 1

Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 08:54
von tobi45f
Hallo zusammen,

ich habe ein Skript mit kleiner tkinter Oberfläche, zur Unterstützung einiger Arbeiten. Gerne wird es von den Kollegen mal irgendwo hin verschoben oder minimiert, also übersehen und eine zweite Instanz geöffnet. Danach ist die Verwirrung groß...

Wie kann ich sowas verhindern, also dass eine zweite Instanz geöffnet wird?

Meine bisherige Idee war es die laufenden Prozeesse über psutil abzufragen:

Code: Alles auswählen

def check_existing_instance(instance):
    import psutil
    from datetime import datetime, timedelta
    check_time = datetime.now() - timedelta(seconds = 10)
    stamped = check_time.timestamp()
    
    for p in psutil.process_iter():
        
        if "python" in p.name():
            
           if instance in p.cmdline()[1] and stamped > p.create_time():
                return p
    return None
Dann also zu gucken, ob der Dateiname der Python-File in der cmdline steht. Problem dabei ist, dass ja die Datei, die als zweites aufgerufen wird, denselben Namen haben hat. Somit muss ich irgendwie prüfen, dass er nicht den grade aufgerufenen Prozess findet, sondern den anderen, älteren. Somit habe ich einfach angenommen, dass der alte Aufruf älter sein muss, als 10 Sekunden.
Fühlt sich eher wie eine Krücke an :D aber das Beste, was mir einfiel.
Kann ich beispielsweise abfragen, wie die ID des aufrufenden Prozess ist, sodass ich das besser filtern kann, als über die Zeit? Oder gibt es generell einen klügeren Ansatz?

Grüße Tobias

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 09:34
von Sirius3
Bei einer Funktion die `check_existing_instance` heißt, würde ich erwarten, dass sie True oder False zurückliefert, aber keinen Prozess.
Also entweder heißt die Funktion falsch, oder hat einen falschen Rückgabewert.
Importe gehören an den Anfang der Datei und nicht in Funktionen.
Die `in`-Vergleiche scheinen mir sehr fragil.
Die eigene PID bekommt man per os.getpid().

Code: Alles auswählen

import psutil
import os
import sys

def check_existing_instance(instance):
    own_pid = os.getpid()
    return any(
        p.exe() == sys.executable and instance in p.cmdline()[1] and p.pid != own_pid
        for p in psutil.process_iter()
    )

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 11:51
von tobi45f
Super, das hilft mir sehr. Danke!

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 13:38
von DeaD_EyE
Als normaler User bekomme ich einen Fehler, wenn ich die Methode exe verwende (fehlende Berechtigung).
Test:

Code: Alles auswählen

from psutil import process_iter

# bei mir knallt es erst bei PID 2
list(p.exe() for p in process_iter())
Alternativ könnte man ja einfach die pid und die cmdline abfragen, was immer geht.

Code: Alles auswählen

import time
import sys
import subprocess

from psutil import Process, process_iter


def check_existing_instance():
    self_proc = Process()
    self_cmdline = self_proc.cmdline()
    self_pid = self_proc.pid

    for proc in process_iter():
        if proc.cmdline() == self_cmdline and proc.pid != self_pid:
            return True

    return False



if check_existing_instance():
    raise SystemExit("Es läuft eine zweite Instanz. Abbruch")
else:
    print("Die einzige Instanz")


print("Starte nun das gleiche Programm nochmal, was dann die Meldung auslösen sollte")
cmdline = Process().cmdline()
print(" ".join(cmdline))
subprocess.run(cmdline)


time.sleep(10)
print("Beende erste Instanz")
Anmerkung: Wenn der Anwender das Programm mit anderen Kommandos startet, stimmt cmdline nicht mehr, da ich die komplette cmdline verwende und nicht nur den zweiten Teil.

Manche Programme legen aber auch eine Datei mit ihrer ProzessID an einem bekannten Ort ab und vor dem Start prüft das Programm dann, ob es noch einen Prozess mit der eingetragenen ID gibt. Falls ja, den Start verhindern, ansonsten die alte PID aus der Datei mit der neuen PID überschreiben.

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 15:00
von imonbln
Unter Linux/Unix ist es üblich Programm/Script nur einmal auszuführen, indem man mit fnctl einfach ein Lock auf das erste Byte in der Datei zu bekommen versucht. Wer das Lock bekommt, ist die Instanz! Alle anderen beenden sich. Der Vorteil hierbei ist, dass man nicht die Prozessliste parsen muss (was schwerer sein kann, als es erstmal erscheint) und das Unix, dass Lock abräumt, wenn der Prozess beendet wird.

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 16:17
von __deets__
@imonbln: hast du ein Beispiel dafuer? Ich kenne den lock-Ansatz eher mit einer PID-Datei, das man auf das Executable selbst zeigt (so verstehe ich dich zumindest) ist mir noch nicht untergekommen. Heisst aber nix :)

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 17:20
von imonbln
@ __deets__

Ich glaube, ich habe Ansatz ein paar mal unter Solaris und in der Perl ecke gesehen. Runter gebrochen auf das minimalste sieht es so aus.

Code: Alles auswählen

import fcntl

def main():
    try:
        with open(__file__ ) as ownfile:
            fcntl.flock(ownfile.fileno(), fcntl.LOCK_EX | fcntl.LOCK_NB)
            input('simulate the program running. (enter to exit)')
    except BlockingIOError:
        print('Other instance is running')
        
if __name__ == '__main__':
    main()
das flock kannst du noch gegen den fcntl ersetzen, aber dann musst du die "struct flock fl" gemäß manpage fcntl selbst mit struct.pack zusammen bauen. hast dafür dann aber etwa mehr Kontrolle.

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Donnerstag 17. Februar 2022, 18:09
von nezzcarth
__deets__ hat geschrieben: Donnerstag 17. Februar 2022, 16:17 Ich kenne den lock-Ansatz eher mit einer PID-Datei
So kenne ich das auch und der File Hierarchy Standard sieht dafür zumindest auch einen eigenen Ordner vor. Insofern gehe ich zumindest davon aus, dass das wenigstens unter Linux nicht ganz unüblich ist. Aber das sagt natürlich nicht, dass die andere Methode nicht auch gängig ist.

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Freitag 18. Februar 2022, 11:05
von tobi45f
DeaD_EyE hat geschrieben: Donnerstag 17. Februar 2022, 13:38 Als normaler User bekomme ich einen Fehler, wenn ich die Methode exe verwende (fehlende Berechtigung).
Dasselbe Problem habe ich auch bekommen.
DeaD_EyE hat geschrieben: Donnerstag 17. Februar 2022, 13:38 Anmerkung: Wenn der Anwender das Programm mit anderen Kommandos startet, stimmt cmdline nicht mehr, da ich die komplette cmdline verwende und nicht nur den zweiten Teil.

Manche Programme legen aber auch eine Datei mit ihrer ProzessID an einem bekannten Ort ab und vor dem Start prüft das Programm dann, ob es noch einen Prozess mit der eingetragenen ID gibt. Falls ja, den Start verhindern, ansonsten die alte PID aus der Datei mit der neuen PID überschreiben.
Leider bekomme ich bei proc.cmdline() auch ein AccessDenied Error bei einigen Prozessen, weshalb ich dann doch Python noch abfrage, damit ich da nicht reinlaufe...?

Die anderen Ansätze mit fcntl und co verstehe ich leider nicht. Ich vermute, ihr diskutiert da unter euch ein Unix/Linux Thema aus?

Wenn ich mein Skript direkt starte, dann erkenne ich, ob es schon läuft. Wenn ich allerdings das Skript über ein anderes Python-Programm starte, dann erkenne ich das leider nicht. In meinem Fall habe ich ein Archivfenster, welches einmal direkt aber alternativ aus einem Berechnungsfenster aufgerufen werden können soll. Wenn ich dann das Archiv über die Berechnung aufrufe, dann ist das Archiv bei den Prozessen leider nicht separat gelistet, sondern läuft auch unter der Berechnung.

Vermutlich muss ich es anders aufrufen?

Code: Alles auswählen

#archivfenster.py
import tkinter as tk
import tkinter.messagebox as messagebox
from archiv import *

def main():
    if check_existing_instance("archiv"):
        messagebox.showerror(title="bereits geöffnet", message="Das Fenster ist bereits geöffnet.")
        return


    root = tk.Tk()
    root.title("GUI_Archiv")
    Archiv(root)
    root.mainloop()

if __name__ == "__main__":
    main()    

Code: Alles auswählen

#archiv.py
import tkinter as tk
from psutil import Process, process_iter
import os

class Archiv:
    def __init__(self, parent:tk.Tk):
        self.window = parent
        tk.Label(self.window, text="Archivinhalt etc").pack()

def check_existing_instance(instance):
    self_proc = Process()
    self_cmdline = self_proc.cmdline()
    self_pid = self_proc.pid

    for proc in process_iter():
        if "python" in proc.name():
            if proc.cmdline() == self_cmdline and proc.pid != self_pid:
                return True

    return False

Code: Alles auswählen

#berechnungsfenster.py 
import tkinter as tk
import tkinter.messagebox as messagebox
from archiv import *
import subprocess
import sys

class Berechnungsfenster:
    def __init__(self, parent:tk.Tk):
        self.window = parent
        tk.Label(self.window, text="Berechnungskram etc").pack()
        tk.Button(self.window, text="Archiv öffnen", command=self.archiv).pack()

    def archiv(self):
        # #1
        # if check_existing_instance("GUI_Archiv"):
        #     messagebox.showerror(title="bereits geöffnet", message="Das Fenster ist bereits geöffnet.")
        #     return

        # root=tk.Toplevel()
        # root.title("GUI_Archiv")
        # Archiv(root)
        # root.mainloop()

        #2
        if check_existing_instance("archiv"):
            messagebox.showerror(title="bereits geöffnet", message="Das Fenster ist bereits geöffnet.")
            return

        subprocess.Popen([sys.executable, "archivfenster.py"], shell=False)

if __name__ == "__main__":
    root = tk.Tk()
    root.title("GUI_Berechnung")
    Berechnungsfenster(root)
    root.mainloop()

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Freitag 18. Februar 2022, 13:41
von peterpy
ich handhabe das folgendermassen:
wird das Script gestartet wird, in meinem Fall, ein lock_file in /var/tmp/ angelegt.
Wird das Script ordentlich beendet, also nicht abgewürgt, wird das lock_fil gelöscht.
Das Löschen wird auch beim Starten vom PC aufgerufen (Cronjob).

Code: Alles auswählen

def beende_anwendung(lock_file):
    if Path.exists(lock_file):
        Path.unlink(lock_file)
        exit()   
        
def pruefe_lock_file(lock_file):
    if Path.exists(lock_file):
        print("Anwendung schon geöffnet")      
        exit()
    else:
        with open (lock_file, 'w') as file:
            print("starte Anwendung")

def meine_anwendung():
    lock_file = Path('/var/tmp/meine_anwendung_lock')
    pruefe_lock_file(lock_file)
    root = tk.Tk()
    tk.Button(root, text='Beenden', command=partial(beende_anwendung,
                                                    lock_file)).pack()    
    root.mainloop()    
    
if __name__ == "__main__":
    meine_anwendung()
Der Nachteil ist halt, wenn das Script abgewürgt wird, muss das lock_file von Hand oder mittels Neustart der Maschine gelöscht werden.
Gruss Peter

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Freitag 18. Februar 2022, 13:58
von __deets__
Genau wegen dieses Problem benutzt man nicht einfach nur ein File, sondern lockt das. Denn das File-Lock ist gekoppelt an den Prozess, und wenn der Abschmiert, dann geht das weg. Ohne das die Datei entfernt werden muss.

Vor Jahren habe ich mal das hier geschrieben: https://github.com/AbletonAG/abl.util/b ... ockfile.py

Das funktioniert unabhaengig vom OS.

Re: Abfragen, ob gestartetes Skript bereits läuft

Verfasst: Montag 21. Februar 2022, 10:33
von peterpy
__deets__'s Lösung finde ich die bisher beste.
Gruss Peter