You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@httpd.apache.org by Saju Pillai <sa...@gmail.com> on 2006/03/01 11:45:00 UTC

Event MPM accept() handling

Greetings,

(On httpd-trunk event-mpm event.c)

 I can understand why serializing apr_pollset_poll() & accept() for the
listener threads doesn't make sense in the event-mpm. A quick look through
the code leaves me confused about the following ...

 It looks like all the listener threads epoll() simultaenously on the
listener sockets + their private set of sockets added to the pollset by
workers. Will apr_pollset_poll() return "success" to each listener if a new
connection arrives on a main listener socket ? If so won't each listener
attempt to accept() the new connection ?

How is this prevented from happening in the event mpm ?

regards
srp

Re: Event MPM accept() handling

Posted by Greg Ames <gr...@apache.org>.
Paul Querna wrote:

> This is traditionally called the 'Thundering Herd' Problem.
> 
> When you have N worker processes, and all N of them are awoken for an 
> accept()'able new client.  Unlike the prefork MPM, N is usually a smaller
> number in Event, because you don't need that many EventThreads Per 
> Number of WorkerThreads,

I'll buy that.  you only need 2 processes to deal with 3rd party module reliability 
worries.  not much of a herd.  if you have reliable code and we scale well enough to 
handle the traffic, nothing stops you from using one process.

> I also reason that on a busy server, the place you most likely want to 
> put the event mpm, you will have many more non-listener sockets to deal 
> with, and those will fire more often than new clients are connecting, 

sure, assuming that there are typically multiple HTTP requests per connection or big 
files/slow network combinations to trigger Brian's async write logic.

> meaning you will already be coming out of the _poll() with 'real' 
> events.  So the 'cost' of being put into the Run Queue isn't a 'waste', 
> like it is on the Prefork MPM, where you just would go back into _poll() 
> without having done anything.

I'll buy that too.  once the poll pops, there is a loop to consume all of the poll events 
in one go.  judging by the comments in worker.c::listener_thread, I think Manoj meant to 
do that in worker's ancestor but never got a round tuit.

Greg

Re: Event MPM accept() handling

Posted by Saju Pillai <sa...@gmail.com>.
On 01/03/06, Greg Ames <gr...@apache.org> wrote:
>
> Saju Pillai wrote:
>
> > For a brand new client connection, why should there be 2 exchanges
> > between the listener thread and the worker thread before the request is
> > actually read ?
> >
> > The client socket is accepted() and passed to a worker thread which runs
> > the create_connection hooks and marks the connection in the
> > CONN_STATE_CHECK_REQUEST_LINE_READABLE state.
>
> hmmm?
>
> in event.c::process_socket()
>
> if (cs == NULL) {           /* This is a new connection */
>      cs = apr_pcalloc(p, sizeof(conn_state_t));
> [...]
>      cs->state = CONN_STATE_READ_REQUEST_LINE;
>
> >   This is again pushed back
> > to the listener which will poll it for readability and mark it as
> > CONN_STATE_READ_REQUEST_LINE if it's readable. A worker now grabs this
> > client and performs the actual read.
>
> I want to walk thru this with gdb.  obviously you've been studying the
> code and I've been
> away from it for too long.



my bad ... The core create_connection hook does ...

 c->cs->expiration_time = 0;
 c->cs->state = CONN_STATE_CHECK_REQUEST_LINE_READABLE;
 c->cs->c = c;
 c->cs->p = ptrans;
 c->cs->bucket_alloc = alloc;

  return c;

but process_socket in event.c does the explicit

cs->state = CONN_STATE_READ_REQUEST_LINE;

for a new conenction.. Sorry, while reading I jumped from
create_connection() to the if .. else code in process_socket which handles
the CONN_STATE_CHECK_REQUEST_LINE_READABLE by pushing client socket back to
listener.

The worker really does accept() & read() without round trips to listener.

regards
srp




> Why do we need an explicit state CONN_STATE_CHECK_REQUEST_LINE_READABLE
> > ?.
>
> it is very useful when checking for requests after the first (Keep-Alive)
>
> >  Can the first worker not read() directly from the accepted client ?
>
> sure it can.  that is the intention.
>
> Greg
>

Re: Event MPM accept() handling

Posted by Greg Ames <gr...@apache.org>.
Saju Pillai wrote:

> For a brand new client connection, why should there be 2 exchanges 
> between the listener thread and the worker thread before the request is 
> actually read ?
> 
> The client socket is accepted() and passed to a worker thread which runs 
> the create_connection hooks and marks the connection in the 
> CONN_STATE_CHECK_REQUEST_LINE_READABLE state. 

hmmm?

in event.c::process_socket()

if (cs == NULL) {           /* This is a new connection */
     cs = apr_pcalloc(p, sizeof(conn_state_t));
[...]
     cs->state = CONN_STATE_READ_REQUEST_LINE;

>   This is again pushed back 
> to the listener which will poll it for readability and mark it as  
> CONN_STATE_READ_REQUEST_LINE if it's readable. A worker now grabs this 
> client and performs the actual read.

I want to walk thru this with gdb.  obviously you've been studying the code and I've been 
away from it for too long.

> Why do we need an explicit state CONN_STATE_CHECK_REQUEST_LINE_READABLE 
> ?. 

it is very useful when checking for requests after the first (Keep-Alive)

>  Can the first worker not read() directly from the accepted client ? 

sure it can.  that is the intention.

Greg

Re: Event MPM accept() handling

Posted by Saju Pillai <sa...@gmail.com>.
Final question on event_mpm

For a brand new client connection, why should there be 2 exchanges between
the listener thread and the worker thread before the request is actually
read ?

The client socket is accepted() and passed to a worker thread which runs the
create_connection hooks and marks the connection in the
CONN_STATE_CHECK_REQUEST_LINE_READABLE state. This is again pushed back to
the listener which will poll it for readability and mark it as
CONN_STATE_READ_REQUEST_LINE if it's readable. A worker now grabs this
client and performs the actual read.

Why do we need an explicit state CONN_STATE_CHECK_REQUEST_LINE_READABLE ?.
Can the first worker not read() directly from the accepted client ? Is it to
avoid a DoS attack where clients open connections but don't send a request ?

In cases of kernel (like freebsd) that support "http_ready" filters won't
the accept() only succeed once a request line is already available for
reading ? i.e on bsd (maybe) we can get better benchmarks if accept() &
read() at the same time.

regards
srp



On 01/03/06, Paul Querna <ch...@force-elite.com> wrote:
>
> Saju Pillai wrote:
> >
> >
> > On 01/03/06, *Paul Querna* <chip@force-elite.com
> > <ma...@force-elite.com>> wrote:
> >
> >     Greg Ames wrote:
> >      > Saju Pillai wrote:
> >      >
> >      >> I can understand why serializing apr_pollset_poll() & accept()
> >     for the
> >      >> listener threads doesn't make sense in the event-mpm.   A quick
> look
> >      >> through the code leaves me confused about the following ...
> >      >  >
> >      >>  It looks like all the listener threads epoll() simultaenously
> >     on the
> >      >> listener sockets + their private set of sockets added to the
> >     pollset
> >      >> by workers.
> >      >
> >      > looks like you are correct.
> >      >
> >      > originally there was a separate event thread for everything but
> new
> >      > connections and the listener thread's accept serialization was
> >     the same
> >      > as worker's.  then it seemed like a good idea to merge the
> >     listener and
> >      > event threads, and it only supported a single worker process
> briefly.
> >      > since there was only one merged listener/event thread in the
> whole
> >      > server there was nothing to serialize at that time.  then a few
> of us
> >      > grumbled about what happens if some 3rd party module seg faults
> >     or leaks
> >      > memory and we went back to multiple worker processes.
> >
> >
> >
> > Can you tell us what was the reasoning behind merging the event thread
> > and the listener thread ? A seperate event thread implies that the
> > listener threads could serialize for accept() for platforms that need
> > serialized accept(). The event thread never needs to accept() - it only
> > needs to check for read/write/close events.  ( or the platforms that
> > event mpm runs on - don't need serializing of accept ?)
>
> Because the Event MPM requires a moderm OS with a newer-epoll/kqueue
> type thing, none of our supported operating systems require the use of
> an accept lock :)
>
> -Paul
>
>

Re: Event MPM accept() handling

Posted by Paul Querna <ch...@force-elite.com>.
Saju Pillai wrote:
> 
> 
> On 01/03/06, *Paul Querna* <chip@force-elite.com 
> <ma...@force-elite.com>> wrote:
> 
>     Greg Ames wrote:
>      > Saju Pillai wrote:
>      >
>      >> I can understand why serializing apr_pollset_poll() & accept()
>     for the
>      >> listener threads doesn't make sense in the event-mpm.   A quick look
>      >> through the code leaves me confused about the following ...
>      >  >
>      >>  It looks like all the listener threads epoll() simultaenously
>     on the
>      >> listener sockets + their private set of sockets added to the
>     pollset
>      >> by workers.
>      >
>      > looks like you are correct.
>      >
>      > originally there was a separate event thread for everything but new
>      > connections and the listener thread's accept serialization was
>     the same
>      > as worker's.  then it seemed like a good idea to merge the
>     listener and
>      > event threads, and it only supported a single worker process briefly.
>      > since there was only one merged listener/event thread in the whole
>      > server there was nothing to serialize at that time.  then a few of us
>      > grumbled about what happens if some 3rd party module seg faults
>     or leaks
>      > memory and we went back to multiple worker processes. 
> 
> 
> 
> Can you tell us what was the reasoning behind merging the event thread 
> and the listener thread ? A seperate event thread implies that the 
> listener threads could serialize for accept() for platforms that need 
> serialized accept(). The event thread never needs to accept() - it only 
> needs to check for read/write/close events.  ( or the platforms that 
> event mpm runs on - don't need serializing of accept ?)

Because the Event MPM requires a moderm OS with a newer-epoll/kqueue 
type thing, none of our supported operating systems require the use of 
an accept lock :)

-Paul


Re: Event MPM accept() handling

Posted by Saju Pillai <sa...@gmail.com>.
On 01/03/06, Paul Querna <ch...@force-elite.com> wrote:
>
> Greg Ames wrote:
> > Saju Pillai wrote:
> >
> >> I can understand why serializing apr_pollset_poll() & accept() for the
> >> listener threads doesn't make sense in the event-mpm.   A quick look
> >> through the code leaves me confused about the following ...
> >  >
> >>  It looks like all the listener threads epoll() simultaenously on the
> >> listener sockets + their private set of sockets added to the pollset
> >> by workers.
> >
> > looks like you are correct.
> >
> > originally there was a separate event thread for everything but new
> > connections and the listener thread's accept serialization was the same
> > as worker's.  then it seemed like a good idea to merge the listener and
> > event threads, and it only supported a single worker process briefly.
> > since there was only one merged listener/event thread in the whole
> > server there was nothing to serialize at that time.  then a few of us
> > grumbled about what happens if some 3rd party module seg faults or leaks
> > memory and we went back to multiple worker processes.



Can you tell us what was the reasoning behind merging the event thread and
the listener thread ? A seperate event thread implies that the listener
threads could serialize for accept() for platforms that need serialized
accept(). The event thread never needs to accept() - it only needs to check
for read/write/close events.  ( or the platforms that event mpm runs on -
don't need serializing of accept ?)

regards
srp




>
> >>   Will apr_pollset_poll() return "success" to each listener if a new
> >> connection arrives on a main listener socket ? If so won't each
> >> listener attempt to accept() the new connection ?
> >
> > I think so, but I'm not a fancy poll expert.  Paul?
>
> Correct. This is on Purpose.  It actually turns out to be faster to call
> a nonblocking accept() and fail than it is to use the AcceptLock() that
> the other MPMs do. (Micro benchmarks I did back then seemed to show
> this, and just hammering a machine and comparing the results for Worker
> & Event MPMs seem to indicate this too).
>
> > then the question is how bad is it?
>
> Not that bad :)




This is traditionally called the 'Thundering Herd' Problem.
>
> When you have N worker processes, and all N of them are awoken for an
> accept()'able new client. Unlike the prefork MPM, N is usually a smaller
> number in Event, because you don't need that many EventThreads Per
> Number of WorkerThreads,
>
> I also reason that on a busy server, the place you most likely want to
> put the event mpm, you will have many more non-listener sockets to deal
> with, and those will fire more often than new clients are connecting,
> meaning you will already be coming out of the _poll() with 'real'
> events.  So the 'cost' of being put into the Run Queue isn't a 'waste',
> like it is on the Prefork MPM, where you just would go back into _poll()
> without having done anything.
>
> -Paul
>
>

Re: Event MPM accept() handling

Posted by Paul Querna <ch...@force-elite.com>.
Greg Ames wrote:
> Saju Pillai wrote:
> 
>> I can understand why serializing apr_pollset_poll() & accept() for the 
>> listener threads doesn't make sense in the event-mpm.   A quick look
>> through the code leaves me confused about the following ...
>  >
>>  It looks like all the listener threads epoll() simultaenously on the 
>> listener sockets + their private set of sockets added to the pollset 
>> by workers. 
> 
> looks like you are correct.
> 
> originally there was a separate event thread for everything but new 
> connections and the listener thread's accept serialization was the same 
> as worker's.  then it seemed like a good idea to merge the listener and 
> event threads, and it only supported a single worker process briefly.  
> since there was only one merged listener/event thread in the whole 
> server there was nothing to serialize at that time.  then a few of us 
> grumbled about what happens if some 3rd party module seg faults or leaks 
> memory and we went back to multiple worker processes.
> 
>>   Will apr_pollset_poll() return "success" to each listener if a new 
>> connection arrives on a main listener socket ? If so won't each
>> listener attempt to accept() the new connection ?
> 
> I think so, but I'm not a fancy poll expert.  Paul?

Correct. This is on Purpose.  It actually turns out to be faster to call 
a nonblocking accept() and fail than it is to use the AcceptLock() that 
the other MPMs do. (Micro benchmarks I did back then seemed to show 
this, and just hammering a machine and comparing the results for Worker 
& Event MPMs seem to indicate this too).

> then the question is how bad is it?

Not that bad :)

This is traditionally called the 'Thundering Herd' Problem.

When you have N worker processes, and all N of them are awoken for an 
accept()'able new client. Unlike the prefork MPM, N is usually a smaller 
number in Event, because you don't need that many EventThreads Per 
Number of WorkerThreads,

I also reason that on a busy server, the place you most likely want to 
put the event mpm, you will have many more non-listener sockets to deal 
with, and those will fire more often than new clients are connecting, 
meaning you will already be coming out of the _poll() with 'real' 
events.  So the 'cost' of being put into the Run Queue isn't a 'waste', 
like it is on the Prefork MPM, where you just would go back into _poll() 
without having done anything.

-Paul


Re: Event MPM accept() handling

Posted by Greg Ames <gr...@apache.org>.
Saju Pillai wrote:

> I can understand why serializing apr_pollset_poll() & accept() for the 
> listener threads doesn't make sense in the event-mpm.   A quick look
> through the code leaves me confused about the following ...
 >
>  It looks like all the listener threads epoll() simultaenously on the 
> listener sockets + their private set of sockets added to the pollset by 
> workers. 

looks like you are correct.

originally there was a separate event thread for everything but new connections and the 
listener thread's accept serialization was the same as worker's.  then it seemed like a 
good idea to merge the listener and event threads, and it only supported a single worker 
process briefly.  since there was only one merged listener/event thread in the whole 
server there was nothing to serialize at that time.  then a few of us grumbled about what 
happens if some 3rd party module seg faults or leaks memory and we went back to multiple 
worker processes.

>   Will apr_pollset_poll() return "success" to each listener if a 
> new connection arrives on a main listener socket ? If so won't each
> listener attempt to accept() the new connection ?

I think so, but I'm not a fancy poll expert.  Paul?

then the question is how bad is it?

Greg



Greg