Jay,<br><br>Thanks for a very detailed and informative response. Although it obviously depends on circumstances, I feel that, given Erlang's extremely fast process creation time and small process size, I would first consider your last option, namely, to create an individual process per request, and use an ETS table to coordinate responses. If there are very many responses to be collected for each request, I would intuitively imagine in my "Erlang newbie fog" that using an ETS table with its constant-time performance and no-garbage-collection characteristics would be better on average than using selective receive, which I understand has to do a linear scan and move unprocessed messages to another area. Of course, intuition often does not stand up to the reality of performance measurements, so it would be interesting to see a benchmark of the various architectural options you have described, perhaps as a function of response time vs. request rate.<br>
<br>Regards,<br>Edwin Fine<br><br><div class="gmail_quote">On Sun, Jun 1, 2008 at 8:09 PM, Jay Nelson <<a href="mailto:jay@duomark.com">jay@duomark.com</a>> wrote:<br><blockquote class="gmail_quote" style="border-left: 1px solid rgb(204, 204, 204); margin: 0pt 0pt 0pt 0.8ex; padding-left: 1ex;">
<div class="Ih2E3d">I wrote:<br>
<br>
 > >Whenever you have disjoint receive statements, you need to<br>
 > >take care that there is a technique for emptying unexpected<br>
 > >messages.<br>
<br>
</div>Edward Fine accidentally replied only to me directly:<br>
<div class="Ih2E3d"><br>
 > Is this a good place to use the catch-all, or is there a better<br>
 > technique? I ask this as a newcomer to Erlang.<br>
<br>
</div>(This posting also gives an alternative example to Valentin's<br>
priority problem suggestion)<br>
<br>
Consider a case where you are doing a scatter / gather algorithm<br>
to spread processing across nodes or across different processing<br>
algorithms.  To make it concrete, suppose we have a database<br>
with 5 different tables and we need to collect information from each<br>
table to assemble into a single view to the user.<br>
<br>
The standard approach is to use the DB capability to join the tables.<br>
This introduces a single point access problem since the database<br>
server is doing all the work while the initiating process waits.<br>
<br>
Instead we put each of the tables in a different DB, flat file or ets<br>
table.<br>
Then we create a process for each one that provides caching and<br>
an access interface using messages.  They may end up on the same<br>
machine or on 5 different machines, but we will get parallelism on<br>
the I/O and possibly on the cache and assembly processing (if there<br>
are multiple cores or multiple machines in the case of cache and<br>
assembly).<br>
<br>
What does the code look like?  [Assume getQueries(UserId) generates<br>
a list of queries that are related to the database information we would<br>
like to display and that the length of this list matches the number of<br>
DB processes we have. ]<br>
<br>
doUserQuery(UserId) -><br>
    Queries = getQueries(UserId),<br>
    QueryRef = make_ref(),<br>
    [Pid ! {getData, QueryRef, UserId, Query} || {Pid, Query} <-<br>
lists:zip(DbPids, Queries)],<br>
    Responses = collect_responses(QueryRef),<br>
    display_db_info(Responses),<br>
    erlang:send_after(1000, self(), {cleanup, QueryRef}).<br>
<br>
<br>
This is a pretty hokey approach -- you would want something better<br>
than a 1 second delay to tell you whether to eliminate old messages<br>
from the queue, but it is a concrete example to describe why you<br>
would want to use selective receive and what to do to make sure it<br>
doesn't cause you a problem.<br>
<br>
collectResponses(QueryRef) -><br>
    collectResponses(QueryRef, []).<br>
<br>
collectResponses(QueryRef, Responses) -><br>
    receive<br>
       {responseData, QueryRef, _UserId, Results} -><br>
            collectResponses(QueryRef, [Responses | Results])<br>
    after 100 -> Responses<br>
    end.<br>
<br>
Again, my hokey example collects results as long as they are present<br>
or no new ones show up for 100 milliseconds.<br>
<br>
What we have so far is a single request message sent to 5 processes<br>
and a function which implements selective receive to collect only the<br>
messages that are in response to the initial request from a variety of<br>
responders (hopefully all, but not if some are slow to respond).<br>
<br>
What happens if we have a slow responding database, but it does<br>
actually produce results after 1/2 second.  It was too slow to be<br>
collected<br>
but it puts messages on the queue anyway.  If we have no mechanism<br>
to clear them, they will build up and cause things to gradually slow<br>
down.<br>
<br>
So at some higher level we need the following code:<br>
<br>
main() -><br>
    receive<br>
        %% Throw away late arriving results from a previous request<br>
        {cleanup, QueryRef} -> dumpOldResults(QueryRef);<br>
        {userRequest, UserId} -> doUserQuery(UserId)<br>
    end,<br>
    main().<br>
<br>
dumpOldQueryResults(QueryRef) -><br>
     receive<br>
         {responseData, QueryRef, _UserId, _Results} -><br>
              dumpOldQueryResults(QueryRef)<br>
     after 0 -> ok<br>
     end.<br>
<br>
In the main function, we give priority to cleaning up old messages.<br>
This will keep the queue short, however, it ensures a full queue<br>
scan for every user request.  As long as the queue is short, that<br>
won't hurt us.  Dumping old messages just cycles as fast as it can<br>
accepting messages that have our unique token and ignoring the<br>
rest of the data in the message.  If there are no clean up messages<br>
remaining, we than accept a new user request (which will necessarily<br>
cause the message queue to grow for a short period) and display the<br>
results.<br>
<br>
What did we see?  Selective receive used in 3 different ways:<br>
<br>
1) To collect the results of a request (a two-way session conversation)<br>
2) To handle self notifications for maintenance + user requests<br>
3) To handle old messages from an expired session<br>
<br>
It turns out the {cleanup, QueryRef} message is not necessary in<br>
the above example and we can just consume all {responseData, ...}<br>
messages inside main(), but it depends on how new requests are<br>
placed on the queue and whether timing allows two requests to<br>
be interleaved in the results set (you don't want to remove all the<br>
responseData for a pending request that has not had time to collect<br>
results yet).  Structuring as above gave more explicit different uses<br>
of selective receive.<br>
<br>
The problem remaining in the code above is that there is no<br>
"catch all" clause.  Do we worry about that?  It depends on how the<br>
system evolves.  If you interface to a known protocol and you have<br>
covered all the messages supported via selective receive, then<br>
you could do without a catch all.  If your system is evolving or there<br>
are other processes or programmers who might inject new message<br>
types, you need a catch all in the main/0 function (although you have<br>
to be careful not consume something that should stay on the queue).<br>
<br>
I have not tried this code, nor have I typed it into a erl prompt, so I<br>
can't guarantee it even compiles.  Mostly it should give you ideas<br>
about ways to use selective receive.<br>
<br>
What if we didn't have selective receive?  I see two choices:<br>
<br>
1) Start a thread and open a new socket to the databases for each<br>
user request.  Maintain the conversations as independent channels.<br>
<br>
2) Create a hash table of messages received related to each request.<br>
This requires managing the conversation correlations yourself.<br>
<br>
Both of these approaches are much more code than selective receive<br>
requires and the complexity of concepts does not increase, so selective<br>
receive is a better approach and a useful feature of erlang.<br>
<br>
Is there a better way to manage the conversations rather than the whole<br>
cleanup back channel messaging?<br>
<br>
If you can spawn a new process for each request, the responses will<br>
go to privately owned message queues.  When enough responses, or<br>
enough time has passed, the newly spawned request process returns<br>
its results and terminates.  Any messages stuck on the queue are<br>
eliminated.  Any future messages are silently discarded since there is<br>
no process to receive them.  If the backend DB process were monitoring<br>
the request process, it could even interrupt its response to discard the<br>
results rather than waiting for processing to complete and pass them<br>
on to a non-existent process.<br>
<br>
With erlang, there are many architectural choices when you consider<br>
the uses of messaging and selective receive.<br>
<div><div></div><div class="Wj3C7c"><br>
jay<br>
<br>
_______________________________________________<br>
erlang-questions mailing list<br>
<a href="mailto:erlang-questions@erlang.org">erlang-questions@erlang.org</a><br>
<a href="http://www.erlang.org/mailman/listinfo/erlang-questions" target="_blank">http://www.erlang.org/mailman/listinfo/erlang-questions</a><br>
<br>
</div></div></blockquote></div><br>