2013-02-22 17 views
8

Ho timeout contesto gestore che funziona perfettamente con i segnali ma solleva errore nella modalità multithread perché segnali funzionano solo in thread principale.pitone timeout gestore contesto con fili

def timeout_handler(signum, frame): 
    raise TimeoutException() 

@contextmanager 
def timeout(seconds): 
    old_handler = signal.signal(signal.SIGALRM, timeout_handler) 
    signal.alarm(seconds) 
    try: 
     yield 
    finally: 
     signal.alarm(0) 
     signal.signal(signal.SIGALRM, old_handler) 

Ho visto implementazione decoratore di timeout, ma non so come passare yield all'interno classe derivata da threading.Thread. La mia variante non funzionerà.

@contextmanager 
def timelimit(seconds): 
    class FuncThread(threading.Thread): 
     def run(self): 
      yield 

    it = FuncThread()   
    it.start() 
    it.join(seconds) 

    if it.isAlive(): 
     raise TimeoutException() 

risposta

-2

I timeout per le chiamate di sistema sono eseguiti con i segnali. La maggior parte delle chiamate di sistema di blocco ritornano con EINTR quando si verifica un segnale, quindi è possibile utilizzare l'allarme per implementare i timeout.

Ecco un manager contesto che funziona con la maggior parte delle chiamate di sistema, causando IOError di essere sollevato da una chiamata di sistema di bloccaggio se ci vuole troppo tempo.

import signal, errno 
from contextlib import contextmanager 
import fcntl 

@contextmanager 
def timeout(seconds): 
    def timeout_handler(signum, frame): 
     pass 

    original_handler = signal.signal(signal.SIGALRM, timeout_handler) 

    try: 
     signal.alarm(seconds) 
     yield 
    finally: 
     signal.alarm(0) 
     signal.signal(signal.SIGALRM, original_handler) 

with timeout(1): 
    f = open("test.lck", "w") 
    try: 
     fcntl.flock(f.fileno(), fcntl.LOCK_EX) 
    except IOError, e: 
     if e.errno != errno.EINTR: 
      raise e 
     print "Lock timed out" 
+0

Come nel mio primo variante ho ottenuto 'ValueError: segnale funziona solo in thread' principale riga 'original_handler = signal.signal (signal.SIGALRM, timeout_handler)' – San4ez

+1

Come indicato dall'OP, * i segnali funzionano solo nel thread principale *. L'OP necessita invece di una soluzione diversa. –

3

non riesco a vedere un modo di fare ciò che si sta proponendo con un contesto Manager, è possibile non yield il flusso da un thread ad un altro. Quello che vorrei fare è avvolgere la tua funzione con un thread interrompibile con il timeout. Ecco uno recipe per quello.

Avrete un thread in più e la sintassi non sarà così bella ma funzionerebbe.

+2

Si noti che il thread interrompibile descritto dalla ricetta non è effettivamente interrotto e continua a funzionare. AFAIK non c'è un modo affidabile per interrompere un thread python non principale. – Lethargy

9

Se il codice custodito dal contesto manager è basata su loop, si consideri la manipolazione di questo il modo di trattare il filetto di uccisione. Uccidere un altro thread non è generalmente sicuro, quindi l'approccio standard prevede che il thread di controllo imposti un flag visibile al thread worker. Il thread worker controlla periodicamente tale flag e si chiude in modo pulito. Ecco come si può fare qualcosa di analogo con timeout:

class timeout(object): 
    def __init__(self, seconds): 
     self.seconds = seconds 
    def __enter__(self): 
     self.die_after = time.time() + self.seconds 
     return self 
    def __exit__(self, type, value, traceback): 
     pass 
    @property 
    def timed_out(self): 
     return time.time() > self.die_after 

Ecco un thread singolo esempio di utilizzo:

with timeout(1) as t: 
    while True: # this will take a long time without a timeout 
     # periodically check for timeouts 
     if t.timed_out: 
      break # or raise an exception 
     # do some "useful" work 
     print "." 
     time.sleep(0.2) 

e multithread uno:

import thread 
def print_for_n_secs(string, seconds): 
    with timeout(seconds) as t: 
     while True: 
      if t.timed_out: 
       break # or raise an exception 
      print string, 
      time.sleep(0.5) 

for i in xrange(5): 
    thread.start_new_thread(print_for_n_secs, 
          ('thread%d' % (i,), 2)) 
    time.sleep(0.25) 

questo approccio è più intrusivo di usando i segnali ma funziona per i thread arbitrari.

+0

È un approccio possibile ma non così breve e chiaro come mi piacerebbe avere.La tua variante richiede di avvolgere il codice con funzioni come decoratore, ma è il nuovo approccio per me, quindi ti ho dato la taglia. Grazie. – San4ez

0

so che è tardi, ma sto solo ora la lettura di questo, ma per quanto riguarda la creazione del proprio gestore di segnalatore/contesto? Sono nuovo di Python piacerebbe ricevere feedback da sviluppatori esperti questa implementazione.

Questo si basa fuori della risposta da "Mr Fooz"

class TimeoutSignaller(Thread): 
    def __init__(self, limit, handler): 
     Thread.__init__(self) 
     self.limit = limit 
     self.running = True 
     self.handler = handler 
     assert callable(handler), "Timeout Handler needs to be a method" 

    def run(self): 
     timeout_limit = datetime.datetime.now() + datetime.timedelta(seconds=self.limit) 
     while self.running: 
      if datetime.datetime.now() >= timeout_limit: 
       self.handler() 
       self.stop_run() 
       break 

    def stop_run(self): 
     self.running = False 

class ProcessContextManager: 
    def __init__(self, process, seconds=0, minutes=0, hours=0): 
     self.seconds = (hours * 3600) + (minutes * 60) + seconds 
     self.process = process 
     self.signal = TimeoutSignaller(self.seconds, self.signal_handler) 

    def __enter__(self): 
     self.signal.start() 
     return self.process 

    def __exit__(self, exc_type, exc_val, exc_tb): 
     self.signal.stop_run() 

    def signal_handler(self): 
     # Make process terminate however you like 
     # using self.process reference 
     raise TimeoutError("Process took too long to execute") 

Caso d'uso:

with ProcessContextManager(my_proc) as p: 
    # do stuff e.g. 
    p.execute()