[squid-dev] Squid: Small packets and low performance between squid and icap
Prashanth Prabhu
prashanth.prabhu at gmail.com
Wed Feb 17 00:55:37 UTC 2016
[+ squid-dev; bcc ssquid-users]
Hi Alex,
Sorry about the late reply.
Please see inline.
>> Here's the behavior I have seen: When the connection is set up, the
>> buffer gets a size of 16KB (default). Squid reads from the socket,
>> parses the data, and then sends it towards c-icap as appropriate. Now,
>> as part of parsing the data, the buffer is NUL-terminated via a call
>> to c_str(). This NUL-termination, however, is not accounted for by an
>> increase in the "offset" (off) in the underlying MemBlob, therefore,
>> the offset and size go out of sync.
>
> Just to avoid a misunderstanding:
>
> * MemBlob does not have an "offset".
Indeed. I was imprecise in my explanation -- the effect of drafting
email even as I was in the middle of investigating the code.
The c_str() code doesn't increment SBuf::len_. As a result the
MemBlob::canAppend() call, which takes in SBuf::off_ and SBuf::len_
doesn't match the MemBlob::size_, which was incremented as part of the
c_str() call.
> * A call to c_str() should not increase SBuf::len_ either because it
> does not add a new character to the SBuf object. That call just
> terminates the underlying buffer.
Well, without an increment of the MemBlob::size_ (or with an increment
to the SBuf::len_) this would have been OK. However, once that 'size_'
is incremented, we have the SBuf concept of how much buffer is used
being "out of sync" with the MemBlob's conception of buffer usage.
FWIW I don't quite understand how the NUL-char doesn't add a new
character to the SBuf object. Yes, it is terminating the string, but
in 'C' it is also a legit character. So, unclear what we are
attempting with this magic; or why. Seems to me, not incrementing
'len_' is a mistake.
> Single-owner optimizations aside (a known TODO), the above is the
> desired behavior according to the documented c_str() guarantees:
Can you please explain or point me to a document that has more info
about this "Single-owner" optimization?
>
>> * The returned value points to an internal location whose contents
>> * are guaranteed to remain unchanged only until the next call
>> * to a non-constant member function of the SBuf object.
>
> In other words, we cannot allow some _other_ SBuf object to overwrite
> our null-termination character in the MemBlob we share with that other SBuf.
>
> The high price for that strong guarantee is one of the reasons we should
> avoid c_str() calls in Squid code.
Note that the issue that I have explained is from a mostly stock squid
3.5.1 codebase. This isn't stemming from new c_str() calls added to
the codebase.
>> When canAppend() fails, a new
>> buffer is re-allocated. When this reallocation occurs, however, the
>> new size of the buffer is dependent on the size being reserved.
>
> If we are still talking about the I/O buffer (and not just some random
> SBuf string somewhere), then the I/O buffer _capacity_ should not shrink
> below a certain minimum, regardless of how much content the buffer has
> already stored. There should be some Squid code that ensures the minimum
> capacity of the I/O buffer used to read requests. If it is missing, it
> is a Squid bug.
It does shrink, as you can see from the debugs that I posted earlier.
>> As a temporary measure, I have an experimental change that checks
>> whether the body size is known and if known always reserves a large
>> enough size (currently 16K).
>
> It is difficult to discuss this without seeing your changes, but the
> reservation should probably be unconditional -- the I/O buffer capacity
> should always be at least 16KB (or whatever size we start with).
Yes, that would be another way of fixing this issue.
I have posted the current changes that are currently working for the
most part below. It uses current capacity size as a heuristic to bump
size up. The diff also has some previous fixes, that were pointed out
to me on the thread, embedded in it.
---
diff --git a/3rdparty/squid-3.5.1/src/MemBlob.h b/3rdparty/squid-3.5.1/src/Mem
Blob.h
index b96330e..d265576 100644
--- a/3rdparty/squid-3.5.1/src/MemBlob.h
+++ b/3rdparty/squid-3.5.1/src/MemBlob.h
@@ -94,6 +94,8 @@ public:
/// extends the available space to the entire allocated blob
void clear() { size = 0; }
+ size_type currentCapacity() const { return (capacity); };
+
/// dump debugging information
std::ostream & dump(std::ostream &os) const;
diff --git a/3rdparty/squid-3.5.1/src/SBuf.cc b/3rdparty/squid-3.5.1/src/SBuf.
cc
index 53221d6..91886a0 100644
--- a/3rdparty/squid-3.5.1/src/SBuf.cc
+++ b/3rdparty/squid-3.5.1/src/SBuf.cc
@@ -76,7 +76,7 @@ SBufStats::operator +=(const SBufStats& ss)
SBuf::SBuf()
: store_(GetStorePrototype()), off_(0), len_(0)
{
- debugs(24, 8, id << " created");
+ debugs(24, 8, id << " created, size=" << spaceSize());
++stats.alloc;
++stats.live;
}
@@ -171,6 +171,7 @@ SBuf::rawSpace(size_type minSpace)
// the store knows the last-used portion. If
// it's available, we're effectively claiming ownership
// of it. If it's not, we need to go away (realloc)
+ debugs(24, 7, "off_=" << off_ << "; len_=" << len_ << "; size="
<< store_->size);
if (store_->canAppend(off_+len_, minSpace)) {
debugs(24, 7, "not growing");
return bufEnd();
diff --git a/3rdparty/squid-3.5.1/src/SBuf.h b/3rdparty/squid-3.5.1/src/SBuf.h
index ef77733..5bb3ef4 100644
--- a/3rdparty/squid-3.5.1/src/SBuf.h
+++ b/3rdparty/squid-3.5.1/src/SBuf.h
@@ -541,6 +541,8 @@ public:
/// std::string export function
std::string toStdString() const { return std::string(buf(),length()); }
+ size_type currentCapacity() const { return (store_->currentCapacity()); }
+
// TODO: possibly implement erase() similar to std::string's erase
// TODO: possibly implement a replace() call
private:
diff --git a/3rdparty/squid-3.5.1/src/client_side.cc
b/3rdparty/squid-3.5.1/src/client_side.cc
index f2d0ce0..e191550 100644
--- a/3rdparty/squid-3.5.1/src/client_side.cc
+++ b/3rdparty/squid-3.5.1/src/client_side.cc
@@ -2347,6 +2348,9 @@ ConnStateData::In::maybeMakeSpaceAvailable()
buf.reserveCapacity(wantCapacity);
}
debugs(33, 2, "growing request buffer: available=" <<
buf.spaceSize() << " used=" << buf.length());
+ } else if (buf.spaceSize() < buf.currentCapacity() / 2) {
+ debugs(33, 2, "growing request buffer: available=" <<
buf.spaceSize() << " used=" << buf.length());
+ buf.reserveCapacity(CLIENT_REQ_BUF_SZ * 4);
}
return (buf.spaceSize() >= 2);
}
@@ -3244,6 +3248,7 @@ ConnStateData::clientReadRequest(const CommIoCbParams &io)
* Plus, it breaks our lame *HalfClosed() detection
*/
+ in.maybeMakeSpaceAvailable();
CommIoCbParams rd(this); // will be expanded with ReadNow results
rd.conn = io.conn;
switch (Comm::ReadNow(rd, in.buf)) {
@@ -3354,11 +3359,13 @@ ConnStateData::handleRequestBodyData()
return false;
}
} else { // identity encoding
- debugs(33,5, HERE << "handling plain request body for " <<
clientConnection);
- putSize = bodyPipe->putMoreData(in.buf.c_str(), in.buf.length());
- if (!bodyPipe->mayNeedMoreData()) {
- // BodyPipe will clear us automagically when we produced everything
- bodyPipe = NULL;
+ debugs(33,5, HERE << "handling plain request body for " <<
clientConnection << "; len=" << in.buf.length());
+ if (in.buf.length() > 0) {
+ putSize = bodyPipe->putMoreData(in.buf.c_str(), in.buf.length());
+ if (!bodyPipe->mayNeedMoreData()) {
+ // BodyPipe will clear us automagically when we
produced everything
+ bodyPipe = NULL;
+ }
}
}
@@ -3537,9 +3544,6 @@ ConnStateData::start()
BodyProducer::start();
HttpControlMsgSink::start();
- // ensure a buffer is present for this connection
- in.maybeMakeSpaceAvailable();
-
if (port->disable_pmtu_discovery != DISABLE_PMTU_OFF &&
(transparent() || port->disable_pmtu_discovery ==
DISABLE_PMTU_ALWAYS)) {
#if defined(IP_MTU_DISCOVER) && defined(IP_PMTUDISC_DONT)
---
Regards.
Prashanth
On 9 February 2016 at 13:54, Alex Rousskov
<rousskov at measurement-factory.com> wrote:
> [this should be on squid-dev instead]
>
> On 02/09/2016 01:20 PM, Prashanth Prabhu wrote:
>
>> Here's the behavior I have seen: When the connection is set up, the
>> buffer gets a size of 16KB (default). Squid reads from the socket,
>> parses the data, and then sends it towards c-icap as appropriate. Now,
>> as part of parsing the data, the buffer is NUL-terminated via a call
>> to c_str(). This NUL-termination, however, is not accounted for by an
>> increase in the "offset" (off) in the underlying MemBlob, therefore,
>> the offset and size go out of sync.
>
> Just to avoid a misunderstanding:
>
> * MemBlob does not have an "offset".
>
> * SBuf::off_ should not change when we are adding characters to SBuf
> because it is the start of the buffer, not the end of it.
>
> * A call to c_str() should not increase SBuf::len_ either because it
> does not add a new character to the SBuf object. That call just
> terminates the underlying buffer.
>
> Based on your comments below, I think I know what you mean by "go out of
> sync", but everything is as "in sync" as it can be when one adds
> termination characters that are not really there from SBuf::length()
> point of view. The bug is elsewhere.
>
>
>> MemBlob::canAppend() failing because
>> MemBlob::isAppendOffset() fails -- the 'off' and 'size' are not the
>> same due to the above c_str() call.
>
> Single-owner optimizations aside (a known TODO), the above is the
> desired behavior according to the documented c_str() guarantees:
>
>> * The returned value points to an internal location whose contents
>> * are guaranteed to remain unchanged only until the next call
>> * to a non-constant member function of the SBuf object.
>
> In other words, we cannot allow some _other_ SBuf object to overwrite
> our null-termination character in the MemBlob we share with that other SBuf.
>
> The high price for that strong guarantee is one of the reasons we should
> avoid c_str() calls in Squid code.
>
>
>> When canAppend() fails, a new
>> buffer is re-allocated. When this reallocation occurs, however, the
>> new size of the buffer is dependent on the size being reserved.
>
> If we are still talking about the I/O buffer (and not just some random
> SBuf string somewhere), then the I/O buffer _capacity_ should not shrink
> below a certain minimum, regardless of how much content the buffer has
> already stored. There should be some Squid code that ensures the minimum
> capacity of the I/O buffer used to read requests. If it is missing, it
> is a Squid bug.
>
>
>> As a temporary measure, I have an experimental change that checks
>> whether the body size is known and if known always reserves a large
>> enough size (currently 16K).
>
> It is difficult to discuss this without seeing your changes, but the
> reservation should probably be unconditional -- the I/O buffer capacity
> should always be at least 16KB (or whatever size we start with).
>
>
> HTH,
>
> Alex.
>
More information about the squid-dev
mailing list