2014-03-28 25 views
1

我最近在Mac OS X中遇到的ECONNRESET heisenbug般的出現與二郎(不是我的機器!)。爲了簡化問題的根源,爲了更好地理解錯誤,我編寫了以下模塊來觸發ECONNRESET。不幸的是,我自己並不擁有Mac。在我的Linux(ArchLinux的)下面的代碼不會隨着{error,econnreset}一個badmatch崩潰,但{error,closed}代替。Erlang:如何在Linux上觸發ECONNRESET?

-module(econnreset). 

-export([run/0]). 

run() -> 
    Pid = spawn_link(fun listen_and_accept/0), 
    Pid ! {get_port,self()}, 
    Port = receive {port,P} -> P end, 

    {ok,Socket} = gen_tcp:connect("localhost", Port, [{active,false}]), 
    ok = gen_tcp:send(Socket, lists:duplicate(100, $a)), 

    %% On the following line, I expected a badmatch w/ {error,econnreset} 
    {ok,_} = gen_tcp:recv(Socket, 0, 1000), 
    ok = gen_tcp:close(Socket), 
    ok. 

listen_and_accept() -> 
    {ok,LSocket} = gen_tcp:listen(0, [binary,{active,false}]), 
    {ok,Port} = inet:port(LSocket), 
    receive {get_port,Pid} -> Pid ! {port,Port} end, 

    {ok,Socket} = gen_tcp:accept(LSocket), 
    {ok,Bin} = gen_tcp:recv(Socket, 1), %=> read only 1 byte to trigger tcp RST 
    io:format("Server read ~p~n", [Bin]), 
    gen_tcp:close(Socket), 
    gen_tcp:close(LSocket). 

正如我只能在Mac OS X中遇到ECONNRESET但並沒有在Linux上,這可以歸結爲兩個問題:

  1. 是上面甚至能生產ECONNRESET的代碼通過調用recv/2
  2. Mac OS X和Linux的行爲在這裏有所不同嗎?

回答

1

一些場景中,會發生這種情況。

試圖發送一個插座,其中遠程端點已經開始關閉它的連接會導致ECONNRESET結束新的數據。 (有時可能導致SIGPIPE得到提升)。

通常,因爲大多數插座代碼被通過檢測的recv()調用返回0。

它也可以發生,如果遠程主機崩潰或者失去電力通知已關閉的連接的,這是難以重現。典型的場景是兩臺主機之間有一個活動的TCP連接。兩臺主機都不使用TCP保持活動,而只是週期性地發送/接收對方之間的數據。目前,假設兩臺主機都處於空閒狀態,並且沒有任何數據可以相互發送。

的第一主機,主機A,遭受臨時停電(即拉電源線),並重新啓動。另一個主機B主機沒有意識到中斷。因此,TCP狀態機認爲套接字仍處於連接狀態。在某個時候,HOST B嘗試在套接字上發送數據。 IP數據包最終會到達主機A.但是,自主機重啓後,主機A的TCP狀態機沒有HOST B IP:端口的連接記錄。因此,它唯一能做的就是發回一個RST通知主機B連接已經死亡。

我聽說過,但我可能是錯的......,一個插座可以被強制進入關閉狀態,而無需使用SO_LINGER與零值發送一個FIN。因此,由另一方發送的後續數據將導致RST作爲響應。