在Programming Language Examples Alike Cookbook關于Sockets的章節中,“Pre-Forking Servers”部分使用了這樣的 SIGCHLD 處理程式:
module PidSet = Set.Make(struct type t = int let compare = compare end)
let children = ref PidSet.empty
(* takes care of dead children *)
let rec reaper _ =
Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper);
match Unix.wait ()
with (pid, _) -> children := PidSet.remove pid !children
(* ... *)
let () =
(* ... *)
Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper);
(* ... *)
該reaper
功能看起來不對,但我不太確定。我擔心兩個或更多孩子碰巧幾乎同時退出的情況。該reaper
函式可能只運行一次,因為信號在 Unix 中沒有排隊。結果是只有一個孩子被收割,而其他孩子仍然是僵尸。
reaper
我從“分叉服務器”部分查看了一個替代功能:
let rec reaper signal =
try while true do ignore (Unix.waitpid [Unix.WNOHANG] (-1)) done
with Unix.Unix_error (Unix.ECHILD, _, _) -> ();
Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
但是,這似乎也是錯誤的,因為Unix.waitpid [Unix.WNOHANG] (-1)
可能永遠不會導致任何Unix.Unix_error
,從而導致大量回圈Unix.waitpid [Unix.WNOHANG] (-1)
始終回傳 pid 值0
。
這是我嘗試撰寫一個正確的 SIGCHLD 處理程式,以獲取“預分叉服務器”部分的所有終止子節點:
let rec reaper _ =
try
while true do
let (pid, _) = Unix.waitpid [Unix.WNOHANG] (-1) in
if pid > 0 then
children := PidSet.remove pid !children
else
raise Not_found (* Exit loop. *)
done
with Not_found -> ();
Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)
它是否正確?
uj5u.com熱心網友回復:
這是我使用多年的版本。此函式設定為帶有 的Sys.sigchld
處理程式Sys.set_signal Sys.sigchld (Sys.Signal_handle reap)
。
let rec reap n = match Unix.waitpid [Unix.WNOHANG] (-1) with
| 0,_ -> ()
| _ -> reap n
| exception Unix.Unix_error (Unix.ECHILD,_,_) -> ()
| exception _ -> (* log it *) ()
請注意,該函式在waitpid
回傳一個孩子時遞回呼叫自身,直到它耗盡所有完成的孩子,然后再次等待信號。
例外是安靜的ECHILD
,因為它通常發生在您嘗試使已經收獲的孩子成熟時(這發生在 OCaml 中,請參閱下面的 PPS 注釋)。在實際應用程式中,我記錄了其余的例外,只是為了確保系統的行為符合我的預期(各種 Unices 有不同的行為,上面的代碼僅在 Linux 和 macOS (Darwin) 上經過全面測驗)。
關于您的實作,它非常相似,只是您沒有顯式捕獲例外,因此它們可能出現在代碼的隨機部分(在記憶體分配期間異步呼叫信號處理程式,因此最好不要允許例外跳出他們)。我不知道為什么需要一個children
集合,可能它是特定于應用程式的,但你不需要它來保持你的行程表沒有僵尸,因為內核中已經存在這樣的集合。
PS,重要的是,對于小于 4.13 的 OCaml 版本,如果您的服務器應用程式沒有執行任何分配或阻塞呼叫,例如,如果它正在執行while true; do () done
or let rec loop () = loop ()
,則不會呼叫任何信號處理程式。對于更現代的 OCaml 版本(從 4.13 及更高版本開始的任何版本),這不再是真的,感謝@octachron 的澄清。
PPS,以及 OCaml 信號處理的另一個警告,
reaper 函式可能只運行一次,因為信號在 Unix 中沒有排隊。
這通常是正確的,除了在 OCaml 中信號確實是排隊的。當信號到達時,它會排隊等待直到下一個同步點,即下一個分配或阻塞系統呼叫。一旦發生,OCaml 會為佇列中待處理的每個信號呼叫信號處理程式。當然,不能保證不會丟失任何信號,因此您的保守假設仍然有效 - 我們不能確定每個孩子都會收到一個信號。但是可能發生的情況是,我們可以接收到我們已經在reap
信號處理程式的上一個觸發器中獲得的孩子的信號。這就是我ECHILD
明確沉默的原因。
購買力平價,
在我的 reaper 函式中,我再次呼叫 Sys.set_signal Sys.sigchld (Sys.Signal_handle reaper)。那有必要嗎?我注意到您的實作沒有它。
You don't need to reinstall the signal handler. Once it is installed it will be there until reinstalled.1 Though, reinstalling it won't really hurt I would advise against having a recursive function that doesn't really call itself recursively but via a signal handler. It violates the single function single responsibility principle, as your function now has two orthogonal responsibilities - one is to reap the children and another is to install itself as the signal handler. It is much better to have the signal handler that does its work and install it separately and once in the server initialization procedure.
1) In a POSIX-compliant system, or in fact if sigaction
and sigprocmask
are available, OCaml uses sigaction
to install signals and ensures that the signal handler is kept attached. Therefore it is guaranteed that on Linux and macOS you don't need to reinstall the signals. When BSD semantics is detected (and POSIX signals are not detected), the signal
function will be used, but it will still keep the handler attached (as it is the BSD semantics). There is still a possibility of a system that lacks both POSIX-compliant signal system and BSD signals, i.e., pure System V, but most likely for such a system, our code will have many other problems, like most of the Unix functions will be unimplemented, including waitpid
. With that said, you can reinstall the signal, it won't hurt, but I wouldn't do it.
轉載請註明出處,本文鏈接:https://www.uj5u.com/net/434308.html
上一篇:Windows下的Unix行尾