/*
- * Copyright (C) 1996-2016 The Squid Software Foundation and contributors
+ * Copyright (C) 1996-2021 The Squid Software Foundation and contributors
*
* Squid software is distributed under GPLv2+ license and includes
* contributions from numerous individuals and organizations.
#include "base/RefCount.h"
#include "Debug.h"
#include "sbuf/DetailedStats.h"
-#include "sbuf/Exceptions.h"
-#include "sbuf/OutOfBoundsException.h"
#include "sbuf/SBuf.h"
#include "util.h"
#include <iostream>
#include <sstream>
-#ifdef VA_COPY
-#undef VA_COPY
-#endif
-#if defined HAVE_VA_COPY
-#define VA_COPY va_copy
-#elif defined HAVE___VA_COPY
-#define VA_COPY __va_copy
-#endif
-
InstanceIdDefinitions(SBuf, "SBuf");
SBufStats SBuf::stats;
const SBuf::size_type SBuf::npos;
const SBuf::size_type SBuf::maxSize;
-SBuf::SBuf()
- : store_(GetStorePrototype()), off_(0), len_(0)
+SBuf::SBuf() : store_(GetStorePrototype())
{
debugs(24, 8, id << " created");
++stats.alloc;
++stats.live;
}
-SBuf::SBuf(const std::string &s)
- : store_(GetStorePrototype()), off_(0), len_(0)
+SBuf::SBuf(const std::string &s) : store_(GetStorePrototype())
{
debugs(24, 8, id << " created from std::string");
lowAppend(s.data(),s.length());
++stats.live;
}
-SBuf::SBuf(const char *S, size_type n)
- : store_(GetStorePrototype()), off_(0), len_(0)
+SBuf::SBuf(const char *S, size_type n) : store_(GetStorePrototype())
{
append(S,n);
++stats.alloc;
++stats.live;
}
-SBuf::SBuf(const char *S)
- : store_(GetStorePrototype()), off_(0), len_(0)
+SBuf::SBuf(const char *S) : store_(GetStorePrototype())
{
append(S,npos);
++stats.alloc;
if (!mustRealloc && len_ >= req.maxCapacity)
return spaceSize(); // but we cannot reallocate
- const size_type newSpace = std::min(req.idealSpace, maxSize - len_);
+ const size_type desiredSpace = std::max(req.minSpace, req.idealSpace);
+ const size_type newSpace = std::min(desiredSpace, maxSize - len_);
reserveCapacity(std::min(len_ + newSpace, req.maxCapacity));
debugs(24, 7, id << " now: " << off_ << '+' << len_ << '+' << spaceSize() <<
'=' << store_->capacity);
return spaceSize(); // reallocated and probably reserved enough space
}
+char *
+SBuf::rawAppendStart(size_type anticipatedSize)
+{
+ char *space = rawSpace(anticipatedSize);
+ debugs(24, 8, id << " start appending up to " << anticipatedSize << " bytes");
+ return space;
+}
+
+void
+SBuf::rawAppendFinish(const char *start, size_type actualSize)
+{
+ Must(bufEnd() == start);
+ Must(store_->canAppend(off_ + len_, actualSize));
+ debugs(24, 8, id << " finish appending " << actualSize << " bytes");
+
+ size_type newSize = length() + actualSize;
+ Must3(newSize <= min(maxSize, store_->capacity-off_), "raw append fits", Here());
+ len_ = newSize;
+ store_->size = off_ + newSize;
+}
+
char *
SBuf::rawSpace(size_type minSpace)
{
debugs(24, 7, id << " not growing");
return bufEnd();
}
- // TODO: we may try to memmove before realloc'ing in order to avoid
- // one allocation operation, if we're the sole owners of a MemBlob.
- // Maybe some heuristic on off_ and length()?
cow(minSpace+length());
return bufEnd();
}
SBuf::Printf(const char *fmt, ...)
{
// with printf() the fmt or an arg might be a dangerous char*
- // NP: cant rely on vappendf() Locker because of clear()
+ // NP: can't rely on vappendf() Locker because of clear()
const Locker blobKeeper(this, buf());
va_list args;
size_type requiredSpaceEstimate = strlen(fmt)*2;
char *space = rawSpace(requiredSpaceEstimate);
-#ifdef VA_COPY
va_list ap;
- VA_COPY(ap, vargs);
+ va_copy(ap, vargs);
sz = vsnprintf(space, spaceSize(), fmt, ap);
va_end(ap);
-#else
- sz = vsnprintf(space, spaceSize(), fmt, vargs);
-#endif
+ Must3(sz >= 0, "vsnprintf() succeeds", Here());
/* check for possible overflow */
/* snprintf on Linux returns -1 on output errors, or the size
requiredSpaceEstimate = sz*2; // TODO: tune heuristics
space = rawSpace(requiredSpaceEstimate);
sz = vsnprintf(space, spaceSize(), fmt, vargs);
- if (sz < 0) // output error in vsnprintf
- throw TextException("output error in second-go vsnprintf",__FILE__,
- __LINE__);
+ Must3(sz >= 0, "vsnprintf() succeeds (with increased buffer space)", Here());
}
- if (sz < 0) // output error in either vsnprintf
- throw TextException("output error in vsnprintf",__FILE__, __LINE__);
-
// data was appended, update internal state
len_ += sz;
return buf();
}
-void
-SBuf::forceSize(size_type newSize)
-{
- debugs(24, 8, id << " force " << (newSize > length() ? "grow" : "shrink") << " to length=" << newSize);
-
- Must(store_->LockCount() == 1);
- if (newSize > min(maxSize,store_->capacity-off_))
- throw SBufTooBigException(__FILE__,__LINE__);
- len_ = newSize;
- store_->size = newSize;
-}
-
const char*
SBuf::c_str()
{
++stats.rawAccess;
/* null-terminate the current buffer, by hand-appending a \0 at its tail but
* without increasing its length. May COW, the side-effect is to guarantee that
- * the MemBlob's tail is availabe for us to use */
+ * the MemBlob's tail is available for us to use */
*rawSpace(1) = '\0';
++store_->size;
++stats.setChar;
", lastPossible=" << (void*) lastPossible );
tmp = static_cast<char *>(memchr(start, needleBegin, lastPossible-start));
if (tmp == NULL) {
- debugs(24, 8 , "First byte not found");
+ debugs(24, 8, "First byte not found");
return npos;
}
- // lastPossible guarrantees no out-of-bounds with memcmp()
+ // lastPossible guarantees no out-of-bounds with memcmp()
if (0 == memcmp(needle.buf(), tmp, needle.length())) {
debugs(24, 8, "Found at " << (tmp-buf()));
return (tmp-buf());
++stats.caseChange;
}
-/**
- * checks whether the requested 'pos' is within the bounds of the SBuf
- * \throw OutOfBoundsException if access is out of bounds
- */
-void
-SBuf::checkAccessBounds(size_type pos) const
-{
- if (pos >= length())
- throw OutOfBoundsException(*this, pos, __FILE__, __LINE__);
-}
-
/** re-allocate the backing store of the SBuf.
*
* If there are contents in the SBuf, they will be copied over.
* NO verifications are made on the size parameters, it's up to the caller to
* make sure that the new size is big enough to hold the copied contents.
* The re-allocated storage MAY be bigger than the requested size due to size-chunking
- * algorithms in MemBlock, it is guarranteed NOT to be smaller.
+ * algorithms in MemBlock, it is guaranteed NOT to be smaller.
*/
void
SBuf::reAlloc(size_type newsize)
{
debugs(24, 8, id << " new size: " << newsize);
- if (newsize > maxSize)
- throw SBufTooBigException(__FILE__, __LINE__);
+ Must(newsize <= maxSize);
+ // TODO: Consider realloc(3)ing in some cases instead.
MemBlob::Pointer newbuf = new MemBlob(newsize);
- if (length() > 0)
+ if (length() > 0) {
newbuf->append(buf(), length());
+ ++stats.cowAllocCopy;
+ } else {
+ ++stats.cowJustAlloc;
+ }
store_ = newbuf;
off_ = 0;
- ++stats.cowSlow;
debugs(24, 7, id << " new store capacity: " << store_->capacity);
}
if (newsize == npos || newsize < length())
newsize = length();
- if (store_->LockCount() == 1 && newsize == length()) {
- debugs(24, 8, id << " no cow needed");
- ++stats.cowFast;
- return;
+ if (store_->LockCount() == 1) {
+ // MemBlob::size reflects past owners. Refresh to maximize spaceSize().
+ store_->syncSize(off_ + length());
+
+ const auto availableSpace = spaceSize();
+ const auto neededSpace = newsize - length();
+ if (neededSpace <= availableSpace) {
+ debugs(24, 8, id << " no cow needed; have " << availableSpace);
+ ++stats.cowAvoided;
+ return;
+ }
+ // consume idle leading space if doing so avoids reallocation
+ // this case is typical for fill-consume-fill-consume-... I/O buffers
+ if (neededSpace <= availableSpace + off_) {
+ debugs(24, 8, id << " no cow after shifting " << off_ << " to get " << (availableSpace + off_));
+ store_->consume(off_);
+ off_ = 0;
+ ++stats.cowShift;
+ assert(neededSpace <= spaceSize());
+ return;
+ }
}
reAlloc(newsize);
}