[erlang-questions] eep: multiple patterns

Ulf Wiger (TN/EAB) ulf.wiger@REDACTED
Fri May 30 10:25:04 CEST 2008


Richard A. O'Keefe skrev:
> 
> On 30 May 2008, at 9:21 am, Ulf Wiger (TN/EAB) wrote:
>> and then the "higher-order receive anybody" thread:
>>
>> http://www.erlang.org/pipermail/erlang-questions/2004-February/011514.html 
>>
> 
> Abstract patterns *almost* do that.
> You can specify "what to receive" as an abstract pattern,
> which can be passed around like a function,
> but all it can do is build a term containing selected
> data.  That's all you really need, so
>     Action(?#Filter)
> is in effect a higher order receive.

Here's a slightly sanitized example from an actual
product, using a main select statement with a filter
record, in line with the idea I suggested in my
EUC presentation. It is, in fact, the Extremely
Readable Rewrite of the real-life example in the
presentation. (:

I thought I'd try to convey a feeling for just
how hairy the receive clauses can get. I think
this is the worst one.

It would of course be a candidate for the sort of
refactoring you've shown on the other examples.
It is perhaps a matter of taste, whether or not
one prefers to extract as much code as possible out
of the receive statement (which in this case is 650
lines of code + comments). Most of the snipped
action bodies are only a handful of lines of code.
A few are considerably longer (up to 30 LOC).

Most of the action logic is performed in the
callback modules, which have to register
themselves with the main event loop.
These callbacks are relatively straightforward.

The callback modules can indicate which messages
they are expecting, allowing the main wait_loop
to buffer other messages. There can be many tens
of thousand ongoing sessions, but many possible
events affect all sessions or groups of sessions,
so it isn't obvious whether a central (buffering)
event loop is best, or decomposing it into one
or more processes per session.

Introducing a way to use selective receive,
and making heavy use of Erlang's pattern matching
as you can see, we have greatly reduced the
accidental complexity, but the application code
alone is still 100 KLOC - excluding platform and
middleware code.

(What does it do? It basically makes a Telephony
over IP network look like a legacy TDM switch,
in order to enable all legacy phone services
over an IP backbone. Obviously, all standard
requirements of 5-nines availability, scalability,
in-service upgrade, etc. apply.)

BR,
Ulf W

wait_loop(#mlgCmConnTable{hcKey       = HcKey,
                           hcId        = _HcId,
                           expectedMsg = Expected,
                           hcTrigger   = HcTrigger,
                           waitReq     = WaitReq} = HcRec,
           TraceLog) ->
     receive

         %%-------------------------------------------------------
         %% Data Checking/Collection procedures
         %%-------------------------------------------------------
         {distr_msg, DistrMsgId, {print_hc, Type}} ->
             ...;

         {collect_data, collect_data, CcpcArgs} ->
             ...,
             end_session(...);

         {collect_data, connection_trace, CcpcArgs} ->
             ...;

         {get_data,Sender} ->
             ...;

         %%-------------------------------------------------------
         %% Cleanup HC
         %%-------------------------------------------------------
         {distr_msg, DistrMsgId, ?HCMSG_REMOVE_HC} ->
             ...;

         {distr_msg, DistrMsgId, ?HCMSG_REMOVE_HC_DATA} ->
             ...;

         {distr_msg, DistrMsgId, ?HCMSG_REMOVE_ALL_HC} ->
             ...;

         {distr_msg, DistrMsgId, ?HCMSG_BEARER_RELEASED} ->
             ...;

         {distr_msg, DistrMsgId, ?HCMSG_RELEASE_BEARER} ->
             ...;

         %%-------------------------------------------------------
         %% Info HC
         %%-------------------------------------------------------
         {distr_msg, DistrMsgId, ?HCMSG_REPORT_LINE_EVENT} ->
             ...;

         %%-------------------------------------------------------
         %% Unexpected notify
         %%-------------------------------------------------------
         {distr_msg, DistrMsgId, {M, unexpected_gcp_notify=F, A}}
           when HcTrigger==?HC_TRIGGER_DEFAULT ->
             %% This is an on-hook sent ... in null context and with
             %% provisional event id. ...
             %% Make sure it does not interrupt any ongoing sequences.
             ...,
             end_session(Result);

         %%-------------------------------------------------------
         %% Update Context Server???
         %%-------------------------------------------------------
         {distr_msg, DistrMsgId, {M, F, A}}
         when atom(M), atom(F), list(A), F/=unexpected_gcp_notify ->
             ...,
             end_session(Result);

         ... (snipped housekeeping tasks)

         %%-------------------------------------------------------
         %% Cleanup HC
         %%-------------------------------------------------------
         {_, _, [_HcKey, M, F, ?HCMSG_REMOVE_HC = Msg]} -> ...;
         {_, _, [_HcKey, M, F, ?HCMSG_REMOVE_HC_DATA = Msg]} -> ...;
         {_, _, [_HcKey, M, release_on_notify = F,  Msg]} -> ...;

         %%-------------------------------------------------------
         %% Get HC Info
         %%-------------------------------------------------------
         {_, _, [_HcKey, M, get_hc_info = F, Msg]} -> ...;
         {_, _, [_HcKey, _M, get_tracelog = _F, _Msg]} -> ...;

         %%-------------------------------------------------------
         %% Only Gcp messages are accepted here.
         %%-------------------------------------------------------
         {_, _, [_HcKey, M, gcp_msg = F, Msg]}
         when WaitReq == ?HC_WAIT_FOR_GCP_REPLY ->
             HR1 = HcRec#mlgCmConnTable{
                        waitReq = ?HC_NO_PENDING_GCP_MSG},
             ...,
             end_session(apply(M, F, [HR1, Msg] ++ [TraceLog2]));

         %%-------------------------------------------------------
         %% Special case: transferDtmf + Notify. (signalCompletion)
         %%-------------------------------------------------------
         {_, _, [_HcKey, M, ?MSG_NOTIFY_SC = F, Msg]}
         when WaitReq == ?MSG_WAIT_REQ_SC ->
             HR1 = HcRec#mlgCmConnTable{
                      waitReq = ?HC_NO_PENDING_GCP_MSG},
             ...,
             end_session(apply(M, F2, [HR1, Msg] ++ [TraceLog2]));

         %%-------------------------------------------------------
         %% Special case for HCMSG_RELEASE to avoid deadlock
         %%-------------------------------------------------------
         {_, _, [_HcKey, _M, hc_msg = _F,
                 #mlgCmHcMsgRelease{
                    domain=Domain, function=Function} = Msg]}
         when ( is_tuple(hd(Expected)) andalso
                (element(1,hd(Expected)) == mlgCmHcMsgReleaseRes) 
andalso
                (WaitReq == ?HC_NO_PENDING_GCP_MSG) ) ->
             ...,
             end_session(...);

         %%-------------------------------------------------------
         %% Ignore HCMSG_RELEASE_COMPLETE in wrong states
         %%-------------------------------------------------------
     {_, _, [_HcKey, _M, hc_msg, #mlgCmHcMsgReleaseComplete{}]}
         when not 
(((HcTrigger#mlgCmHcTrigger.m==?HCTM_INTRA_DOMAIN_TERM) and
                    (HcTrigger#mlgCmHcTrigger.f==?HCTF_RP_AGW_TDM) and
                    (HcTrigger#mlgCmHcTrigger.a==?HCTA_RELEASE)) or
 
((HcTrigger#mlgCmHcTrigger.m==?HCTM_INTRA_DOMAIN_TERM) and
                    (HcTrigger#mlgCmHcTrigger.f==?HCTF_RP_AGW_TDM) and
                    (HcTrigger#mlgCmHcTrigger.a==?HCTA_SUBTRACT))) ->
             end_session(...);

         %%-------------------------------------------------------
         %% Clash handling:                              (...)
         %%       GW notifications ignored during renegotiation
         %%-------------------------------------------------------
         {_, _, [_HcKey, M,
                 gcp_notify = F,
                 [_HcInfo, _DcId,
                  {actionRequest,
                   #mlgArGcpData{
                     notification =
                       #mlgArNotify{
                          events = [{"ctyp/dtone","dtt",_}|_]}
                   }}] = Msg]}
      when ... -> ...;

         %%-------------------------------------------------------
         %% Clash handling:    (...)
         %%       CodecReneg hc_msg received while this half call
         %%       is already in renegotiation,
         %%-------------------------------------------------------
         {_, _, [_HcKey, _M, hc_msg = _F,
                 #mlgCmHcMsgCodecReneg{function = IncomingRenegF}] = Msg}
         when (HcRec#mlgCmConnTable.renegData)#mlgRenegData.renegState /=
           ?RENEG_NULL ->

             ...,
             end_session({?ES_UNCHANGED, ?EC_IGNORED, HR1, TL});

         %%-------------------------------------------------------
         %% Clash handling:    (...)
         %%       BRM BEAR_INFO msg is received when HC is
         %%       waiting for GCP modReply or hc_msg CodecRenegRes
         %%-------------------------------------------------------
         {_, _, [_HcKey, _M,
                 msg_received = _F,
            [[?BRM_BEARER_INFO = _MsgId, _Tid,
             [_Sid, _SDID, _BI]] | _] = Msg]}
         when
           HcTrigger#mlgCmHcTrigger.m == ?HCTM_CODEC_RENEG_ORIG_IM,
           HcTrigger#mlgCmHcTrigger.f == ?HCTF_RENEG_AUDIO_VOIP_VOIP,
           HcTrigger#mlgCmHcTrigger.a == ?HCTA_MODIFY;

           ... (snipped 11 more similar guard groups)
         ->
           ...;


         ... (a few more similar clash handling cases);

         %%-------------------------------------------------------
         %% Only hc messages can have the expected message set.
         %% ...
         %%-------------------------------------------------------
         {_, _, [_HcKey, M, hc_msg = F, Msg]}
         when ((element(?HC_MSG_REF_POS, Msg) == hd(Expected) orelse
                hd(Expected) == hc_msg) andalso
               (WaitReq == ?HC_NO_PENDING_GCP_MSG)) ->
             ...,
             end_session(apply(M, F, [HR1, Msg] ++ [TraceLog2]));

         %%-------------------------------------------------------
         %% RELEASE_BEARER, REMOVE_HC_DATA and REMOVE_HC peer msgs
         %% have to be processed immediately.
         %%-------------------------------------------------------

         {_, _, [_HcKey, M, hc_msg = F,
                 #mlgCmHcMsgPeerMsg{function = Func} = Msg]}
         when Func == ?HCTF_RELEASE_BEARER;
              Func == ?HCTF_REMOVE_HC;
              Func == ?HCTF_REMOVE_HC_DATA ->
             case {Func, HcTrigger} of
                 ...
             end;


         %%-------------------------------------------------------
         %% Immediate processing of all PDM half calls
         %%-------------------------------------------------------

         {_, _, [_HcKey, M, hc_msg = F, Msg]}
         when is_record(Msg,mlgCmHcMsgStatReq);
              is_record(Msg,mlgCmHcMsgStatReqRes);
              is_record(Msg,mlgCmHcMsgFetchStat);
              is_record(Msg,mlgCmHcMsgFetchStatRes);
              is_record(Msg,mlgCmHcMsgStatistics) ->
             ...,
             end_session(apply(M, F, [HcRec, Msg] ++ [TraceLog2]));


         ... (several internal messages snipped)

     after get_wait_loop_timeout(HcRec) ->
             handle_wait_loop_timeout(
               get_wait_loop_timeout(HcRec), HcRec, TraceLog)
     end.



More information about the erlang-questions mailing list