Quantcast

Message Receive Semantics (was eep: Multiple Patterns)

classic Classic list List threaded Threaded
12 messages Options
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Message Receive Semantics (was eep: Multiple Patterns)

Jay Nelson-2
Sean Hinde wrote:

 > There is no contradiction here. In the first test the message queue
 > never grows so you do not get a slowdown.

As Per's later posted code showed, the mailbox was loaded with
messages before the consumers were started in both cases.  The
problem has nothing to do with garbage collection, but with the
linear scanning that takes place on the message queue.

 > Badly designed use of selective receive is one way to fill a message
 > queue, but by no means the only way. As I understand it once a  
message
 > has been examined and not matched it is not visited again until the
 > pattern changes.

The message queue is inspected one message at a time.  All patterns
are matched against that message.  If none match, the second message
in the queue is matched.  Progress continues until a match is found or
the end of the queue is hit.  If the end of the queue is hit and the  
receive
doesn't timeout, then none of the previous messages are revisited.  Once
a match happens, however, the scanning process goes back to the first
message in the queue because the act of finding a match may have now
made an earlier message match due to a change in pattern states.

Check out the following tutorial on my website.  It discusses the issues
related to the message queue.  Ulf doesn't like my conclusions because
they don't encourage power users, but the vast majority of message
handling situations are not as sophisticated as what his group deals  
with.

http://www.duomark.com/erlang/briefings/euc2006/index.html


I seem to recall that Francesco's group had a presentation last year on
priority messaging, but don't go there -- priorities with erlang message
semantics is a very messy problem to deal with.  You are much better
off finding an alternative to priorities, unless you unload the whole
message queue and manage it in your own process as a real priority
queue rather than a linear list.

jay

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: Multiple Patterns)

Ulf Wiger-2
2008/5/31 Jay Nelson <[hidden email]>:

> Check out the following tutorial on my website.  It discusses the issues
> related to the message queue.  Ulf doesn't like my conclusions because
> they don't encourage power users, but the vast majority of message
> handling situations are not as sophisticated as what his group deals
> with.
>
> http://www.duomark.com/erlang/briefings/euc2006/index.html

I like your conclusions, Jay. (:

I've been on a crusade about selective receive for a few years,
since it dawned on me that the vast majority of people building
complex soft-real-time systems had no basic understanding
of the problem, and kept hitting the complexity wall, and /still/
didn't know what had hit them.

It took me a while to figure it out myself, and when I did I
looked around for teaching material or books that explain
it, but found basically nothing. *

I agree that the vast majority of processes do not have this
problem, but I think that it should be part of everyone's
repertoire to realize that it /can/ happen, and roughly under
which circumstances.

BR,
Ulf W

* In 2002, I had the opportunity to run my thoughts by Simon
Peyton-Jones. He was severely jetlagged and looked like
he was going to doze off while I explained it to him. Then
he quickly confirmed that it was "obvious" (that it would get
you into trouble) and "not a well known problem", before
excusing himself in order to hit the sack.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: Multiple Patterns)

Sean Hinde
In reply to this post by Jay Nelson-2

On 31 May 2008, at 06:10, Jay Nelson wrote:

> Sean Hinde wrote:
>
>> There is no contradiction here. In the first test the message queue
>> never grows so you do not get a slowdown.
>
> As Per's later posted code showed, the mailbox was loaded with
> messages before the consumers were started in both cases.  The
> problem has nothing to do with garbage collection, but with the
> linear scanning that takes place on the message queue.
>
>> Badly designed use of selective receive is one way to fill a message
>> queue, but by no means the only way. As I understand it once a
> message
>> has been examined and not matched it is not visited again until the
>> pattern changes.
>
> The message queue is inspected one message at a time.  All patterns
> are matched against that message.  If none match, the second message
> in the queue is matched.  Progress continues until a match is found or
> the end of the queue is hit.  If the end of the queue is hit and the
> receive
> doesn't timeout, then none of the previous messages are revisited.  
> Once
> a match happens, however, the scanning process goes back to the first
> message in the queue because the act of finding a match may have now
> made an earlier message match due to a change in pattern states.

Indeed, I posted my own followup agreeing that my understanding was  
wrong.

I still think that it would be an interesting optimisation - to store  
the last used pattern, and a pointer to where in the queue has been  
matched against that pattern - unless the pattern changes the system  
would not need to traverse the whole queue to re-test the same  
messages next time round the receive loop.

BR,
Sean

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: Multiple Patterns)

Steve Davis
In reply to this post by Ulf Wiger-2
At the risk of sounding like a "dumb noob" (which is, actually, pretty
much what I am)...

...can anyone clarify for me why you would wish to retain messages in
the process mailbox that the process didn't wish to receive or act
upon?

It struck me from the get-go when learning about Erlang that I could
not think of a use case for this mode of operation, and it always
seemed to me to be a touch dangerous (and a good way to break an
unsuspecting application).

Yes - I'm asking why the erlang messaging works the way it does.

/Steve
_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: Multiple Patterns)

Ulf Wiger-2
Steve,

The simplest (and most ubiquitous) case is
gen_server:call().

The function gen:await_resp(...) uses pattern matching
to check different messages that relate to the request
sent. It touches no other messages.

It's a form of scoped receive, and there are of course
other ways to achieve the same thing.

BR,
Ulf W

2008/6/1 Steve <[hidden email]>:

> At the risk of sounding like a "dumb noob" (which is, actually, pretty
> much what I am)...
>
> ...can anyone clarify for me why you would wish to retain messages in
> the process mailbox that the process didn't wish to receive or act
> upon?
>
> It struck me from the get-go when learning about Erlang that I could
> not think of a use case for this mode of operation, and it always
> seemed to me to be a touch dangerous (and a good way to break an
> unsuspecting application).
>
> Yes - I'm asking why the erlang messaging works the way it does.
>
> /Steve
> _______________________________________________
> erlang-questions mailing list
> [hidden email]
> http://www.erlang.org/mailman/listinfo/erlang-questions
>
_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: Multiple Patterns)

Christian S-2
In reply to this post by Steve Davis
> ...can anyone clarify for me why you would wish to retain messages in
> the process mailbox that the process didn't wish to receive or act
> upon?

Having one receive-clause retain messages doesnt mean that you have to
make all do it.

So it is a matter of the process only wanting to handle a certain kind
of message _at this time_.

A perfect example of a selective receive is in a gen_server:call, you
want the reply to your request back, not the next message in the
mailbox.
_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: MultiplePatterns)

Valentin Micic
In reply to this post by Steve Davis
> From: "Steve" <[hidden email]>

> ...can anyone clarify for me why you would wish to retain messages in
> the process mailbox that the process didn't wish to receive or act
> upon?
>

Consider a following scenario: for every five "high" priority messages
processed, I wanted to handle three "normal" and one "low" priority
message(*). To implement this with a selective receive is relatively easy
and concise, even for a novice programmer. To do without it, may be
considerably more difficult and messy.
Aside selective receive caveats due to the current realization (I really
like Sean's idea for optimization), Erlang is much better with, than without
it. IMO, the selective receive reflect the spirit of ERLANG. After all, you
don't have to use it.


> Yes - I'm asking why the erlang messaging works the way it does.

 So, I suppose the answer might be: because everything else works pretty
much the same way.

V.

(*) Assuming that priority is given as a part of the message.

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: MultiplePatterns)

Steve Davis
Many thanks all - I consider myself fully "clarified" on this issue.

Also - forewarned is forearmed - I actually do have a number of high
volume "priority queues" in my current application effort - hence my
initial interest in this thread. I admit that I was being a touch
disingenuous in phrasing my question but it seemed the briefest way to
get to the point - i.e. ask the dumbest relevant question possible. I
was avoiding using pattern matching on receive exactly because of this
nagging issue about "dead messages" - but maybe I'll review this
approach given what's been said.

I do have a concern that e.g. if you scan through and so end up with a
box full of low priority messages and then a new high priority message
arrives... that the scan will have to go through all the old messages
to get a match to the new high priority one... but I think I'll leave
my concerns about that to when the app is working correctly and I can
do some genuine performance measurements - unless anyone has some rule-
of-thumb guidelines/gotchas I should be aware of before I commit to
this path?

Regs,
Steve
_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep: MultiplePatterns)

Jay Nelson-2
In reply to this post by Valentin Micic
Valentin wrote:

 > Consider a following scenario: for every five "high" priority  
messages
 > processed, I wanted to handle three "normal" and one "low" priority
 > message(*). To implement this with a selective receive is  
relatively easy
 > and concise, even for a novice programmer. To do without it, may be
 > considerably more difficult and messy.


I think priority is one of the harder cases to get right, actually.  
The beginner
will end up blocked waiting for the 5th high priority message when only
4 are on the queue, or will end up delaying high priority messages while
they scan for low priority ones.

So here is the concrete challenge:

1) Send a message to a linked process:
     a) {request, Msg}
2) The other process should generate 20 or so messages randomly
     and then quit
3) Always handle messages in the following order:
     a) 5 messages of {reply, high, Msg}
     b) 3 messages of {reply, normal, Msg}
     c) 1 message of {reply, low, Msg}
     d) 'EXIT' message

Of course, there may not be 5 remaining high messages or 3 remaining
normal messages so you need to deal with these end cases.  In doing so,
make sure you don't just end up polling every 100 milliseconds.  I've
simplified the problem by only having one process that can generate
replies, so that the selective receive doesn't become even more
complicated.

I challenge any beginner or intermediate erlang programmer to write
and post the code for both processes and  a description of your results.
All the advanced erlang people can help point out the issues that will
have to be addressed when you change the initial code approach.

At least, that is my bet.  Valentin may be right.  Let's see some code!
In another post, I'll give an alternative problem that is easier and
shows the usefulness of selective receive.

In general, I would argue that erlang's approach to message is geared
towards handling multiple conversations or sessions on a single channel,
and that handling priorities is a pathological case for the simple  
message
queue approach that erlang uses.  It is far easier to implement  
priorities
by unloading the whole queue into a process which implements a priority
queue and then doling out messages according to your own priority logic.

jay

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep:MultiplePatterns)

dmercer

Jay Nelson wrote:

> So here is the concrete challenge:

>

> 1) Send a message to a linked process:

>      a) {request, Msg}

> 2) The other process should generate 20 or so messages randomly

>      and then quit

> 3) Always handle messages in the following order:

>      a) 5 messages of {reply, high, Msg}

>      b) 3 messages of {reply, normal, Msg}

>      c) 1 message of {reply, low, Msg}

>      d) 'EXIT' message

>

> Of course, there may not be 5 remaining high messages or 3 remaining

> normal messages so you need to deal with these end cases.  In doing so,

> make sure you don't just end up polling every 100 milliseconds.  I've

> simplified the problem by only having one process that can generate

> replies, so that the selective receive doesn't become even more

> complicated.

>

> I challenge any beginner or intermediate erlang programmer to write

> and post the code for both processes and  a description of your results.

> All the advanced erlang people can help point out the issues that will

> have to be addressed when you change the initial code approach.

>

> At least, that is my bet.  Valentin may be right.  Let's see some code!

 

OK, let me begin with the output from my program so you can see if I got the answer functionally correct:

 

2> nelson:start("Message text").

Sending message 20 with high priority.

Sending message 19 with high priority.

Sending message 18 with normal priority.

Sending message 17 with normal priority.

Sending message 16 with high priority.

Sending message 15 with high priority.

Sending message 14 with high priority.

Sending message 13 with high priority.

Sending message 12 with low priority.

Sending message 11 with normal priority.

Sending message 10 with normal priority.

Sending message 9 with low priority.

Sending message 8 with high priority.

Sending message 7 with low priority.

Sending message 6 with normal priority.

Sending message 5 with low priority.

Sending message 4 with low priority.

Sending message 3 with normal priority.

Sending message 2 with normal priority.

Sending message 1 with high priority.

Exit (normal) signal received from sender; begin prioritized receive sequence.

Received {"Message text",high,20}.

Received {"Message text",high,19}.

Received {"Message text",high,16}.

Received {"Message text",high,15}.

Received {"Message text",high,14}.

Received {"Message text",normal,18}.

Received {"Message text",normal,17}.

Received {"Message text",normal,11}.

Received {"Message text",low,12}.

Received {"Message text",high,13}.

Received {"Message text",high,8}.

Received {"Message text",high,1}.

Received {"Message text",normal,10}.

Received {"Message text",normal,6}.

Received {"Message text",normal,3}.

Received {"Message text",low,9}.

Received {"Message text",normal,2}.

Received {"Message text",low,7}.

Received {"Message text",low,5}.

Received {"Message text",low,4}.

complete

 

It appears to me to do as requested.  It receives 5 high priority messages before receiving 3 normal priority messages and then a single low priority message.  When there are no more of any particular type, it skips it and moves to the next.  Here’s my code:

 

%%% @author David Mercer <[hidden email]>

%%% @doc Programming challenge by Jay Nelson, 6/1/08.

%%%

%%% Jay Nelson wrote:

%%% <pre>

%%% > So here is the concrete challenge:

%%% >

%%% > 1) Send a message to a linked process:

%%% >      a) {request, Msg}

%%% > 2) The other process should generate 20 or so messages randomly

%%% >      and then quit

%%% > 3) Always handle messages in the following order:

%%% >      a) 5 messages of {reply, high, Msg}

%%% >      b) 3 messages of {reply, normal, Msg}

%%% >      c) 1 message of {reply, low, Msg}

%%% >      d) 'EXIT' message

%%% >

%%% > Of course, there may not be 5 remaining high messages or 3 remaining

%%% > normal messages so you need to deal with these end cases.  In doing so,

%%% > make sure you don't just end up polling every 100 milliseconds.  I've

%%% > simplified the problem by only having one process that can generate

%%% > replies, so that the selective receive doesn't become even more

%%% > complicated.

%%% >

%%% > I challenge any beginner or intermediate erlang programmer to write

%%% > and post the code for both processes and  a description of your results.

%%% > All the advanced erlang people can help point out the issues that will

%%% > have to be addressed when you change the initial code approach.

%%% </pre>

 

-module(nelson).

 

%% API

-export([start/1, start/3]).

 

 

%% @spec start(Msg::any()) -> complete

%%

%% @doc Execute the Nelson challenge.

%%

%% Spawns a linked process, waits for the process to exit, then receives the

%% messages in accordance with the priority rules laid out by Nelson.

%%

%% The Msg argument is simply passed through to the spawned process, which

%% will return it in the messages it sends back.

%%

%% @equiv start(Msg, 20, {5, 3, 1})

 

start(Msg) -> start(Msg, 20, {5, 3, 1}).

 

 

%% @spec start(Msg::any(), N::integer(), PriorityRules::PriorityRules) -> complete

%%     PriorityRules = {High::integer(), Med::integer(), Low::integer()}

%%

%% @doc Execute the Nelson challenge.

%%

%% Spawns a linked process, waits for the process to exit, then receives the

%% messages in accordance with the priority rules laid out by Nelson.

%%

%% The Msg argument is simply passed through to the spawned process, which

%% will return it in the messages it sends back.

%%

%% N is the number of messages for the spawned process to send.

%%

%% The PriorityRules specify the numbers of messages of each priority which

%% are received in each cycle.  Nelson specified that these numbers be 5, 3,

%% and 1 respectively.

 

start(Msg, N, {High, Med, Low}) ->

 

       % Create linked process for sending prioritized messages

      

       Self = self(),

       process_flag(trap_exit, true),

       Pid = spawn_link(fun() -> start_sending(Self, Msg, N) end),

      

       % Send the initial message

      

       Pid ! {request, Msg},

      

       % Wait for the linked process to exit

      

       receive

              {'EXIT', Pid, Reason} ->

                     % Generally we expect Reason = normal, but we don't care if it's something else

                    

                     io:format("Exit (~p) signal received from sender; begin prioritized receive sequence.\n", [Reason]),

 

                     % Once we receive the exit message, begin receiving the messages

 

                     loop({0, High}, {0, Med}, {0, Low})

       end.

 

 

start_sending(Pid, Msg, N) ->

      

       % Seed the random number generator

      

       {A1, A2, A3} = now(),

       random:seed(A1, A2, A3),

      

       % Send

      

       send(Pid, Msg, N).

 

 

send(_Pid, _Msg, 0) -> complete;

send(Pid, Msg, N) when N > 0 ->

       Priority = case random:uniform(3) of

                     1 -> high;

                     2 -> normal;

                     3 -> low

              end,

       io:format("Sending message ~p with ~p priority.\n", [N, Priority]),

       Pid ! {reply, Priority, {Msg, Priority, N}},

       send(Pid, Msg, N - 1).

 

 

%% loop/3 is the prioritized receive loop.  It works by keeping track of

%% counters for the three priorities, counting up each one in priority

%% order until it reaches the max.  If no message of that priority is

%% received, then the max is set to 0.  When all counters are zero, all messages have been received.

 

loop({0, 0}, {0, 0}, {0, 0}) -> complete;

loop({H, H}, {M, M}, {L, L}) -> loop({0, H}, {0, M}, {0, L});

 

loop({H1, H2}, {0, M}, {0, L}) when H2 > H1 ->

       receive

              {reply, high, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H1 + 1, H2}, {0, M}, {0, L})

              after 0 ->

                     loop({0, 0}, {0, M}, {0, L})

       end;

 

loop({H, H}, {M1, M2}, {0, L}) when M2 > M1 ->

       receive

              {reply, normal, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H, H}, {M1 + 1, M2}, {0, L})

              after 0 ->

                     loop({H, H}, {0, 0}, {0, L})

       end;

 

loop({H, H}, {M, M}, {L1, L2}) when L2 > L1 ->

       receive

              {reply, low, Msg} ->

                     io:format("Received ~p.\n", [Msg]),

                     loop({H, H}, {M, M}, {L1 + 1, L2})

              after 0 ->

                     loop({H, H}, {M, M}, {0, 0})

       end.

 

Please advise.  Thank-you.


_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep:MultiplePatterns)

Jay Nelson-2
Valentin wrote:

> Back in 2000, when I just started with Erlang
>

Ok, you've got 2 years on me so your insights may be more grounded
than mine.


> (had a extensive commercial experience in procedural languages,  
> though), one of the first problems I tried to solve had to do with  
> priority handling. So I wrote something very ugly, but someting  
> that reflected a paridgm I had at the time -- I remember writing  
> something like this:
>
> loop_high( 0 ) -> loop_normal( 3 );
> loop_high( N ) ->
>  receive
>   {high, Msg} ->
>         process_msg( Msg ),
>         loop_high( N-1 )
>  after 0 -> receive
>        {normal, Msg}->
>             process_msg( Msg ),
>             loop_high( N )
>

This is the first revelation that a beginner needs to understand.
It sometimes takes a while to realize that the after clause of a
receive can be another receive (and that the semantics of that
are different than two successive receives).



> loop_low( 0 ) -> loop_high( 5 );
> loop_low( N ) -> ...
> ...
> ...
>

The next revelation is that this is polling.  You will consume
CPU time when there are no messages available.  This may
or may not matter to your application.  As you say below, it
feels grungy and it doesn't look like a very erlangish approach.


>
> The above code would do the trick, however, being ugly as it may --  
> would eventually trigger refactoring to the tune of:
>
> loop_high( 0 ) -> loop_normal( 3 );
> loop_hihg(N) ->
>   receive
>      {high, Msg} ->
>          process_msg( Msg ),
>          loop_high( N-1 );
>       {normal, Msg} ->
>           process_msg( Msg ),
>           loop_high( N );
>        {low, Msg} ->
>           process_msg( Msg ),
>           loop_high(N);
>         ANY  ->
>              case process_msg( ANY ) of
>                  stop -> exit( normal );
>                  _      -> loop_high( N )
>              end
>    after 1000 -> loop_high( N )
>   end.
>

Are you sure the above works?  Suppose you have
a Low message followed by a High message.
Won't the Low message get handled first?

The same applies to the other clauses.  I also
don't see the purpose of the 1000 timeout above,
but again you end up with a polling.


>
> What I'm trying to say, I guess -- it does not really matter if one  
> is novice or not. We all know how to think, and only through  
> thinking we can arrive to a point where we could articulate the  
> "right" kind of questions. Nothing wrong in making a few mistakes  
> in a process.
>

I may have mis-stated my objection by phrasing it as a challenge to  
beginners.
I conflated two issues: 1) advanced message handling requires one to  
become
fluent and familiar with simpler message handling first, and 2)  
priority messaging
is a pathological case for erlang's sequential mailbox approach.

#1 is true of any new feature of any new language, however, I would  
bet the
majority of new programmers to erlang have never used another language
that has as simple a mechanism for message handling, and therefore are
likely to be unfamiliar with the various options that receive  
enables.  The same
would not be true of lists or hash tables.

#2 is a result of the sequential, and wisely chosen simple nature, of  
the
receive mechanism.  I don't believe it is possible for a single  
process to
implement priority without polling, unless the message queue is unloaded
and maintained using standard data structures.  That's what I mean by
pathological -- normal use of the receive statement leads to bad results
and unorthodox means to handle things efficiently.  Although the choice
that the language designers chose makes a lot of other cases very easy
to implement and very easy to change your architecture.  Overall it is
a win, but always be aware of the worst case scenario so you can avoid
it.

(My general recommendation on priority messaging is to avoid it by
changing the architecture.  Often times it is just a side effect of the
previous techniques for handling messages.  The lightweightedness
of erlang processes allows alternative approaches to standard
priorities.)

Suppose the requirements change to add one more level of priority.
It ripples through all the receive statements.  Not a desirable outcome.


>
> BTW, how easy one may make a mistake depends largely on how the  
> problem was stated. If one says:
>
>
>
> 3) Always handle messages in the following order:
>     a) 5 messages of {reply, high, Msg}
>     b) 3 messages of {reply, normal, Msg}
>     c) 1 message of {reply, low, Msg}
>     d) 'EXIT' message
>
>
>
> one should not get surprised if the resulting code blocks until 5  
> high priority messages have been processed. However, if one  
> indicates that preference should be given to high priority  
> messages, than normal, followed by low priority message, utilising  
> a ratio of 5:3:1, and stipulate that should there no higher  
> priority message, lower one should be processed --  one may close a  
> semantic gap, and thus prevent a novice from making a mistake. All  
> of that in far better English, of course ;-).
>
>

Well, I did botch the explanation, but it seemed a rather arbitrary
requirement to start with.  Did you really mean 5:3:1 or was that an  
easy
way to approximate priority because you can't easily do it with
a normal receive?  Wouldn't you really want to process as many High
priority messages as quickly as possible, and then normal and low as
you get a chance, maybe in parallel?   Why not let the erlang process
time-slicing manage it for you, using the erlang process priority to
give preference to high, normal and low messages in that order?

Since there is one message queue and one process, you can't use
any of the built in process time-sharing or parallelism.  Even if you
unload the mailbox and put items in a priority queue, you will still
have one process to handle them.  The only way that relies on the
VM semantics of fair scheduling is to use a router process to receive
items and to forward them to one of 3 different processes for handling.
Using that approach also allows the flexibility to add new priorities
easily (one new pattern and one new process), although it is still a
little tricky to manage the balance of high vs normal vs low if you  
don't
just want the VM to treat them equally -- there are two choices: 1) use
process priorities and 2) send a series of messages followed by a
request to ack them so that the router controls the flow by waiting
for acks before adding messages to the process queues.

Doing it with a router process is the most flexible (it allows both
selective receive and unloading the mailbox to a priority queue,
plus new priority levels have less impact).  It is also arguably much
easier to understand the resulting code.  It is unfortunately a forced
solution to simplify the complexity caused by serial queues.

jay

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Reply | Threaded
Open this post in threaded view
|  
Report Content as Inappropriate
star

Re: Message Receive Semantics (was eep:MultiplePatterns)

Jay Nelson-2
In reply to this post by dmercer
David Mercer posted a submission that performed as the
requirements stated. In between my post and David's, Valentin
pointed out that requirements documents may not be my calling.

The solution David posted is a cleanly written separation of the
various priorities, and it avoids polling by quitting after the message
queue is cleared.  Of course, he perfectly satisfied my requirements
as restated from Valentin's original.

In a real operational situation a few things would be different:

1) New messages can arrive at any time
2) Newly arriving higher priority messages should interrupt a
      lesser priority loop
3) The 5:3:1 is an artificial substitute for true process priorities


To allow new messages, I believe your clean separation has to
be violated. This typically occurs by nesting receives as Valentin
did (the after clause of high priority contains a receive that includes
both high and normal).

The 5:3:1 is  just a way to say that given a single message queue
and a single process, allow some of each message type to get
processed to avoid constant priority pre-emption without exhibiting
priority inversion (too many low messages when there are higher
priority messages).

To leverage erlang strengths, use a router on your single queue
and a separate process for each of the priorities.  This accomplishes
the following:

1) One scan of the message queue is possible (although
      selective receive may still be useful depending on the
      application).

   * [Using your approach, there will be one scan per priority level --
       suppose we have 8 priorities and the queue has 1000 low priority
       messages.  There will be 7 scans of all the messages to do
       nothing followed by a scan that processes them all, unless we
       have to check for a newly arriving higher priority message in
       which case there will be 999 more unsuccessful scans (x 7
       higher priorities) of a queue that decreases in length by
       1 each time.]

2) You now have uniform priority queues per process.  You can
      process in order, or use selective receive to thread a single
      conversation, but you don't have to worry about priority issues.

3) You now have separate processes which can be bumped or
      reduced in priority, or fed messages quickly or slowly as the
      router wishes to balance the relative amount of processing.

4) Adding a few new priority levels doesn't ripple through receive
      statements, nor does it multiply the number of message scans.
      The new priorities require a few new spawns and pattern to
      route messages.

The real message is: be careful if you find yourself wishing for
priority message handling.  Look for alternative ways to route
messages to accomplish the intended effect.  Priority is a hack
for single channels to work as if they had more smarts, more
horsepower and to simulate multiple channel communications.

In a threaded world, I constantly come across architectures
with callbacks on events.  Eventually, the implementer wants
priority because the message queues are too long and they
can't get to the messages that need handling immediately.
It is a symptom of an architectural issue, and a patch to make
it work, rather than reconsidering the whole communications
structure.

With erlang, it is easy to reorganize the architecture or
communications structure without impacting the application
logic.  It is much harder in other languages, so the lazy way
is a quick patch rather than an architectural refactoring.


There is a completely separate discussion about how to
recognize priority properly (we had this a few months back).
In our examples here, the message was magically marked
with the correct priority.  To do that requires all senders to
have global knowledge and logic to set the proper level.
A better approach is to mark the messages with the data
needed to make a decision, but allow the router process
to use its own logic to determine the priorities based on
the content provided to it.  It enables a simple logic change
in the router to affect the throughput of the entire system.
If the logic is spread throughout the system, or is globally
determined, the impact of priority change is large.

jay

_______________________________________________
erlang-questions mailing list
[hidden email]
http://www.erlang.org/mailman/listinfo/erlang-questions
Loading...