httpd-dev mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From Tony Finch <...@dotat.at>
Subject Re: [PATCH] Filter registration.
Date Thu, 27 Jul 2000 05:52:50 GMT
[apologies for the length]

This is the result of conversations with Ryan over the last couple of
days and a few hours of refining the ideas and presentation... It
slightly escaped from me, so I hope it makes sense :-)

"Roy T. Fielding" <fielding@kiwi.ICS.UCI.EDU> wrote:
>Tony Finch <dot@dotat.at> wrote:
>>
>>I think the lifetime of a bucket should be independent of the request
>>handler function because otherwise you lose a lot of advantages of the
>>abstraction. I wouldn't consider it to be a properly first-class data
>>type if you can't return it from a function.
>
>Hmmm, I disagree -- you just have to make it part of the abstraction.
>If we say that the bucket always has to be emptied before it can be
>returned, and we think of the write call always "returning" the
>bucket when it returns, then this does match the abstraction.

I don't think this is useful because a lot of the data that you put
into a bucket has a longer lifetime than only one trip down and up the
call stack, and you want to be able to make the most of that property.

>It also makes a certain degree of sense.  What we really care about
>being persistent is the contents of the bucket, not the bucket itself.

But the bucket exists to hold the metadata for its contents, and so
the bucket should exist as long as the contents do. I'm not very happy
with the current names for the bucket colours because I find them
confusing. Instead I prefer these names; some of them are less
necessary than others so won't be implemented right away:

IMMORTAL
	data that's around for ever, like literal strings
TRANSIENT
	short-lifetime data (e.g. in a buffer that will be re-written
	when the call stack is unwound)
POOL
	data in a pool, e.g. a variable in the environment table in
	the request pool
HEAP
	data on the C heap that must be free()d when the bucket is
	destroyed
MMAP
	mmap()ed data that must be munmap()ed when the bucket is
	destroyed
FILE
	a region of a file (not in memory yet) for sendfile()
PIPE
	data that will (in the future) come from a pipe e.g. a CGI

The basic idea is that when a bucket is created the contents that you
pass to the creation function become the responsibility of the bucket.
There are a couple of exceptions (the immortal and transient colours)
for reasons of optimisation (and because the free operation is a
no-op), and in the pool case the bucket shares responsibility with the
pool code.

Some of the buckets have to be slightly more elaborate in order to
handle the split operation efficiently and to ensure that the data
isn't freed when a bucket still refers to it. I.e. the buckets refer
to an extra structure for holding the data together with a reference
count.

Buckets are consumed in two stages: first the contents (in the form of
a base pointer and length) are retrieved, and then the bucket is told
how many bytes have actually been used (sent to the network or the
next filter) which updates its internal base and length. When all the
bytes have been consumed the bucket is deleted.

Deleting is straightforward except for POOL buckets. Pool buckets
exist so that if their data needs to be kept longer than the pool
(e.g. concatenating the end of one response onto the start of the next
pipelined response) then this can be spotted and the data copied
somewhere safe. If the pool disappears before the bucket does then the
cleanup that the bucket has registered copies the data onto the heap
and turns the bucket into a HEAP bucket. If the opposite occurs the
cleanup just has to be deregistered.

Getting the contents of the bucket is more interesting. Most of the
time the function can just return the base pointer and length that are
held in the bucket, but in the FILE and PIPE cases the pointer and
length aren't there yet. Therefore a FILE bucket would mmap the
required region and morph into an MMAP bucket (which could be done
in-place but it might be simpler to create a new bucket and delete the
old one). A PIPE bucket would read some data from the pipe and put it
into a HEAP bucket which gets inserted into the brigade before the
PIPE bucket which remains there in order to represent the rest of the
stream. (PIPE buckets are cool because they allow you to offload
simple CGIs to a select() thread in the same way that you can with
FILE buckets.)

Another important thing is the handling of TRANSIENT buckets, where
the bottom level filter must copy the data if it needs to stay around
after the function returns. I tend to think this should be handled as
a special-case which would morph TRANSIENT buckets into HEAP buckets,
since other colours don't need to do anything clever.

These bucket types are quite ad-hoc which doesn't seem very elegant,
but splitting the ideas of the lifetime and the type of the data in
the bucket into orthogonal dimensions doesn't provide any obvious
simplifications as far as I can see. In most cases the buckets and
their contents have identical lifetimes, and where this isn't the case
it is to support special semantics or optimisations so I suppose this
makes sense.

If a bucket's contents can be altered in place then there are some
opportunities for optimisation. One possibility is 8 bit character set
translation, but Ryan tells me that the iconv stuff might not work in
place. Another is appending to the end of a bucket brigade with
something like printf(), which happens quite frequently. One option is
to format the data into a buffer on the stack and pass it down as a
TRANSIENT bucket; this is quite efficient if the bucket doesn't have
to be kept (and the data copied). Alternatively, if HEAP bucket data
is overwritable and they are allocated with extra space for data then
you can sprintf() straight into the extra space and keep top
efficiency.

>Consider, for example, real-life bucket brigades.  The bucket is
>passed downstream with water and then back up empty.

This analogy doesn't really work because we don't always completely
empty the buckets at the bottom.

>It is crucial to control the lifetime of the data within the bucket,
>not the bucket structure itself.  The reason is that, on those occasions
>when it is necessary to set bucket data aside, it is often more efficient
>for the server to combine all the buckets into a single page at that
>point rather than set aside both the data and the bucket structures.

Agreed, but that is an optimisation. At this point you are discarding
the original bucket and creating a new one, and you have moved the
data to a new place with a different lifetime.

>Personally, I'd rather reinvent lists than import a huge pile of
>poorly named macros, of which we will only use five or six.  Dean's
>splim has the next/prev pointers in the structure.

*shrug* This is an uninteresting discussion; I was simply trying to
reduce NIH.

Tony.
-- 
f.a.n.finch    fanf@covalent.net    dot@dotat.at
305 strip-mined comedy ore

Mime
View raw message