erlangyawshttpc

YAWS responds with errors under light load. "Unhandled reply fr. do_recv() {error,econnaborted}"


Hello fellow Erlangers :)

Just another Erlang enthusiast playing with the language here. I have a very simple YAWS app module which works fine when accessed by single clients. However, if I try to span multiple clients to simulate light traffic, those clients start receiving errors. Any idea what can be causing it?

Code spawning the clients:

-module(concurrent).
-export([measure/0, get/0]).

get() ->
  Result = httpc:request("http://localhost:8080/blah"),
  io:format("Result: ~p.\n", [Result]).

get_spawner(0) -> io:format("Done.\n");
get_spawner(Times) ->
  spawn(concurrent, get, []),
  get_spawner(Times - 1).

measure() ->
  io:fwrite("Starting inets...\n"),
  inets:start(),
  io:fwrite("Done\n"),

  io:fwrite("Creating blah...\n"),
  httpc:request(put, { "http://localhost:8080/blah", [], "", "" }, [], []),
  io:fwrite("Done\n"),

  get_spawner(9),

  io:fwrite("Stopping inets...\n"),
  inets:stop(),
  io:fwrite("Done\n"),

  init:stop().

I observed that it all works just fine for 8 concurrent clients, but when I span 9 or more clients, they start receiving the following message back from the server:

=ERROR REPORT==== 24-Jan-2017::12:30:04 ===
** Generic server httpc_manager terminating
** Last message in was {request,
                           {request,undefined,<0.74.0>,0,http,
                               {"localhost",8080},
                               "/blah",[],get,
                               {http_request_h,undefined,"keep-alive",
                                   undefined,undefined,undefined,undefined,
                                   undefined,undefined,undefined,undefined,
                                   undefined,undefined,undefined,undefined,
                                   undefined,undefined,"localhost:8080",
                                   undefined,undefined,undefined,undefined,
                                   undefined,undefined,undefined,undefined,
                                   undefined,[],undefined,undefined,undefined,
                                   undefined,"0",undefined,undefined,
                                   undefined,undefined,undefined,undefined,[]},
                               {[],[]},
                               {http_options,"HTTP/1.1",infinity,true,
                                   {essl,[]},
                                   undefined,false,infinity,false},
                               "http://localhost:8080/blah",[],none,[],
                               1485261004189,undefined,undefined,false}}
** When Server state == {state,[],httpc_manager__handler_db,
                            {cookie_db,undefined,8209},
                            httpc_manager__session_db,httpc_manager,
                            {options,
                                {undefined,[]},
                                {undefined,[]},
                                0,2,5,120000,2,disabled,false,inet,default,
                                default,[]}}
** Reason for termination ==
** {'EXIT',
       {shutdown,
           {gen_server,call,
               [httpc_handler_sup,
                {start_child,
                    [<0.61.0>,
                     {request,#Ref<0.0.6.64>,<0.74.0>,0,http,
                         {"localhost",8080},
                         "/blah",[],get,
                         {http_request_h,undefined,"keep-alive",undefined,
                             undefined,undefined,undefined,undefined,
                             undefined,undefined,undefined,undefined,
                             undefined,undefined,undefined,undefined,
                             undefined,"localhost:8080",undefined,undefined,
                             undefined,undefined,undefined,undefined,
                             undefined,undefined,undefined,[],undefined,
                             undefined,undefined,undefined,"0",undefined,
                             undefined,undefined,undefined,undefined,
                             undefined,[]},
                         {[],[]},
                         {http_options,"HTTP/1.1",infinity,true,
                             {essl,[]},
                             undefined,false,infinity,false},
                         "http://localhost:8080/blah",[],none,[],
                         1485261004189,undefined,undefined,false},
                     {options,
                         {undefined,[]},
                         {undefined,[]},
                         0,2,5,120000,2,disabled,false,inet,default,default,
                         []},
                     httpc_manager]},
                infinity]}}}

=ERROR REPORT==== 24-Jan-2017::12:30:04 ===
Error in process <0.74.0> with exit value:
{{case_clause,
     {undefined,
         {error,
             {'EXIT',
                 {shutdown,
                     {gen_server,call,
                         [httpc_handler_sup,
                          {start_child,
                              [<0.61.0>,
                               {request,#Ref<0.0.6.64>,<0.74.0>,0,http,
                                   {"localhost",8080},
                                   "/blah",[],get,
                                   {http_request_h,undefined,"keep-alive",
                                       undefined,undefined,undefined,
                                       undefined,undefined,undefined,
                                       undefined,undefined,undefined,
                                       undefined,undefined,undefined,
                                       undefined,undefined,"localhost:8080",
                                       undefined,undefined,undefined,
                                       undefined,undefined,undefined,
                                       undefined,undefined,undefined,[],
                                       undefined,undefined,undefined,
                                       undefined,"0",undefined,undefined,
                                       undefined,undefined,undefined,
                                       undefined,[]},
                                   {[],[]},
                                   {http_options,"HTTP/1.1",infinity,true,
                                       {essl,[]},
                                       undefined,false,infinity,false},
                                   "http://localhost:8080/blah",[],none,[],
                                   1485261004189,undefined,undefined,false},
                               {options,
                                   {undefined,[]},
                                   {undefined,[]},
                                   0,2,5,120000,2,disabled,false,inet,default,
                                   default,[]},
                               httpc_manager]},
                          infinity]}}}}}},
 [{httpc,handle_request,9,[{file,"httpc.erl"},{line,574}]},
  {concurrent,get,0,
      [{file,
           "c:/Users/piotr_justyna/Documents/rets/performance_tests/concurrent.erl"},
       {line,5}]}]}

On the server side I am getting this:

=ERROR REPORT==== 24-Jan-2017::12:28:55 ===
Unhandled reply fr. do_recv() {error,econnaborted}

I'm sure it's something straightforward, just can't figure out what it is. Is it the number of concurrent connections that causes it? Can you help shed more light on this?

Regards, Piotr


Solution

  • Your code spawns a number of httpc:request/1 calls, but then immediately afterwards it stops inets:

    ...
    get_spawner(9),
    io:fwrite("Stopping inets...\n"),
    inets:stop(),
    ...
    

    Because inets is shut down, requests that are still in progress are getting shut down prematurely, as you can see in the error report:

    ** Reason for termination ==
    ** {'EXIT',
           {shutdown,
               {gen_server,call,
                   [httpc_handler_sup,
    

    You need to make the parent of all the spawned requests wait for those requests to finish before calling inets:stop/0. One way to do that would be to pass the parent pid to each spawned child and once httpc:request/1 returns, have the child send a message to the parent. The parent would spawn all N children, then sit in a loop waiting to receive N completion messages from the children, and only after receiving all completion messages would it call inets:stop/0.

    The reason it works for 8 requests but not 9 is due to a race condition around the lack of waiting prior to calling inets:stop/0. The default size of the Yaws pool of request handling acceptor processes happens to be 8. When your code sends 8 requests, the pool handles it quickly enough to beat your code's call to inets:stop/0, but with 9, there's a slight delay as one acceptor handles two requests, and that slight timing change is enough to allow your inets:stop/0 call to clobber one or more of your client processes. You can modify the Yaws acceptor pool size via the acceptor_pool_size configuration variable.

    If you're serious about load testing you might consider using an industrial-strength solution like tsung instead of rolling your own.