Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-21 Thread rbb
On Sat, 21 Dec 2002, Cliff Woolley wrote:

 On Fri, 20 Dec 2002 [EMAIL PROTECTED] wrote:

  apr_brigade_create(bb, ...);
  fill_brigade_with_data(bb, ...);
  ap_pass_brigade(bb, ...)
 
  ap_clean_brigade(bb, ...);
  fill_brigade_with_more_data(bb, ...);
  ap_pass_brigade(bb, ...);
 
  This is perfectly legal for a handler to do.

 No it isn't, because if ap_pass_brigade() makes it all the way down to the
 core_output_filter, then after ap_pass_brigade_() returns, bb will have
 been destroyed by the core_output_filter.  I'm not necessarily saying
 that's *right*, just that that's the case today.

That would be a pretty massive bug.  I have both handlers and filters that
follow this pattern because it is supposed to work.  The reason it works
in my testing, is that _most_ of the time the core_output_filter doesn't
destroy that brigade.

if (ctx-b) {
APR_BRIGADE_CONCAT(ctx-b, b);
b = ctx-b;
ctx-b = NULL;
}

This is where the magic happens.  If the filter has already saved aside a
brigade for this request (or a previous request in this connection), we
merge the two and drop the new one (a potential memory leak if not for
pools).

As I have said all day, a filter cannot keep or destroy a brigade that it
didn't create.  The creator of the brigade owns the brigade, and no other
filter can modify the brigade.  They can modify the buckets in the
brigade, but they can't do anything to the brigade itself.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-21 Thread rbb
On 20 Dec 2002, Brian Pane wrote:

 On Fri, 2002-12-20 at 21:37, [EMAIL PROTECTED] wrote:
  On 20 Dec 2002, Brian Pane wrote:
 
   On Fri, 2002-12-20 at 13:50, [EMAIL PROTECTED] wrote:
  
 Basically, if you received a brigade from a higher filter, you can only
 assume that it will survive a single trip down the filter stack.
  
   Right.  And you also can't assume that the context that passed
   the brigade down the filter stack still exists by the time the
   brigade reaches the last filter.  It's a safe assumption in
   httpd-2.0, where the brigade is passed as a synchronous function
   call, but it's not valid for apps in general (potentially including
   future Apache versions) where the brigade may be queued up for
   asynchronous processing by an I/O completion thread.
 
  Actually, it's not even a safe assumption in httpd-2.0.  You have data
  from requests that originally lived in brigades allocated out of the
  request pool moved to brigades allocated out of the connection pool
  because the request pool is going away.  But that just proves the point,
  the data must be moved from one brigade to another to change it's
  lifetime.  That is part of the design of buckets and brigades.  It is why
  the concat and split operations were written for maximum performance.
 
  We are talking about a couple of pointer assignments for the concatenation
  and a malloc and some pointer assignments for the split.

 And you're also talking about needing a pool from which you
 can do the alloc.  With pool-based brigade allocation, there
 are two basic choices when allocating a new brigade during
 the split, and they both have problems:

Um, you need a pool that lives at least as long as your filter.  But that
should make sense, because if you are going to do any memory allocation,
you will need a pool in your filter.

- Allocate from a connection pool.  This is what httpd-2.0
  does, but it's not a robust solution, given that connections
  can be very long-lived.  And for non-http connections, like
  an app server connector, connections may live forever, so
  doing per-request allocations from a connection-level pool
  guarantees a memory leak.

No, httpd-2.0 only uses the connection pool in filters that live as long
as the connection.  If that scope doesn't make sense, then you have to
clear the brigade when you are done with it.  Remember, we are talking
about a couple of bytes for the size of the brigade.  Even if it does leak
until the end of the connection, it isn't a very big leak.  It is a huge
leak if you don't clear the brigade, but that is why we have
brigade_clear.

 Pools just aren't a good match for the allocation needs of
 brigades.  We happen to have been able to hack around many
 of the problems through liberal use of the copy the brigade
 into another pool with a more appropriate scope technique
 in httpd-2.0, but that's not a solution for apps in general.

I'm really sorry, but I am sick of hearing this isn't a solution for apps
in general.  This is now the fourth time that I have asked for a clear
description of _why_.  I helped to design and write buckets and
bucket_brigades.  I understand them incredibly well.  You seem to be
unable or unwilling to give a clear description of the problem you are
having.  All you are doing is hand-waving saying that this might be a
problem for some apps.  Give a clear description please.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-21 Thread rbb
On 20 Dec 2002, Brian Pane wrote:

 On Fri, 2002-12-20 at 22:03, Cliff Woolley wrote:

  Can we at least agree to do one thing:
  release 2.0.44 *without* Brian's change.

 Okay with me.

I am vetoing this change until I get a clear description of the problem it
is solving.  I have explained multiple times why it is a bad change, but I
can't offer any other solutions until I know why it is needed.

Then we can sort out the best
  course of action without delaying the entire release process on the stable
  branch.  It might well be that the best course of action is to just fix
  the places in httpd that abuse brigades.  But as Bill pointed out, what
  about third party modules?  Ay caramba.

 FirstBill's patch should allow third party 2.0.x modules to work.

It will not.  If you _ever_ create a brigade with a NULL pool, you will
have memory leaks.  I dare say that every filter ever written will leak
if the feature implemented with this patch is ever used.  I have already
posted one code segment in the core_output_filter that proves the memory
leak exists.  Trust me when I tell you that there are hundreds more, and
some of them are in 3rd party modules that you don't control.

  +1 to moving brigades back into httpd, fwiw.

 +1

++1, but they should go into an external library so that projects like
serf have access to them.  Happily, I don't have to worry about that.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-21 Thread rbb


On 21 Dec 2002, Brian Pane wrote:

 On Sat, 2002-12-21 at 07:58, [EMAIL PROTECTED] wrote:

  I'm really sorry, but I am sick of hearing this isn't a solution for apps
  in general.  This is now the fourth time that I have asked for a clear
  description of _why_.  I helped to design and write buckets and
  bucket_brigades.  I understand them incredibly well.  You seem to be
  unable or unwilling to give a clear description of the problem you are
  having.  All you are doing is hand-waving saying that this might be a
  problem for some apps.  Give a clear description please.

 Sorry, but that's just bogus.  I've described repeatedly, and in
 detail, the reasons why we can't make bucket allocation dependent
 on pools.  You keep posting objections along the lines of but we
 can just move a brigade into a pool of appropriate scope, when
 you know that's not a solution.

No, that is a solution, in fact, it was part of the original design.  What
you've done is said The brigade may not live long enough, but you
haven't explained how that is the case.  You also haven't explained why
moving the buckets to another brigade won't work.  Which I have a very
hard time believing, because that is how Apache works today.  You also
haven't answered the problem that even _WITH_ this change, you still need
to move the buckets to a new brigade.  At the end of the day, this change
doesn't get you anything, because you _still_ have to do the brigade
migration that you say you can't do.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-21 Thread rbb

What
  you've done is said The brigade may not live long enough, but you
  haven't explained how that is the case.

 This, too, is something that we've discussed already.  Request
 cleanups can happen asynchronously relative to transmission of
 brigades created from the request.  Having a pool cleanup
 trigger the destruction of a brigade in another thread is
 dangerous.  The canonical solution is to migrate the data to
 a new brigade outside the pool before handing it off to another
 thread, which is fine as long as that doesn't create a need for
 another pool in the application (for the reasons noted above).

You are talking about an application that is built around pools.  If you
want an async MPM that uses a single thread to write data to the network
and multiple threads to generate the data, then your network I/O thread
will need to have a pool.  This pool will be very long lived (as long as
the process is alive).  You can allocate the brigade when the server
starts, and just re-use it forever.  You can have a pool of brigades that
are continuosly re-used, or you can create a sub-pool for each call into
your network write function.  But, moving brigade allocation outside of
pools isn't solving your problem and it is opening huge holes in the
abstraction that was created.

Screw it.  Buckets and bucket brigades are being moved into a project that
I no longer have any desire to work on.  My veto stands for as long as the
code is in an APR project.  The change is wrong and it is dangerous.  I
have explained why in detail.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb


I am actually pretty sure that allocating brigades out of the
bucket_allocator is a VERY big mistake.  In fact, I am close to asking
that change to be backed out because it is too dangerous.

When we first designed buckets and bucket brigades, we made one VERY clear
distinction.  Bucket_brigades are allocated out of a pool, because that
stops us from leaking memory.  Buckets shouldn't be allocated out of a
pool, because nobody knows which pool to allocate them from.  Basically,
we said that in Apache, it is safe to just drop a bucket briade because
the pool cleanups will free the memory.  It is not safe to just drop a
bucket, it must either be left on the brigade to be cleaned up by the
brigade_cleanup function, or it must be destroyed when it is removed from
the brigade.

By allocating bucket_brigades out of the bucket_allocator, we have removed
the protection that the pool cleanups used to give us.  I may be wrong
about how the bucket_allocator works, but I don't believe I am.
Basically, this change will make Apache leak memory on some requests, and
it goes against the original design of buckets and bucket brigades.  In
fact, allocating the brigades out of a pool was a requirement, because
some people at the meeting were afraid of memory leaks with bucket
brigades.

Ryan


On Fri, 20 Dec 2002, Bill Stoddard wrote:

 If a pool is passed to apr_brigade_create, the brigade is allocated out of the
 pool. If the pool is NULL, the brigade is allocated out of the bucket allocator.
 We don't want a pool pointer hanging around in a brigade allocated out of the
 bucket allocator. That's just asking for trouble.  This patch makes how the
 brigade is allocated, either out of the pool or out of the allocator, explicit.

 Index: apr_brigade.c
 ===
 RCS file: /home/cvs/apr-util/buckets/apr_brigade.c,v
 retrieving revision 1.55
 diff -u -r1.55 apr_brigade.c
 --- apr_brigade.c 17 Dec 2002 19:16:39 -  1.55
 +++ apr_brigade.c 20 Dec 2002 15:17:58 -
 @@ -95,7 +95,9 @@
  apr_pool_cleanup_kill(b-p, b, brigade_cleanup);
  }
  rv = apr_brigade_cleanup(b);
 -apr_bucket_free(b);
 +if (!b-p) {
 +apr_bucket_free(b);
 +}
  return rv;
  }

 @@ -104,7 +106,12 @@
  {
  apr_bucket_brigade *b;

 -b = apr_bucket_alloc(sizeof(*b), list);
 +if (p) {
 +b = apr_palloc(p, sizeof(*b));
 +}
 +else {
 +b = apr_bucket_alloc(sizeof(*b), list);
 +}
  b-p = p;
  b-bucket_alloc = list;








Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb
On 20 Dec 2002, Brian Pane wrote:

 On Fri, 2002-12-20 at 08:57, [EMAIL PROTECTED] wrote:
  I am actually pretty sure that allocating brigades out of the
  bucket_allocator is a VERY big mistake.  In fact, I am close to asking
  that change to be backed out because it is too dangerous.
 
  When we first designed buckets and bucket brigades, we made one VERY clear
  distinction.  Bucket_brigades are allocated out of a pool, because that
  stops us from leaking memory.

 Allocating brigades from a pool is often a design mistake.
 Brigades tend to outlive the transactions that produce
 them (especially in apps like http servers), and having a
 brigade disappear as a side-effect of a transaction pool's
 cleanup will cause problems elsewhere in the code.

U, I don't believe that it is possible for a brigade to outlive the
transaction that created it, especially not when you look at how brigades
are used in the web server.  Basically, a handler creates a brigade and
passes it down the filter stack.  Each filter in the line is then allowed
to either concatenate that brigade to it's own brigade or keep sending it
down the stack.  At the bottom of the stack, the brigade must have been
generated out of the connection pool, which by definition lives as long as
the transaction that created it.

Basically, if you received a brigade from a higher filter, you can only
assume that it will survive a single trip down the filter stack.  That is
why all filters must create their own brigade to save data between calls.

  By allocating bucket_brigades out of the bucket_allocator, we have removed
  the protection that the pool cleanups used to give us.

 It's important to keep in mind just what sort of protection
 the pool-based brigade allocation offered.  It allowed code
 that did this:

 bb = apr_brigade_create(...);
 apr_brigade_destroy(bb);
 APR_BRIGADE_INSERT_HEAD(bb, bucket);

 to work accidentally.  I'm hesitant to call that protection.

Give it up, that is a side-effect of pools, and it isn't the protection
that we discussed when designing buckets and brigades.  The protection
that we are talking about is against memory leaks, which this change
almost definately opened up in the web server.  There is at least one
place in the code where we drop a bucket brigade on the floor without
cleaning it up, because we rely on the pool_cleanup to do it for us.  This
change removes that ability.  Of course, you won't see that memory leak
unless you make the correct request, but at some point, it will show up
and you will have a hell of a time tracking it down.  I can gaurantee that
this change caused a memory leak.  I know it did because Greg Stein and I
argued over leaving a comment in the code that said essentially This
would be a memory leak if the brigade were allocated out of a pool, but it
is, so it is safe.  We removed the comment, because the bucket_brigade
design requires that all brigades are allocated out of pools.

As for the bogus psuedo-code that you have above, that is pools for you.
The same thing could be said for _ANY_ apr type.  Try this sometime:

apr_file_open(f, .);
apr_file_close(f);
apr_file_name_get(fname, f);

You will get the correct value for fname without a segfault, and this will
work on all platforms.  The only way to fix that type of problem would be
to make all destroy/close functions accept a pointer to the object that
they are destroying/closing.  Then, we could set the pointer to NULL, and
the code above wouldn't work.

We don't try to protect people from doing bone-headed things like using a
structure after it has been destroyed, that is just a programmer not
paying attention.  However, the code that you have added removes the
memory leak protection that brigades were supposed to provide for buckets.
That is wrong, and thus the change should be removed.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb

 and you will have a hell of a time tracking it down.  I can gaurantee that
 this change caused a memory leak.  I know it did because Greg Stein and I
 argued over leaving a comment in the code that said essentially This
 would be a memory leak if the brigade were allocated out of a pool, but it

oops:  s/were/weren't/

 is, so it is safe.  We removed the comment, because the bucket_brigade
 design requires that all brigades are allocated out of pools.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb

On 20 Dec 2002, Brian Pane wrote:

 On Fri, 2002-12-20 at 12:19, [EMAIL PROTECTED] wrote:

  As for this not working when the filter and handler are in different
  threads, of course it will.  As long as the buckets are copied into a
  brigade that was allocated out of a pool that will still be alive when the
  filter is called, this will work just fine.

 In practice, it's not feasible to copy the buckets into a brigade
 allocated from a new pool.  The connection pool isn't generally
 suitable, as it may be a long time before it is cleaned up,
 especially in the case of a proxy.  Creating a response pool
 will work, but that doubles the amount of memory that the server
 must devote to a given request (one pool for request, one for
 response).

That is why the core_output_filter destroys the bucket_brigade, cleaning
up 90% of the memory used by the brigade (all the buckets) as soon as the
data is written to the network.  It is feasible to do the copy, because
nothing else works.  Regardless of how the brigade is allocated, the
brigade that is passed to you doesn't belong to you.  It belongs to the
filter/handler that originally allocated it, and you can't gaurantee that
it will still be around the next time your filter is called.  It is very
simple.  If you are going to set-aside data in a filter, you _must_ copy
the data to a brigade that you allocated.  Nothing else is gauranteed to
work.  That is why copying/merging brigades was made so cheap.  It is also
why buckets aren't allocated out of pools.

   The change doesn't remove that ability at all.  Both my patch
   and Bill's variant allow for a pool cleanup to destroy a brigade.
 
  Yes, but before it was mandatory, not it is optional.  It can't be
  optional, it will cause a memory leak.

 It's wildly inaccurate to say it will cause a memory leak.  A
 more correct characterization is, if the brigade is not allocated
 from a pool, its deallocation is the responsibility of the
 application.  Same semantics as every other non-pool-based
 allocation API ever developed.

It is not inaccurate at all.  The current code has a memory leak if you
don't pass a pool to the brigade_create function.  You may not see it, but
it is there.  Apache is built around pools to remove these types of memory
leaks.  Filters, buckets, and bucket_brigades were designed to use pools
to remove these kinds of memory leaks.  Your change allows a brigade to be
created that removes this protection.  Because the rest of the server
expects brigades to be allocated out of pools, you have created a memory
leak.  This isn't hand-waving.  As soon as Bill's patch is committed, if
you _ever_ pass NULL into the brigade_create function for the pool
variable, you will have a memory leak.  You can fix the leaks, but you
have removed a the memory protection that the filters were designed
around.

  The change isn't buying you anything at all, especially with Bill's
  proposed patch.  All filters must assume that the bucket_brigade was
  allocated out of a pool, because otherwise they will be broken if it
  wasn't.

 That's only true for Apache 2.0.  For later releases, it may
 be not only unnecessary but inadvisable to have brigades allocated
 from pools.  Once you've allocated a brigade from a pool, for
 example, any subsequent split of that brigade will alloc from
 the same pool.  If the split happens in a filter that's running
 in thread 2 while the request's handler is still running in thread
 1, the colliding apr_pallocs will segfault.  We almost certainly
 won't want to add a mutex to apr_palloc, because it would ruin
 the performance of lots of apps.  But if the brigade allocations
 are done from a bucket allocator, it's quite feasible to add a
 mutex (or possibly a spinlock) into the bucket allocator without
 compromising performance.

There are a lot of solutions to the problem you have described above.  The
easiest is to actually pass a pool to the brigade_split function, so that
you know which pool is used to allocate the new brigade.  Yes, using the
bucket_allocator is another solution, but it is currently causing seg
faults, and when that problem is fixed it will cause memory leaks.  To fix
the memory leaks, you will need to ensure that all brigades are destroyed,
which means that the code will become more complex (if only to track when
brigades are created and destroyed).  The pools are used so that we don't
have to track resources this closely, that is, in fact, their main
benefit.

Again, please explain the problem that you are trying to solve in detail
instead of describing your solution.  It is just possible that other
people on this list will have an idea that will solve your problem while
not causing the side-effects that the current change will cause.

In the mean-time, I am -0. leaning towards -1 for this change, because of
the side-effects that it has already demonstrated and those that I have
detailed as lurking around the corner.  This change actually makes it
harder to 

Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb
On Fri, 20 Dec 2002 [EMAIL PROTECTED] wrote:

 On 20 Dec 2002, Brian Pane wrote:

  On Fri, 2002-12-20 at 08:57, [EMAIL PROTECTED] wrote:
   I am actually pretty sure that allocating brigades out of the
   bucket_allocator is a VERY big mistake.  In fact, I am close to asking
   that change to be backed out because it is too dangerous.
  
   When we first designed buckets and bucket brigades, we made one VERY clear
   distinction.  Bucket_brigades are allocated out of a pool, because that
   stops us from leaking memory.
 
  Allocating brigades from a pool is often a design mistake.
  Brigades tend to outlive the transactions that produce
  them (especially in apps like http servers), and having a
  brigade disappear as a side-effect of a transaction pool's
  cleanup will cause problems elsewhere in the code.

 U, I don't believe that it is possible for a brigade to outlive the
 transaction that created it, especially not when you look at how brigades
 are used in the web server.  Basically, a handler creates a brigade and
 passes it down the filter stack.  Each filter in the line is then allowed
 to either concatenate that brigade to it's own brigade or keep sending it
 down the stack.  At the bottom of the stack, the brigade must have been
 generated out of the connection pool, which by definition lives as long as
 the transaction that created it.

 Basically, if you received a brigade from a higher filter, you can only
 assume that it will survive a single trip down the filter stack.  That is
 why all filters must create their own brigade to save data between calls.

Just to be very clear about why this true.  Imagine a handler that does
this:

apr_brigade_create(bb, ...);
fill_brigade_with_data(bb, ...);
ap_pass_brigade(bb, ...)

ap_clean_brigade(bb, ...);
fill_brigade_with_more_data(bb, ...);
ap_pass_brigade(bb, ...);

This is perfectly legal for a handler to do.  However, this means that any
filter that wants to save the data in this brigade for use later, must
copy the buckets to their own brigade.  If they try to just save a pointer
to the brigade, they won't have the data they expect on the second call.
The brigade will still exist, but the data will be wrong.

For this reason, all filters must move the buckets to their own brigade,
which means that a brigade cannot outlive the transaction that it was
created for.  Essentially, the filters mitigate the brigade lifetimes.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb
On Fri, 20 Dec 2002, Aaron Bannert wrote:

 On Friday, December 20, 2002, at 01:26  PM, [EMAIL PROTECTED] wrote:
  The pools are used so that we don't
  have to track resources this closely, that is, in fact, their main
  benefit.

 They were also added for performance reasons, but I don't know if
 this was a side effect or the main reason. Delaying freelist management
 until after a response gives a huge performance gain from the
 perspective
 of an http client.

yes, performance was a reason in httpd, but in APR, it really came down to
convenience.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread rbb
On 20 Dec 2002, Brian Pane wrote:

 On Fri, 2002-12-20 at 13:50, [EMAIL PROTECTED] wrote:

   Basically, if you received a brigade from a higher filter, you can only
   assume that it will survive a single trip down the filter stack.

 Right.  And you also can't assume that the context that passed
 the brigade down the filter stack still exists by the time the
 brigade reaches the last filter.  It's a safe assumption in
 httpd-2.0, where the brigade is passed as a synchronous function
 call, but it's not valid for apps in general (potentially including
 future Apache versions) where the brigade may be queued up for
 asynchronous processing by an I/O completion thread.

Actually, it's not even a safe assumption in httpd-2.0.  You have data
from requests that originally lived in brigades allocated out of the
request pool moved to brigades allocated out of the connection pool
because the request pool is going away.  But that just proves the point,
the data must be moved from one brigade to another to change it's
lifetime.  That is part of the design of buckets and brigades.  It is why
the concat and split operations were written for maximum performance.

We are talking about a couple of pointer assignments for the concatenation
and a malloc and some pointer assignments for the split.

The change to stop allocating the brigade out of a pool is a mistake and I
am asking for it to be reverted, or for a clear description of the problem
that it is solving.  I have asked for this description three times now,
and I still don't understand what exactly you are trying to achieve and
why it can't be achieved with the old code.

Ryan




Re: [PATCH] Update to Brian's patch to allocate brigades out of thebucket allocator

2002-12-20 Thread Cliff Woolley
On Fri, 20 Dec 2002 [EMAIL PROTECTED] wrote:

 apr_brigade_create(bb, ...);
 fill_brigade_with_data(bb, ...);
 ap_pass_brigade(bb, ...)

 ap_clean_brigade(bb, ...);
 fill_brigade_with_more_data(bb, ...);
 ap_pass_brigade(bb, ...);

 This is perfectly legal for a handler to do.

No it isn't, because if ap_pass_brigade() makes it all the way down to the
core_output_filter, then after ap_pass_brigade_() returns, bb will have
been destroyed by the core_output_filter.  I'm not necessarily saying
that's *right*, just that that's the case today.

I still can't decide whom to agree with in this debate.  I'll have to
ponder it overnight before I throw in my opinion.  One thing is certain...
the current level of segfaultyness and memory leakage is bad news for
httpd 2.0.44.  Note that there are quite a few places in the httpd (last I
checked) where we abandon bucket brigades because it's always been the
case that they were cleaned up for us.  The fact that that's changing
doesn't sit well with me, because it makes me nervous about the stability
of the next httpd 2.0 release.  Can we at least agree to do one thing:
release 2.0.44 *without* Brian's change.  Then we can sort out the best
course of action without delaying the entire release process on the stable
branch.  It might well be that the best course of action is to just fix
the places in httpd that abuse brigades.  But as Bill pointed out, what
about third party modules?  Ay caramba.

+1 to moving brigades back into httpd, fwiw.

Thanks,
Cliff