Python multiprocessing模塊文件描述符泄露

背景

因爲最近公司的某個項目需要對接依圖科技的實時語音轉寫服務,但由於對方只提供了Java與Python的接口(其實依圖科技的實時語音轉寫接口採用的是grpc,雖然我給領導提過可以讓對方用proto文件快速生成其他語言的接口,但無賴領導沒有采納),而我們的產品是基於C/C++的,所以決定用Python寫一箇中轉服務(如果開始知道Python這麼多坑,我一定選Java)。

問題

由於GIL的存在,Python服務器就只能採用accept-fork模式,但服務運行一段時間後,拋出異常信息——文件描述符溢出——,確定fork前除了監聽套接字與連接套接字外,沒有創建其他文件描述符,而且fork之後在父進程關閉了連接套接字,在子進程關閉了監聽套接字,理論上不應該存在文件描述符泄露。

解決方案

最後使用lsof命令查看父進程的相關記錄,發現有未關閉的PIPE。後面查看multiprocessing的代碼,發現start方法會默認創建一個PIPE用於與子進程通信,該pipe的描述符保存在sentinel字段,官網關於sentinel的描述

A numeric handle of a system object which will become “ready”
when the process ends.
You can use this value if you want to wait on several events at once using multiprocessing.connection.wait(). Otherwise calling join() is simpler.
On Windows, this is an OS handle usable with the WaitForSingleObject and WaitForMultipleObjects family of API calls. On Unix, this is a file descriptor usable with primitives from the select module.

從官網的說明,大概能猜到Python因爲跨平臺的緣故,父進程需要從該PIPE來獲取子進程結束的消息。

multiprocessing除了close方法外,沒有其他方法能關閉該PIPE,但是在子進程未結束前運行close會拋出異常,而在我們的場景中,父進程又不能使用join來等待子進程結束。因此我最後使用OS.close()主動關閉該sentiel。

改進,有待測試

在父進程中將sentinel註冊到selector中,這樣就可以在不阻塞父進程的前提下又能知道子進程運行結束,然後調用close方法是否multiprocessing的資源。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章