2017-01-08 59 views
1

我需要終止外部程序,這些程序通過一個特定信號從一個asyncio Python腳本運行,如SIGTERM。我的問題是,即使我發送它們SIGTERM信號,程序總是收到SIGINT。用特定信號終止外部程序通過asyncio運行

這裏是一個測試用例,下面的測試中使用的fakeprg的源代碼可以找到here

import asyncio 
import traceback 
import os 
import os.path 
import sys 
import time 
import signal 
import shlex 

from functools import partial 


class ExtProgramRunner: 
    run = True 
    processes = [] 

    def __init__(self): 
     pass 

    def start(self, loop): 
     self.current_loop = loop 
     self.current_loop.add_signal_handler(signal.SIGINT, lambda: asyncio.async(self.stop('SIGINT'))) 
     self.current_loop.add_signal_handler(signal.SIGTERM, lambda: asyncio.async(self.stop('SIGTERM'))) 
     asyncio.async(self.cancel_monitor()) 
     asyncio.Task(self.run_external_programs()) 

    @asyncio.coroutine 
    def stop(self, sig): 
     print("Got {} signal".format(sig)) 
     self.run = False 
     for process in self.processes: 
      print("sending SIGTERM signal to the process with pid {}".format(process.pid)) 
      process.send_signal(signal.SIGTERM) 
     print("Canceling all tasks") 
     for task in asyncio.Task.all_tasks(): 
      task.cancel() 

    @asyncio.coroutine 
    def cancel_monitor(self): 
     while True: 
      try: 
       yield from asyncio.sleep(0.05) 
      except asyncio.CancelledError: 
       break 
     print("Stopping loop") 
     self.current_loop.stop() 

    @asyncio.coroutine 
    def run_external_programs(self): 
     os.makedirs("/tmp/files0", exist_ok=True) 
     os.makedirs("/tmp/files1", exist_ok=True) 
     # schedule tasks for execution 
     asyncio.Task(self.run_cmd_forever("/tmp/fakeprg /tmp/files0 1000")) 
     asyncio.Task(self.run_cmd_forever("/tmp/fakeprg /tmp/files1 5000")) 

    @asyncio.coroutine 
    def run_cmd_forever(self, cmd): 
     args = shlex.split(cmd) 
     while self.run: 
      process = yield from asyncio.create_subprocess_exec(*args) 
      self.processes.append(process) 
      exit_code = yield from process.wait() 
      for idx, p in enumerate(self.processes): 
       if process.pid == p.pid: 
        self.processes.pop(idx) 
      print("External program '{}' exited with exit code {}, relauching".format(cmd, exit_code)) 


def main(): 
    loop = asyncio.get_event_loop() 

    try: 
     daemon = ExtProgramRunner() 
     loop.call_soon(daemon.start, loop) 

     # start main event loop 
     loop.run_forever() 
    except KeyboardInterrupt: 
     pass 
    except asyncio.CancelledError as exc: 
     print("asyncio.CancelledError") 
    except Exception as exc: 
     print(exc, file=sys.stderr) 
     print("====", file=sys.stderr) 
     print(traceback.format_exc(), file=sys.stderr) 
    finally: 
     print("Stopping daemon...") 
     loop.close() 


if __name__ == '__main__': 
    main() 

回答

1

這樣做的原因是:當你開始你的Python程序(父)和它開始它的處理/tmp/fakeprg(孩子),他們獲得與它的PID所有不同的過程,但他們都run in the same foreground process group。你的shell綁定到該組,所以當你打Ctrl-CSIGINT),Ctrl-YSIGTSTP)或Ctrl-\SIGQUIT)他們是前臺進程組發送到所有進程

在你的代碼中,父母甚至可以通過send_signal發送信號給它的孩子,所以這一行發送一個信號給一個已經死了的進程(並且應該失敗,所以IMO這是asyncio的問題)。

爲了解決這個,你可以明確地把你的子進程到一個單獨的進程組,這樣的:

asyncio.create_subprocess_exec(*args, preexec_fn=os.setpgrp)