gen_server ที่ไม่ได้รับการดูแลจะไม่เรียกยุติเมื่อได้รับสัญญาณออก

gen_server เอกสารประกอบใน Module:terminate โทรกลับพูดว่า:

แม้ว่ากระบวนการ gen_server จะไม่ได้เป็นส่วนหนึ่งของแผนผังการควบคุมดูแล แต่ฟังก์ชันนี้จะถูกเรียกใช้หากได้รับข้อความ 'EXIT' จากผู้ปกครอง เหตุผลเหมือนกับในข้อความ 'EXIT'

นี่คือฟังก์ชัน handle_info และ terminate ของฉัน:

handle_info(UnknownMessage, State) ->
    io:format("Got unknown message: ~p~n", [UnknownMessage]),
    {noreply, State}.

terminate(Reason, State) ->
    io:format("Terminating with reason: ~p~n", [Reason]).

ฉันเริ่มเซิร์ฟเวอร์นี้โดยใช้ gen_server:start ฉันคิดว่าเมื่อฉันโทร erlang:exit(Pid, fuckoff) มันควรจะเรียกใช้ฟังก์ชันการโทรกลับ terminate แต่มันแสดงให้เห็นว่า:

Got unknown message: {'EXIT',<0.33.0>,fuckoff}

ซึ่งหมายความว่ากำลังโทร handle_info แต่เมื่อฉันโทร gen_server:stop ทุกอย่างทำงานได้ตามที่กล่าวไว้ในเอกสารประกอบ ฉันกำลังเรียก gen_server ของฉันจากเชลล์ คุณช่วยชี้แจงเรื่องนี้หน่อยได้ไหม?

[อัปเดต]

ที่นี่ คือซอร์สโค้ดของฟังก์ชัน decode_msg ภายใน gen_server หากได้รับข้อความ 'EXIT' ควรเรียกใช้ฟังก์ชัน terminate:

decode_msg(Msg, Parent, Name, State, Mod, Time, Debug, Hib) ->
    case Msg of
    {system, From, Req} ->
        sys:handle_system_msg(Req, From, Parent, ?MODULE, Debug,
                  [Name, State, Mod, Time], Hib);
    {'EXIT', Parent, Reason} ->
        terminate(Reason, Name, Msg, Mod, State, Debug);
    _Msg when Debug =:= [] ->
        handle_msg(Msg, Parent, Name, State, Mod);
    _Msg ->
        Debug1 = sys:handle_debug(Debug, fun print_event/3,
                      Name, {in, Msg}),
        handle_msg(Msg, Parent, Name, State, Mod, Debug1)
end.

ในกรณีของฉัน มันไม่เรียกใช้ฟังก์ชัน terminate

[อัปเดต]

เมื่อฉันเริ่ม gen_server โดยใช้ gen_server:start_link() การส่งสัญญาณออกโดยใช้ erlang:exit(Pid, Reason) จะส่งผลให้มีการเรียกฟังก์ชันโทรกลับ terminate ซึ่งเป็นลักษณะการทำงานที่คาดไว้ ดูเหมือนว่ามีความแตกต่างในการตีความสัญญาณทางออกไม่ว่ากระบวนการจะเชื่อมโยงกับพาเรนต์หรือไม่ก็ตาม


person Majid Azimi    schedule 10.09.2016    source แหล่งที่มา
comment
คุณได้ตั้งค่าสถานะ trap_exit ของกระบวนการเป็น true ที่ไหนสักแห่งในโค้ดหรือไม่   -  person Hamidreza Soleimani    schedule 25.09.2016
comment
ใช่. ภายในฟังก์ชัน init   -  person Majid Azimi    schedule 25.09.2016


คำตอบ (2)


คำตอบสั้นๆ:

หากคุณเรียกใช้ฟังก์ชัน exit/2 จากภายในตัวแสดง gen_server ฟังก์ชันจะทำงานตามที่คาดไว้โดยอิงจากเอกสารประกอบ และฟังก์ชัน terminate/2 จะถูกเรียกกลับ

คำตอบยาว:

เมื่อคุณส่งข้อความทางออกจากเชลล์ ค่า Parent ของ exit tuple จะถูกตั้งค่าเป็นรหัสกระบวนการของเชลล์ ในทางกลับกัน เมื่อคุณเริ่มกระบวนการ gen_server ในรูปแบบเชลล์ ค่า Parent จะถูกตั้งค่าเป็นรหัสกระบวนการของตัวเอง ไม่ใช่เชลล์ รหัสกระบวนการ ดังนั้นเมื่อได้รับข้อความออก มันจะไม่ตรงกับส่วนคำสั่งที่สองของบล็อกการรับในฟังก์ชัน decode_msg/8 ดังนั้นจึงไม่มีการเรียกใช้ฟังก์ชัน terminate/6 และสุดท้ายส่วนคำสั่งถัดไปจะถูกจับคู่ซึ่งกำลังเรียกใช้ฟังก์ชัน handle_msg/5

คำแนะนำ:

สำหรับการรับการเรียกกลับ terminate/3 โดยการส่งข้อความทางออกไปยังกระบวนการ gen_server คุณสามารถดักจับข้อความทางออกใน handle_info/2 แล้วกลับมาโดยหยุด tuple ดังนี้:

init([]) ->
    process_flag(trap_exit, true),
    {ok, #state{}}.

handle_info({'EXIT', _From, Reason}, State) ->
    io:format("Got exit message: ~p~n", []),
    {stop, Reason, State}.

terminate(Reason, State) ->
    io:format("Terminating with reason: ~p~n", [Reason]),
    %% do cleanup ...
    ok.
person Hamidreza Soleimani    schedule 25.09.2016
comment
ฉันเข้าใจแล้ว. ฉันเห็นสิ่งนั้นใน observer เหตุใดผู้ปกครองจึงถูกตั้งค่าเป็นรหัสกระบวนการของตัวเองเมื่อฉันเริ่มกระบวนการในเชลล์ มีเหตุผลพิเศษอะไรไหม? - person Majid Azimi; 25.09.2016
comment
@MajidAzimi: ตามรหัส gen:get_parent/0 ซึ่งรับผิดชอบในการค้นหาพาเรนต์ของ gen_server ค้นหารายการแรกของ $ancestors คีย์ในพจนานุกรมกระบวนการ เนื่องจากกระบวนการ gen_server เป็นกระบวนการ OTP ซึ่งเริ่มต้นด้วย proc_lib จึงควรมีการเรียก ID กระบวนการของนักแสดงในพจนานุกรม $ancestors ซึ่งเป็น ID กระบวนการเชลล์ในกรณีนี้และตั้งเป็นพาเรนต์ แต่มันไม่ทำงานตามที่คาดไว้ . ฉันต้องการเวลามากกว่านี้เพื่อค้นหาว่าเป็นข้อบกพร่องหรืออย่างอื่น - person Hamidreza Soleimani; 25.09.2016
comment
เท่าที่ฉันสามารถตรวจสอบได้ เมื่อฉันเริ่มกระบวนการ OTP โดยใช้ gen_*:start parent pid จะถูกตั้งค่าเป็นกระบวนการที่สร้างขึ้นใหม่ เมื่อเริ่มต้นด้วย gen_*:start_link จะถูกตั้งค่าเป็นองค์ประกอบแรกของ $ancestors ไม่สำคัญว่าพาเรนต์ที่แท้จริงจะเป็นเชลล์หรือ gen_* อื่น ในทั้งสองกรณี $ancestors มีพาเรนต์ทั้งหมดจนถึงรูท gen_*:start_link ใช้องค์ประกอบแรกของ $ancestors เพื่อรับพาเรนต์ (ซึ่งเป็นพฤติกรรมที่คาดหวัง) แต่ด้วยเหตุผลบางอย่าง gen_*:start ใช้ pid ของตัวเองเป็นพาเรนต์ - person Majid Azimi; 25.09.2016

เมื่อคุณเริ่ม gen_server มันเป็นกระบวนการง่ายๆ ดังนั้น erlang:exit/1 หรือ erlang:exit/2 ทำงานตามที่คาดไว้

  • ถ้า Pid ไม่ได้ดักทางออก Pid เองก็ออกโดยมีเหตุผลในการออก
  • หาก Pid กำลังดักทางออก สัญญาณทางออกจะถูกแปลงเป็นข้อความ {'EXIT', From, Reason} และส่งไปยังคิวข้อความของ Pid

ดังนั้น ขณะนี้โค้ดของคุณดักจับสัญญาณ 'EXIT' เนื่องจากข้อความนี้ถูกส่งเหมือนกับข้อความอื่น ๆ ไปยังกล่องจดหมายและตรงกับรูปแบบไวด์การ์ด handle_info/2

หากคุณต้องการข้อมูลเพิ่มเติมเกี่ยวกับเรื่องนั้น คุณสามารถอ่าน gen_server ซอร์สโค้ดและดูวิธีการทำงาน คุณยังสามารถค้นหาปัญหาของคุณ อธิบายไว้ในรหัสนี้

person Mathieu Kerjouan    schedule 10.09.2016
comment
ให้ฉันถามคำถามด้วยวิธีนี้: gen_server:stop ยังส่งสัญญาณออกด้วย มันจะถูกแปลงเป็นข้อความ 'ออก' gen_server แยกข้อความนี้จากข้อความ 'ออก' ของฉันอย่างไร (ซึ่งมาจาก exit) - person Majid Azimi; 11.09.2016
comment
ปัจจุบัน gen_server:handle_info/2 ใช้เพื่อดักจับทุกสัญญาณ (แม้แต่สัญญาณพิเศษ) ดังนั้น หากคุณต้องการแยกสัญญาณ 'EXIT' จากสัญญาณอื่น คำจำกัดความของ handle_info จะต้องแตกต่างออกไป (แก้ไขการจับคู่รูปแบบ เพิ่มตัวป้องกัน...) เพื่อให้ชัดเจน 'EXIT' signal เป็นเหมือนแบบแผนมากกว่ามาตรฐานใน Erlang เนื่องจากเป็นเพียงข้อความธรรมดาๆ - person Mathieu Kerjouan; 11.09.2016