在docker里,使用selenium启动chrome做截图的时候,webdriver quit后,会产生很多僵尸进程。
def screenshot(url):
current_path = os.path.join(os.path.dirname(__file__)) # 文件的暂存路径
chrome_options = Options()
chrome_options.add_argument('--headless')
chrome_options.add_argument('--disable-gpu')
chrome_options.add_argument('--no-sandbox')
chrome_options.add_argument('--disable-dev-shm-usage')
browser_exe_path = os.path.join(current_path, "chromedriver")
browser = webdriver.Chrome(executable_path=browser_exe_path,
chrome_options=chrome_options)
browser.get(url)
time.sleep(5)
size = browser.find_element_by_xpath("//main").size
browser.set_window_size(1200, size.get("height"))
pic_path = os.path.join(current_path, "screenshot.png")
browser.save_screenshot(pic_path)
browser.quit()
在调查了进程树之后,终于发现:chromedriver fork出了多个子进程和一个孙进程,而调用quit后,chrome driver正常退出,而它的子孙们变成了孤儿进程,被托管给了docker的1号进程,也就是docker的启动进程。这些孤儿进程退出后,根据孤儿进程托管的优先级,最终由1号进程托管,而1号进程没有处理(wait/waitpid),这些进程就变成了僵尸进程。
但是!!!这个1号进程和宿主机的init进程有着本质的区别,这个1号进程,并没有能力处理子进程退出的信号,所以导致僵尸进程没有人“收尸”。
僵尸进程是kill不掉的。最终,解决办法是,在1号进程加了一行代码:
signal.signal(signal.SIGCLD, signal.SIG_IGN)
在Linux下可以简单地将SIGCHLD信号的操作设为SIG_IGN,这样,内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同,BSD4下必须显式等待子进程结束才能释放僵尸进程。