Yann,

the positive effect of the recycling of subpools by the allocator is 
measureable in performance by me. Without a mutex'ed allocator however, the 
destruction of subpools did crash on me (but it might also be another bug in my 
code, of course). First, I resolved to using root pools for requests. That did 
work, however no recycling happened there, of course.

With the mutex'ed allocator, it works nicely to have subpools for requests in 
different threads. The recycling happens and the performance drastically 
increases (in my tests, I have many small GETs on a single connection).

Looking at the code in httpd, mpm_worker and mpm_event seem to create own 
alloctors for the pools they use and those allocators are not protected by a 
mutex. And need not, since normally a connection and all its sub-requests run 
in the same thread, right?

Cheers,

  Stefan

----------------------------------------------------------------------------------------------------------------------------
I'll explain a bit how mod_h2 works now in principle (without code), since you 
asked:

1. a) There are connection filters like in mod_spdy for registering in ALPN 
negotiations and taking over processing.
   b) there is a request filter for non-TLS connections that looks for 
"Upgrade:" headers and tokes over if the correct "h2c" protocol is mentioned 
(and no request body is present). An intermediate 101 response is sent and the 
h2c processing basically takes place on the connection of the request. This 
works basically as the websocket module does it.

2. When h2/h2c is enabled on a connection, a h2_session instance is created 
that performs the HTTP/2 state and administrative processing. When no 
sub-requests are onoing, it does blocking reads. Otherwise it does non-blocking 
reads on connection and sub-request outputs with a backoff strategy.
 - libnghttp2 lives isolated in this h2_session, the session is only active in 
the initial connection thread (t0)
 - the session opens a new apr_pool_t with its own mutex'ed allocator.

3. When a new sub-request arrives (stream in http2), the session creates a new 
subpool and does all allocations belonging to the sub-request with this 
subpool. This is all staying inside t0 as well. All this is represented by a 
h2_stream. The new subpools are necessary so that long, long, long living 
connections do not eat up the memory.

4. For the handling of sub-requests in other threads (tn), each sub-request 
gets another subpool that is only used in tn. The object representing this is a 
h2_task. It is given to a worker queue.

5. For the handling of sub-requests, a h2_task creates a new conn_rec, 
populates it with enough data from master to make httpd core sufficiently happy 
to parse and run the request on this connection. The new connection gets its 
own input/output filters and mod_ssl is disabled on it.
 - Here is a lot of plumbing done that should some day no longer be necessary. 
The complete sub-requests with all headers do basically exist, but are 
serialized into HTTP/1 format again for the core parsers. Attempts to call 
something like ap_run_request(r) directly have failed so far.
 - mpm_event does currently not play nice since it has it's special 
event_conn_state structure hidden behind the normal connection state. I have 
written a hack that make mod_h2 run in mpm_event, but that makes assumptions 
about conn_state. An function in mpm that just sets up a "slave" conn_rec, 
given a master, would be helpful here. ap_run_create_connection() is  not 
enough.

6. Data Transfers
Request body data needs to be transferred from session to tasks, or thread-wise 
speaking from t0 to tn and response bodies the other way around. Here I am 
currently working to replace my previous heap allocated buffers with 
apr_bucket_brigades. I hope with my safe allocator and the proper instance of 
apr_bucket_alloc_t, I can make this work. Then mod_h2 would transfer data 
uncopied between sub-requests to the main session processing.

The session has a special objects for transferring data between threads and 
session that has its own mutex for synchronization and some conditionals for 
write/read blocking handling. It also controls memory consumption by blocking 
sub-request responses when a certain amount has been buffered.

Overall, we have:

Main players:
  conn_rec *master 1--->1 h2_session 1--->n* h2_stream 1--->1* h2_task

Pools:
  master->pool       from the main connection
  h2_seession->pool  subpool of master->pool with mutex'ed alloc
  h2_stream->pool    subpool of h2_session->pool
  h2_task->pool      subpool of h2_session->pool

Mutex/Session:
  1 for apr_allocator_t
  1 for session/task io sync

Memory/CPU Footprint: each session has a maximum number of open sub-requests 
(configurable) which are executed by a worker pool (min/max per child 
configurable). Memory limit is buffered memory per sub-request output 
(configurable) and maximum input window controlled by HTTP/2 flow control  
(configurable).

Some clients may keep such connections open for a long time. Here some 
fine-tuning/configurations might become handy (timeout). From a resource 
perspective will be no difference to a long living http/1 connection: if no 
sub-requests are open, all subpools are destroyed and the session performs a 
blocking read on its main connection.

> Am 20.03.2015 um 12:44 schrieb Yann Ylavic <ylavic....@gmail.com>:
> 
> More thoughts...
> 
> On Fri, Mar 20, 2015 at 12:00 PM, Yann Ylavic <ylavic....@gmail.com> wrote:
>> 
>> While pool (allocator) allocations are not thread-safe, creating a
>> subpool is, and each thread can than use its own pool (this model is
>> often used with APR pools).
>> 
>> This tells nothing about pool allocated objects' lifetime though
>> (accross/after threads), so maybe can you describe a bit more (but
>> less than the code ;) which object has its own or shared pool, with
>> regard to http/2 model (request/stream/connection/frame)?
> 
> When a subpool is destroyed, it is also recycled by the allocator for
> further use, and it can also be cleared-then-recycled explicitly (hand
> made, with locking).
> 
> It shouldn't be an issue to create a (sub)pool per http/2 entity
> (request/stream/connection/frame), the lifetime can even be
> controlled/terminated by special (apr_)buckets (destructors), which
> when sent through the filters and handled by the last (core) filter,
> will be cleared (eg. in httpd, when the request is finished, an EOR
> bucket is sent to output filters and will destroy the request once
> (not before!) it is fully forwarded, or an error occured (thus the
> final brigade containing the special bucket is destroyed, explicitly
> or with its pool, and so is the bucket).
> 
> With this model, it's "just" a matter of that entity belongs to that
> other one (destroyed with it, and possibly cleared-than-recycled in
> the meantime for special needs).
> 
> Can't that feet to http/2 model?

<green/>bytes GmbH
Hafenweg 16, 48155 Münster, Germany
Phone: +49 251 2807760. Amtsgericht Münster: HRB5782



Reply via email to