6

Uso di python 2.7.4 su Windows (Nota: WinXP - un commentatore qui sotto suggerisce che funziona correttamente su Win7), ho uno script che crea diversi thread ognuno dei quali esegue un processo figlio tramite Popen con lo stdout/stderr reindirizzato ai file e le chiamate wait(). Ogni Popen ha i suoi propri file stdout/stderr. Dopo ogni ritorni di processo, a volte ho per eliminare i file (in realtà li sposta altrove).Python Popen su Windows con multithreading - impossibile cancellare i log stdout/stderr

Sto trovando che non riesco a cancellare i log stdout/stderr fino a dopo che tutte le chiamate wait() ritornano. Prima di ciò ricevo "WindowsError: [Errore 32] Il processo non può accedere al file perché è utilizzato da un altro processo ". Sembra che Popen stia trattenendo in qualche modo i file stderr fino a quando c'è almeno un processo figlio aperto, anche se i file non sono condivisi.

Codice di prova da riprodurre di seguito.

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] 

Se si esegue questo, vedrete che ogni retryDelete() gira su un errore di accesso al file fino a quando tutti i processi figli sono finiti.

UPDATE: il problema si verifica anche se i descrittori di file stdof e stdef non vengono passati al costruttore Popen. Tuttavia, ciò non avviene (cioè le eliminazioni avvengono immediatamente) se il Popen viene rimosso e l'attesa() sostituita con time.sleep (self.idx). Dal momento che il Popen sembra avere un effetto sui descrittori di file che non sono passati ad esso mi chiedo se questo problema è legato alla gestione dell'ereditarietà.

UPDATE: close_fds = True dà un errore (non supportato su Windows quando il reindirizzamento output/error), e l'eliminazione dell'oggetto Popen con del p dopo la chiamata wait() non fa alcuna differenza per il problema.

UPDATE: sysinternals utilizza explorer di processo per cercare i processi con le maniglie nel file. Ridotto il test a soli 2 thread/bambini e reso il secondo rimane aperto a lungo. La ricerca handle ha mostrato che l'unico processo con handle su stderr0.log era il processo padre python, con due handle aperti.

UPDATE: Per il mio attuale, l'uso urgente, ho trovato una soluzione, che è quello di creare uno script separato che prende la riga di comando e stderr/log stdout file come parametri e viene eseguito il processo figlio reindirizzato. Il genitore esegue quindi questo script di supporto con os.system(). I file di registro vengono quindi liberati correttamente e vengono eliminati. Tuttavia, sono comunque molto interessato alla risposta a questa domanda. Mi sembra un bug specifico per WinXP, ma è ancora possibile che stia facendo qualcosa di sbagliato.

+0

Si suppone che si passi 'stdof' e' stdef' a 'Popen'? –

+0

Sì, grazie, Janne. Tuttavia, questo non è correlato al problema, che persiste dopo la correzione. Ho aggiornato l'esempio. – Tom

+0

Hmmm - interessante, però. Forse il problema non è legato a Popen. Probabilmente sto solo facendo qualcosa di stupido ... – Tom

risposta

0

Si può provare ad aggiornare a Win7, so che questo è un errore comune negli utenti WinXP.

0

Questo problema è obsoleto e questo bug è stato corretto su Python 3.4+. Per la cronaca, ecco un trucco hacky che abbiamo usato per risolvere il problema su python 2.7 o python 3.3-

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

==> Prima di avviare il sottoprocesso, chiamare la seguente funzione

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) 

Questa funzione elaborerà gli ultimi 100 descrittori di file che sono stati aperti e impostarle come "No modalità eredità" , che risolverà il bug. Il numero 100 può essere aumentato se necessario.

Problemi correlati