Erlang 21 - 4. Robustness

4 रोबस्टनेस




erlang

4 रोबस्टनेस

A Larger Example में मैसेंजर उदाहरण के साथ कई चीजें गलत हैं। उदाहरण के लिए, यदि कोई नोड जहां एक उपयोगकर्ता लॉग ऑन है, तो लॉगऑफ़ किए बिना नीचे जाता है, तो उपयोगकर्ता सर्वर के User_List में रहता है, लेकिन क्लाइंट गायब हो जाता है। इससे उपयोगकर्ता के लिए फिर से लॉग इन करना असंभव हो जाता है क्योंकि सर्वर को लगता है कि उपयोगकर्ता पहले से लॉग ऑन है।

या अगर संदेश भेजने के बीच में सर्वर नीचे चला जाता है, तो ग्राहक को await_result फ़ंक्शन में हमेशा के लिए लटका दिया जाता है?

४.१ समय-बहिः

मैसेंजर प्रोग्राम को बेहतर बनाने से पहले, हम कुछ सामान्य सिद्धांतों को देखते हैं, उदाहरण के तौर पर पिंग पोंग प्रोग्राम का उपयोग करते हैं। याद रखें कि जब "पिंग" खत्म हो जाता है, तो यह "पोंग" बताता है कि उसने ऐसा किया है कि परमाणु को "पोंग" के संदेश के रूप में finished किया गया है ताकि "पोंग" भी समाप्त हो सके। "पोंग" खत्म करने का एक और तरीका यह है कि अगर एक निश्चित समय के भीतर पिंग से कोई संदेश प्राप्त नहीं होता है, तो "पोंग" से बाहर निकलें। इसे निम्न उदाहरण में दिखाए गए अनुसार pong में टाइम-आउट जोड़कर किया जा सकता है:

-module(tut19).

-export([start_ping/1, start_pong/0,  ping/2, pong/0]).

ping(0, Pong_Node) ->
    io:format("ping finished~n", []);

ping(N, Pong_Node) ->
    {pong, Pong_Node} ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping(N - 1, Pong_Node).

pong() ->
    receive
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    after 5000 ->
            io:format("Pong timed out~n", [])
    end.

start_pong() ->
    register(pong, spawn(tut19, pong, [])).

start_ping(Pong_Node) ->
    spawn(tut19, ping, [3, Pong_Node]).

इसके बाद संकलित किया जाता है और फ़ाइल tut19.beam को आवश्यक निर्देशिकाओं में कॉपी किया जाता है, निम्नलिखित पर देखा जाता है (pong @ kosken):

([email protected])1> tut19:start_pong().
true
Pong received ping
Pong received ping
Pong received ping
Pong timed out

और निम्नलिखित पर देखा गया है (पिंग @ गोलम):

([email protected])1> tut19:start_ping([email protected]).
<0.36.0>
Ping received pong
Ping received pong
Ping received pong
ping finished   

टाइम-आउट में सेट है:

pong() ->
    receive
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    after 5000 ->
            io:format("Pong timed out~n", [])
    end.

जब प्रवेश receive है तो टाइम-आउट ( after 5000 ) शुरू किया जाता है। यदि {ping,Ping_PID} प्राप्त होता है {ping,Ping_PID} तो टाइम-आउट रद्द कर दिया जाता है। यदि {ping,Ping_PID} प्राप्त नहीं होती है, तो टाइम-आउट के बाद की क्रियाएं 5000 मिलीसेकंड के बाद की जाती हैं। after receive में अंतिम होना चाहिए, अर्थात्, receive करने में अन्य सभी संदेश रिसेप्शन विनिर्देशों से पहले। किसी फ़ंक्शन को कॉल करना संभव है जो टाइम-आउट के लिए पूर्णांक लौटाता है:

after pong_timeout() ->

सामान्य तौर पर, वितरित एर्लांग प्रणाली के कुछ हिस्सों की निगरानी के लिए समय-आउट का उपयोग करने से बेहतर तरीके हैं। समय-बाह्य आमतौर पर बाहरी घटनाओं की निगरानी करने के लिए उपयुक्त होते हैं, उदाहरण के लिए, यदि आपने एक निर्दिष्ट समय के भीतर कुछ बाहरी सिस्टम से संदेश की उम्मीद की है। उदाहरण के लिए, एक टाइम-आउट का उपयोग किसी उपयोगकर्ता को मैसेंजर सिस्टम से लॉग आउट करने के लिए किया जा सकता है यदि उन्होंने इसे दस मिनट के लिए एक्सेस नहीं किया है।

4.2 त्रुटि से निपटने

एरलंग प्रणाली में पर्यवेक्षण और त्रुटि से निपटने के विवरण में जाने से पहले, आइए देखें कि एरलंग प्रक्रियाएं कैसे समाप्त होती हैं, या एरलंग शब्दावली में, बाहर निकलें

एक प्रक्रिया जो exit(normal) निष्पादित करती है या बस सामान्य निकास के लिए चीजों से बाहर निकलती है।

एक प्रक्रिया जो एक रनटाइम त्रुटि का सामना करती है (उदाहरण के लिए, शून्य से विभाजित, खराब मैच, एक फ़ंक्शन को कॉल करने की कोशिश करना जो मौजूद नहीं है और इसी तरह) एक त्रुटि के साथ बाहर निकलता है, अर्थात, एक असामान्य निकास है। एक प्रक्रिया जो exit(Reason) निष्पादित करती है जहां Reason परमाणु के अलावा कोई भी एरलंग शब्द है, इसमें भी असामान्य निकास होता है।

एक Erlang प्रक्रिया अन्य Erlang प्रक्रियाओं के लिंक सेट कर सकती है। यदि कोई प्रक्रिया link(Other_Pid) कॉल करती है, तो यह स्वयं और अन्य प्रक्रिया के बीच एक द्विदिश लिंक स्थापित करती है जिसे Other_Pid कहा जाता है। जब एक प्रक्रिया समाप्त हो जाती है, तो यह उन सभी प्रक्रियाओं को एक संकेत भेजता है, जिनके पास लिंक है।

सिग्नल को उस पिड के बारे में जानकारी दी जाती है जिसे उससे भेजा गया था और बाहर निकलने का कारण।

प्रक्रिया का डिफ़ॉल्ट व्यवहार जो सामान्य निकास प्राप्त करता है, वह संकेत को अनदेखा करना है।

ऊपर दो अन्य मामलों में डिफ़ॉल्ट व्यवहार (यानी, असामान्य निकास) है:

  • सभी संदेशों को प्राप्त करने की प्रक्रिया के लिए बायपास करें।
  • प्राप्त करने की प्रक्रिया को मार डालो।
  • मारे गए प्रक्रिया के लिंक के लिए एक ही त्रुटि संकेत का प्रचार करें।

इस तरह आप लेन-देन की सभी प्रक्रियाओं को एक साथ लिंक का उपयोग करके जोड़ सकते हैं। यदि कोई प्रक्रिया असामान्य रूप से बाहर निकलती है, तो लेनदेन की सभी प्रक्रियाएं मार दी जाती हैं। जैसा कि यह अक्सर एक प्रक्रिया बनाना चाहता है और एक ही समय में इसे लिंक करता है, एक विशेष spawn_link है, spawn_link जो spawn के समान ही करता है, लेकिन यह spawn_link प्रक्रिया के लिए एक लिंक भी बनाता है।

अब "पोंग" को समाप्त करने के लिए लिंक का उपयोग करके पिंग पोंग उदाहरण का एक उदाहरण:

-module(tut20).

-export([start/1,  ping/2, pong/0]).

ping(N, Pong_Pid) ->
    link(Pong_Pid),
    ping1(N, Pong_Pid).

ping1(0, _) ->
    exit(ping);

ping1(N, Pong_Pid) ->
    Pong_Pid ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping1(N - 1, Pong_Pid).

pong() ->
    receive
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong()
    end.

start(Ping_Node) ->
    PongPID = spawn(tut20, pong, []),
    spawn(Ping_Node, tut20, ping, [3, PongPID]).
([email protected])3> tut20:start([email protected]).
Pong received ping
<3820.41.0>
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong

यह पिंग पोंग कार्यक्रम का एक मामूली संशोधन है, जहां दोनों प्रक्रियाएं एक ही start/1 फ़ंक्शन से उत्पन्न होती हैं, और "पिंग" प्रक्रिया को एक अलग नोड पर देखा जा सकता है। link BIF के उपयोग पर ध्यान दें। "पिंग" exit(ping) कॉल करता है जब यह खत्म हो जाता है और इसके कारण एग्जिट सिग्नल "पोंग" को भेजा जाता है, जो समाप्त भी हो जाता है।

किसी प्रक्रिया के डिफ़ॉल्ट व्यवहार को संशोधित करना संभव है, ताकि असामान्य निकास संकेतों को प्राप्त करने पर यह मारा न जाए। इसके बजाय, सभी संकेतों को प्रारूप {'EXIT',FromPID,Reason} पर सामान्य संदेशों में बदल दिया जाता है और प्राप्त करने की प्रक्रिया के अंत में जोड़ दिया जाता है 'संदेश कतार। यह व्यवहार निम्न द्वारा निर्धारित होता है:

process_flag(trap_exit, true)

कई अन्य प्रक्रिया झंडे हैं, erlang(3) । इस तरह से एक प्रक्रिया के डिफ़ॉल्ट व्यवहार को बदलना आमतौर पर मानक उपयोगकर्ता कार्यक्रमों में नहीं किया जाता है, लेकिन ओटीपी में पर्यवेक्षी कार्यक्रमों पर छोड़ दिया जाता है। हालाँकि, पिंग पोंग प्रोग्राम को एग्ज़िट ट्रैपिंग को चित्रित करने के लिए संशोधित किया गया है।

-module(tut21).

-export([start/1,  ping/2, pong/0]).

ping(N, Pong_Pid) ->
    link(Pong_Pid), 
    ping1(N, Pong_Pid).

ping1(0, _) ->
    exit(ping);

ping1(N, Pong_Pid) ->
    Pong_Pid ! {ping, self()},
    receive
        pong ->
            io:format("Ping received pong~n", [])
    end,
    ping1(N - 1, Pong_Pid).

pong() ->
    process_flag(trap_exit, true), 
    pong1().

pong1() ->
    receive
        {ping, Ping_PID} ->
            io:format("Pong received ping~n", []),
            Ping_PID ! pong,
            pong1();
        {'EXIT', From, Reason} ->
            io:format("pong exiting, got ~p~n", [{'EXIT', From, Reason}])
    end.

start(Ping_Node) ->
    PongPID = spawn(tut21, pong, []),
    spawn(Ping_Node, tut21, ping, [3, PongPID]).
([email protected])1> tut21:start([email protected]).
<3820.39.0>
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
pong exiting, got {'EXIT',<3820.39.0>,ping}

4.3 रोबस्टनेस के साथ सबसे बड़ा उदाहरण जोड़ा गया

आइए हम मैसेंजर प्रोग्राम पर लौटते हैं और इसे और अधिक मजबूत बनाने के लिए बदलाव जोड़ते हैं:

%%% Message passing utility.  
%%% User interface:
%%% login(Name)
%%%     One user at a time can log in from each Erlang node in the
%%%     system messenger: and choose a suitable Name. If the Name
%%%     is already logged in at another node or if someone else is
%%%     already logged in at the same node, login will be rejected
%%%     with a suitable error message.
%%% logoff()
%%%     Logs off anybody at that node
%%% message(ToName, Message)
%%%     sends Message to ToName. Error messages if the user of this 
%%%     function is not logged on or if ToName is not logged on at
%%%     any node.
%%%
%%% One node in the network of Erlang nodes runs a server which maintains
%%% data about the logged on users. The server is registered as "messenger"
%%% Each node where there is a user logged on runs a client process registered
%%% as "mess_client" 
%%%
%%% Protocol between the client processes and the server
%%% ----------------------------------------------------
%%% 
%%% To server: {ClientPid, logon, UserName}
%%% Reply {messenger, stop, user_exists_at_other_node} stops the client
%%% Reply {messenger, logged_on} logon was successful
%%%
%%% When the client terminates for some reason
%%% To server: {'EXIT', ClientPid, Reason}
%%%
%%% To server: {ClientPid, message_to, ToName, Message} send a message
%%% Reply: {messenger, stop, you_are_not_logged_on} stops the client
%%% Reply: {messenger, receiver_not_found} no user with this name logged on
%%% Reply: {messenger, sent} Message has been sent (but no guarantee)
%%%
%%% To client: {message_from, Name, Message},
%%%
%%% Protocol between the "commands" and the client
%%% ---------------------------------------------- 
%%%
%%% Started: messenger:client(Server_Node, Name)
%%% To client: logoff
%%% To client: {message_to, ToName, Message}
%%%
%%% Configuration: change the server_node() function to return the
%%% name of the node where the messenger server runs

-module(messenger).
-export([start_server/0, server/0, 
         logon/1, logoff/0, message/2, client/2]).

%%% Change the function below to return the name of the node where the
%%% messenger server runs
server_node() ->
    [email protected]

%%% This is the server process for the "messenger"
%%% the user list has the format [{ClientPid1, Name1},{ClientPid22, Name2},...]
server() ->
    process_flag(trap_exit, true),
    server([]).

server(User_List) ->
    receive
        {From, logon, Name} ->
            New_User_List = server_logon(From, Name, User_List),
            server(New_User_List);
        {'EXIT', From, _} ->
            New_User_List = server_logoff(From, User_List),
            server(New_User_List);
        {From, message_to, To, Message} ->
            server_transfer(From, To, Message, User_List),
            io:format("list is now: ~p~n", [User_List]),
            server(User_List)
    end.

%%% Start the server
start_server() ->
    register(messenger, spawn(messenger, server, [])).

%%% Server adds a new user to the user list
server_logon(From, Name, User_List) ->
    %% check if logged on anywhere else
    case lists:keymember(Name, 2, User_List) of
        true ->
            From ! {messenger, stop, user_exists_at_other_node},  %reject logon
            User_List;
        false ->
            From ! {messenger, logged_on},
            link(From),
            [{From, Name} | User_List]        %add user to the list
    end.

%%% Server deletes a user from the user list
server_logoff(From, User_List) ->
    lists:keydelete(From, 1, User_List).


%%% Server transfers a message between user
server_transfer(From, To, Message, User_List) ->
    %% check that the user is logged on and who he is
    case lists:keysearch(From, 1, User_List) of
        false ->
            From ! {messenger, stop, you_are_not_logged_on};
        {value, {_, Name}} ->
            server_transfer(From, Name, To, Message, User_List)
    end.

%%% If the user exists, send the message
server_transfer(From, Name, To, Message, User_List) ->
    %% Find the receiver and send the message
    case lists:keysearch(To, 2, User_List) of
        false ->
            From ! {messenger, receiver_not_found};
        {value, {ToPid, To}} ->
            ToPid ! {message_from, Name, Message}, 
            From ! {messenger, sent} 
    end.

%%% User Commands
logon(Name) ->
    case whereis(mess_client) of 
        undefined ->
            register(mess_client, 
                     spawn(messenger, client, [server_node(), Name]));
        _ -> already_logged_on
    end.

logoff() ->
    mess_client ! logoff.

message(ToName, Message) ->
    case whereis(mess_client) of % Test if the client is running
        undefined ->
            not_logged_on;
        _ -> mess_client ! {message_to, ToName, Message},
             ok
end.

%%% The client process which runs on each user node
client(Server_Node, Name) ->
    {messenger, Server_Node} ! {self(), logon, Name},
    await_result(),
    client(Server_Node).

client(Server_Node) ->
    receive
        logoff ->
            exit(normal);
        {message_to, ToName, Message} ->
            {messenger, Server_Node} ! {self(), message_to, ToName, Message},
            await_result();
        {message_from, FromName, Message} ->
            io:format("Message from ~p: ~p~n", [FromName, Message])
    end,
    client(Server_Node).

%%% wait for a response from the server
await_result() ->
    receive
        {messenger, stop, Why} -> % Stop the client 
            io:format("~p~n", [Why]),
            exit(normal);
        {messenger, What} ->  % Normal response
            io:format("~p~n", [What])
    after 5000 ->
            io:format("No response from server~n", []),
            exit(timeout)
    end.

निम्नलिखित परिवर्तन जोड़े गए हैं:

मैसेंजर सर्वर ट्रैप से बाहर निकलता है। यदि यह एक निकास संकेत प्राप्त करता है, {'EXIT',From,Reason} , इसका मतलब है कि एक ग्राहक प्रक्रिया समाप्त हो गई है या निम्नलिखित कारणों में से एक के लिए उपलब्ध नहीं है:

  • उपयोगकर्ता ने लॉग ऑफ किया है ("लॉगऑफ़" संदेश हटा दिया गया है)।
  • क्लाइंट के लिए नेटवर्क कनेक्शन टूट गया है।
  • जिस पर क्लाइंट प्रोसेस रहता है वह नोड नीचे चला गया है।
  • ग्राहक प्रक्रियाओं ने कुछ अवैध संचालन किया है।

यदि बाहर निकलने का संकेत मिलता है, तो tuple {From,Name} को server_logoff फ़ंक्शन का उपयोग करते हुए सर्वर से हटा दिया जाता है। यदि नोड जिस पर सर्वर चलता है, क्लाइंट की सभी प्रक्रियाओं के लिए एक निकास संकेत (स्वचालित रूप से सिस्टम द्वारा उत्पन्न) भेजा जाता है: {'EXIT',MessengerPID,noconnection} जिससे सभी क्लाइंट प्रक्रियाएँ समाप्त हो जाती हैं।

इसके अलावा, पांच सेकंड के टाइम-आउट को await_result कार्य await_result में पेश किया गया है। यही है, अगर सर्वर पांच सेकंड (5000 एमएस) के भीतर जवाब नहीं देता है, तो क्लाइंट समाप्त हो जाता है। क्लाइंट और सर्वर के लिंक होने से पहले यह केवल लॉगऑन अनुक्रम में आवश्यक है।

एक दिलचस्प मामला यह है कि क्लाइंट सर्वर से लिंक करने से पहले ही समाप्त हो जाता है। इसका ध्यान रखा जाता है क्योंकि गैर-मौजूद प्रक्रिया से लिंक करने पर एक निकास संकेत होता है, {'EXIT',From,noproc} , स्वतः उत्पन्न होने के लिए। यह ऐसा है जैसे लिंक ऑपरेशन के तुरंत बाद प्रक्रिया समाप्त हो गई।