6

Mit Python 2.7.4 unter Windows (Hinweis: WinXP - ein Kommentar unten schlägt vor, dies funktioniert ordnungsgemäß auf Win7), habe ich ein Skript, das mehrere Threads erstellt Jede davon führt einen untergeordneten Prozess über Popen mit der stdout/stderr umgeleitet zu Dateien und Aufrufe wait(). Jeder Popen hat seine eigenen stdout/stderr Dateien. Nach jedem Prozess gibt es manchmal , um die Dateien zu löschen (tatsächlich verschieben sie sie woanders).Python Popen unter Windows mit Multithreading - kann nicht stdout/stderr logs

Ich finde, dass ich die stdout/stderr Protokolle erst löschen kann, nachdem alle wait() -Aufrufe zurückgeben. Vorher bekomme ich "WindowsError: [Fehler 32] Der Prozess kann nicht auf die Datei zugreifen, weil sie von einem anderen Prozess verwendet wird". Es scheint, dass Popen die stderr-Dateien so lange festhält, wie mindestens ein untergeordneter Prozess geöffnet ist, obwohl die Dateien nicht gemeinsam genutzt werden.

Testcode zur Reproduktion unten.

C: \ test1.py

import subprocess 
import threading 
import os 

def retryDelete(p, idx): 
    while True: 
     try: 
      os.unlink(p) 
     except Exception, e: 
      if "The process cannot access the file because it is being used by another process" not in e: 
       raise e 
     else: 
      print "Deleted logs", idx 
      return 

class Test(threading.Thread): 
    def __init__(self, idx): 
     threading.Thread.__init__(self) 
     self.idx = idx 

    def run(self): 
     print "Creating %d" % self.idx 
     stdof = open("stdout%d.log" % self.idx, "w") 
     stdef = open("stderr%d.log" % self.idx, "w") 
     p = subprocess.Popen("c:\\Python27\\python.exe test2.py %d" % self.idx, 
          stdout=stdof, stderr = stdef) 
     print "Waiting %d" % self.idx 
     p.wait() 
     print "Starting deleting logs %d" % self.idx 
     stdof.close() 
     stdef.close() 
     retryDelete("stderr%d.log" % self.idx, self.idx) 
     print "Done %d" % self.idx 

threads = [Test(i) for i in range(0, 10)] 
for thread in threads: 
    thread.start() 
for thread in threads: 
    thread.join() 

c: \ test2.py:

import time 
import sys 

print "Sleeping",sys.argv[1] 
time.sleep(int(sys.argv[1])) 
print "Exiting",sys.argv[1] 

Wenn Sie dies ausführen, werden Sie sehen, dass jede retryDelete() dreht sich auf der Zugriffsfehlerdatei bis alle Kindprozesse beendet sind.

UPDATE: Das Problem tritt auf, selbst wenn die stdof und stdef Dateideskriptoren nicht in den Popen-Konstruktor übergeben werden. Es geschieht jedoch nicht (d. H. Die Löschvorgänge werden sofort ausgeführt), wenn Popen entfernt und wait() durch time.sleep (self.idx) ersetzt wird. Da der Popen anscheinend Auswirkungen auf Dateideskriptoren hat, die nicht an ihn übergeben werden, frage ich mich, ob dieses Problem mit der Vererbung von Handles zu tun hat.

UPDATE: close_fds = True gibt einen Fehler (unter Windows nicht unterstützt, wenn stdout/stderr Umleitung) und mit del p nach dem wait() Aufruf des Popen Objekt zu löschen macht keinen Unterschied zu dem Thema.

UPDATE: Sysinternals Process Explorer verwendet, um nach Prozessen mit Handles für die Datei zu suchen. Reduzierte den Test auf nur 2 Threads/Kinder und ließ die zweite lange offen bleiben. Die Handle-Suche zeigte, dass der einzige Prozess mit Handles zu stderr0.log der Parent-Python-Prozess war, für den zwei Handles geöffnet waren.

UPDATE: Für meinen aktuellen, dringenden Gebrauch, ich habe eine Abhilfe gefunden, die ein separates Skript zu erstellen, die die Befehlszeile nimmt und stderr/stdout-Log-Dateien als Parameter und führen das Kind Prozess umgeleitet. Das übergeordnete Element führt dieses Hilfsskript nur mit os.system() aus. Die Protokolldateien werden dann erfolgreich freigegeben und gelöscht. Ich bin jedoch immer noch an der Antwort auf diese Frage interessiert. Es fühlt sich für mich wie ein WinXP-spezifischer Fehler an, aber es ist immer noch möglich, dass ich nur etwas falsch mache.

+0

Sollst du "stdof" und "stdef" an "Popen" weitergeben? –

+0

Ja - Danke, Janne. Das hängt jedoch nicht mit dem Problem zusammen, das nach der Behebung weiterhin besteht. Ich habe das Beispiel aktualisiert. – Tom

+0

Hmmm - interessant, obwohl. Vielleicht hängt das Thema nicht mit Popen zusammen. Ich mache wahrscheinlich nur etwas dummes ... – Tom

Antwort

0

Sie können versuchen, auf Win7 zu aktualisieren, ich kenne dies einen häufigen Fehler in WinXP-Benutzer.

0

Dieses Problem ist alt, und dieser Fehler wurde auf Python 3.4 + behoben. Zur Erinnerung, hier ist ein hacky Trick, den wir verwendet haben, um das Problem auf Python 2.7 oder Python 3 zu beheben.3-

This function is made in pure python (no external APIs), and only works on Windows !

==> Vor dem subprocess starten, rufen Sie die folgende Funktion

def _hack_windows_subprocess(): 
    """HACK: python 2.7 file descriptors. 
    This magic hack fixes https://bugs.python.org/issue19575 
    by adding HANDLE_FLAG_INHERIT to all already opened file descriptors. 
    """ 
    # Extracted from https://github.com/secdev/scapy/issues/1136 
    import stat 
    from ctypes import windll, wintypes 
    from msvcrt import get_osfhandle 

    HANDLE_FLAG_INHERIT = 0x00000001 

    for fd in range(100): 
     try: 
      s = os.fstat(fd) 
     except: 
      break 
     if stat.S_ISREG(s.st_mode): 
      handle = wintypes.HANDLE(get_osfhandle(fd)) 
      mask = wintypes.DWORD(HANDLE_FLAG_INHERIT) 
      flags = wintypes.DWORD(0) 
      windll.kernel32.SetHandleInformation(handle, mask, flags) 

dieser Funktion werden die letzten 100 Dateideskriptoren wird verarbeiten, und sie als „keine Vererbungsmodus“ geöffnet wurden, , der den Fehler beheben wird. Die 100-Nummer kann bei Bedarf erhöht werden.