Commit a247c8d3 authored by Jordan DeLong's avatar Jordan DeLong

Remove some files that should have been deleted in earlier commits

Summary: Our script wasn't picking up removed files.
parent 8336eb31
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_HISTOGRAM_INL_H_
#define FOLLY_HISTOGRAM_INL_H_
#include "folly/Conv.h"
#include <glog/logging.h>
namespace folly {
namespace detail {
template <typename T, typename BucketT>
HistogramBuckets<T, BucketT>::HistogramBuckets(ValueType bucketSize,
ValueType min,
ValueType max,
const BucketType& defaultBucket)
: bucketSize_(bucketSize),
min_(min),
max_(max) {
CHECK_GT(bucketSize_, ValueType(0));
CHECK_LT(min_, max_);
unsigned int numBuckets = (max - min) / bucketSize;
// Round up if the bucket size does not fit evenly
if (numBuckets * bucketSize < max - min) {
++numBuckets;
}
// Add 2 for the extra 'below min' and 'above max' buckets
numBuckets += 2;
buckets_.assign(numBuckets, defaultBucket);
}
template <typename T, typename BucketType>
unsigned int HistogramBuckets<T, BucketType>::getBucketIdx(
ValueType value) const {
if (value < min_) {
return 0;
} else if (value >= max_) {
return buckets_.size() - 1;
} else {
// the 1 is the below_min bucket
return ((value - min_) / bucketSize_) + 1;
}
}
template <typename T, typename BucketType>
template <typename CountFn>
unsigned int HistogramBuckets<T, BucketType>::getPercentileBucketIdx(
double pct,
CountFn countFromBucket,
double* lowPct, double* highPct) const {
CHECK_GE(pct, 0.0);
CHECK_LE(pct, 1.0);
unsigned int numBuckets = buckets_.size();
// Compute the counts in each bucket
std::vector<uint64_t> counts(numBuckets);
uint64_t totalCount = 0;
for (unsigned int n = 0; n < numBuckets; ++n) {
uint64_t bucketCount =
countFromBucket(const_cast<const BucketType&>(buckets_[n]));
counts[n] = bucketCount;
totalCount += bucketCount;
}
// If there are no elements, just return the lowest bucket.
// Note that we return bucket 1, which is the first bucket in the
// histogram range; bucket 0 is for all values below min_.
if (totalCount == 0) {
// Set lowPct and highPct both to 0.
// getPercentileEstimate() will recognize this to mean that the histogram
// is empty.
if (lowPct) {
*lowPct = 0.0;
}
if (highPct) {
*highPct = 0.0;
}
return 1;
}
// Loop through all the buckets, keeping track of each bucket's
// percentile range: [0,10], [10,17], [17,45], etc. When we find a range
// that includes our desired percentile, we return that bucket index.
double prevPct = 0.0;
double curPct = 0.0;
uint64_t curCount = 0;
unsigned int idx;
for (idx = 0; idx < numBuckets; ++idx) {
if (counts[idx] == 0) {
// skip empty buckets
continue;
}
prevPct = curPct;
curCount += counts[idx];
curPct = static_cast<double>(curCount) / totalCount;
if (pct <= curPct) {
// This is the desired bucket
break;
}
}
if (lowPct) {
*lowPct = prevPct;
}
if (highPct) {
*highPct = curPct;
}
return idx;
}
template <typename T, typename BucketType>
template <typename CountFn, typename AvgFn>
T HistogramBuckets<T, BucketType>::getPercentileEstimate(
double pct,
CountFn countFromBucket,
AvgFn avgFromBucket) const {
// Find the bucket where this percentile falls
double lowPct;
double highPct;
unsigned int bucketIdx = getPercentileBucketIdx(pct, countFromBucket,
&lowPct, &highPct);
if (lowPct == 0.0 && highPct == 0.0) {
// Invalid range -- the buckets must all be empty
// Return the default value for ValueType.
return ValueType();
}
if (lowPct == highPct) {
// Unlikely to have exact equality,
// but just return the bucket average in this case.
// We handle this here to avoid division by 0 below.
return avgFromBucket(buckets_[bucketIdx]);
}
CHECK_GE(pct, lowPct);
CHECK_LE(pct, highPct);
CHECK_LT(lowPct, highPct);
// Compute information about this bucket
ValueType avg = avgFromBucket(buckets_[bucketIdx]);
ValueType low;
ValueType high;
if (bucketIdx == 0) {
if (avg > min_) {
// This normally shouldn't happen. This bucket is only supposed to track
// values less than min_. Most likely this means that integer overflow
// occurred, and the code in avgFromBucket() returned a huge value
// instead of a small one. Just return the minimum possible value for
// now.
//
// (Note that if the counter keeps being decremented, eventually it will
// wrap and become small enough that we won't detect this any more, and
// we will return bogus information.)
LOG(ERROR) << "invalid average value in histogram minimum bucket: " <<
avg << " > " << min_ << ": possible integer overflow?";
return getBucketMin(bucketIdx);
}
// For the below-min bucket, just assume the lowest value ever seen is
// twice as far away from min_ as avg.
high = min_;
low = high - (2 * (high - avg));
// Adjust low in case it wrapped
if (low > avg) {
low = std::numeric_limits<ValueType>::min();
}
} else if (bucketIdx == buckets_.size() - 1) {
if (avg < max_) {
// Most likely this means integer overflow occurred. See the comments
// above in the minimum case.
LOG(ERROR) << "invalid average value in histogram maximum bucket: " <<
avg << " < " << max_ << ": possible integer overflow?";
return getBucketMax(bucketIdx);
}
// Similarly for the above-max bucket, assume the highest value ever seen
// is twice as far away from max_ as avg.
low = max_;
high = low + (2 * (avg - low));
// Adjust high in case it wrapped
if (high < avg) {
high = std::numeric_limits<ValueType>::max();
}
} else {
low = getBucketMin(bucketIdx);
high = getBucketMax(bucketIdx);
if (avg < low || avg > high) {
// Most likely this means an integer overflow occurred.
// See the comments above. Return the midpoint between low and high
// as a best guess, since avg is meaningless.
LOG(ERROR) << "invalid average value in histogram bucket: " <<
avg << " not in range [" << low << ", " << high <<
"]: possible integer overflow?";
return (low + high) / 2;
}
}
// Since we know the average value in this bucket, we can do slightly better
// than just assuming the data points in this bucket are uniformly
// distributed between low and high.
//
// Assume that the median value in this bucket is the same as the average
// value.
double medianPct = (lowPct + highPct) / 2.0;
if (pct < medianPct) {
// Assume that the data points lower than the median of this bucket
// are uniformly distributed between low and avg
double pctThroughSection = (pct - lowPct) / (medianPct - lowPct);
return low + ((avg - low) * pctThroughSection);
} else {
// Assume that the data points greater than the median of this bucket
// are uniformly distributed between avg and high
double pctThroughSection = (pct - medianPct) / (highPct - medianPct);
return avg + ((high - avg) * pctThroughSection);
}
}
} // detail
template <typename T>
std::string Histogram<T>::debugString() const {
std::string ret = folly::to<std::string>(
"num buckets: ", buckets_.getNumBuckets(),
", bucketSize: ", buckets_.getBucketSize(),
", min: ", buckets_.getMin(), ", max: ", buckets_.getMax(), "\n");
for (unsigned int i = 0; i < buckets_.getNumBuckets(); ++i) {
folly::toAppend(" ", buckets_.getBucketMin(i), ": ",
buckets_.getByIndex(i).count, "\n",
&ret);
}
return ret;
}
template <typename T>
void Histogram<T>::toTSV(std::ostream& out, bool skipEmptyBuckets) const {
for (unsigned int i = 0; i < buckets_.getNumBuckets(); ++i) {
// Do not output empty buckets in order to reduce data file size.
if (skipEmptyBuckets && getBucketByIndex(i).count == 0) {
continue;
}
const auto& bucket = getBucketByIndex(i);
out << getBucketMin(i) << '\t' << getBucketMax(i) << '\t'
<< bucket.count << '\t' << bucket.sum << '\n';
}
}
} // folly
#endif // FOLLY_HISTOGRAM_INL_H_
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_HISTOGRAM_H_
#define FOLLY_HISTOGRAM_H_
#include <cstddef>
#include <limits>
#include <ostream>
#include <string>
#include <vector>
#include <stdexcept>
#include "folly/detail/Stats.h"
namespace folly {
namespace detail {
/*
* A helper class to manage a set of histogram buckets.
*/
template <typename T, typename BucketT>
class HistogramBuckets {
public:
typedef T ValueType;
typedef BucketT BucketType;
/*
* Create a set of histogram buckets.
*
* One bucket will be created for each bucketSize interval of values within
* the specified range. Additionally, one bucket will be created to track
* all values that fall below the specified minimum, and one bucket will be
* created for all values above the specified maximum.
*
* If (max - min) is not a multiple of bucketSize, the last bucket will cover
* a smaller range of values than the other buckets.
*
* (max - min) must be larger than or equal to bucketSize.
*/
HistogramBuckets(ValueType bucketSize, ValueType min, ValueType max,
const BucketType& defaultBucket);
/* Returns the bucket size of each bucket in the histogram. */
ValueType getBucketSize() const {
return bucketSize_;
}
/* Returns the min value at which bucketing begins. */
ValueType getMin() const {
return min_;
}
/* Returns the max value at which bucketing ends. */
ValueType getMax() const {
return max_;
}
/*
* Returns the number of buckets.
*
* This includes the total number of buckets for the [min, max) range,
* plus 2 extra buckets, one for handling values less than min, and one for
* values greater than max.
*/
unsigned int getNumBuckets() const {
return buckets_.size();
}
/* Returns the bucket index into which the given value would fall. */
unsigned int getBucketIdx(ValueType value) const;
/* Returns the bucket for the specified value */
BucketType& getByValue(ValueType value) {
return buckets_[getBucketIdx(value)];
}
/* Returns the bucket for the specified value */
const BucketType& getByValue(ValueType value) const {
return buckets_[getBucketIdx(value)];
}
/*
* Returns the bucket at the specified index.
*
* Note that index 0 is the bucket for all values less than the specified
* minimum. Index 1 is the first bucket in the specified bucket range.
*/
BucketType& getByIndex(unsigned int idx) {
return buckets_[idx];
}
/* Returns the bucket at the specified index. */
const BucketType& getByIndex(unsigned int idx) const {
return buckets_[idx];
}
/*
* Returns the minimum threshold for the bucket at the given index.
*
* The bucket at the specified index will store values in the range
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
* max is smaller than bucketMin + bucketSize.
*/
ValueType getBucketMin(unsigned int idx) const {
if (idx == 0) {
return std::numeric_limits<ValueType>::min();
}
if (idx == buckets_.size() - 1) {
return max_;
}
return min_ + ((idx - 1) * bucketSize_);
}
/*
* Returns the maximum threshold for the bucket at the given index.
*
* The bucket at the specified index will store values in the range
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
* max is smaller than bucketMin + bucketSize.
*/
ValueType getBucketMax(unsigned int idx) const {
if (idx == buckets_.size() - 1) {
return std::numeric_limits<ValueType>::max();
}
return min_ + (idx * bucketSize_);
}
/**
* Determine which bucket the specified percentile falls into.
*
* Looks for the bucket that contains the Nth percentile data point.
*
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
* @param countFn A function that takes a const BucketType&, and returns the
* number of values in that bucket.
* @param lowPct The lowest percentile stored in the selected bucket will be
* returned via this parameter.
* @param highPct The highest percentile stored in the selected bucket will
* be returned via this parameter.
*
* @return Returns the index of the bucket that contains the Nth percentile
* data point.
*/
template <typename CountFn>
unsigned int getPercentileBucketIdx(double pct,
CountFn countFromBucket,
double* lowPct = NULL,
double* highPct = NULL) const;
/**
* Estimate the value at the specified percentile.
*
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
* @param countFn A function that takes a const BucketType&, and returns the
* number of values in that bucket.
* @param avgFn A function that takes a const BucketType&, and returns the
* average of all the values in that bucket.
*
* @return Returns an estimate for N, where N is the number where exactly pct
* percentage of the data points in the histogram are less than N.
*/
template <typename CountFn, typename AvgFn>
ValueType getPercentileEstimate(double pct,
CountFn countFromBucket,
AvgFn avgFromBucket) const;
/*
* Iterator access to the buckets.
*
* Note that the first bucket is for all values less than min, and the last
* bucket is for all values greater than max. The buckets tracking values in
* the [min, max) actually start at the second bucket.
*/
typename std::vector<BucketType>::const_iterator begin() const {
return buckets_.begin();
}
typename std::vector<BucketType>::iterator begin() {
return buckets_.begin();
}
typename std::vector<BucketType>::const_iterator end() const {
return buckets_.end();
}
typename std::vector<BucketType>::iterator end() {
return buckets_.end();
}
private:
const ValueType bucketSize_;
const ValueType min_;
const ValueType max_;
std::vector<BucketType> buckets_;
};
} // detail
/*
* A basic histogram class.
*
* Groups data points into equally-sized buckets, and stores the overall sum of
* the data points in each bucket, as well as the number of data points in the
* bucket.
*
* The caller must specify the minimum and maximum data points to expect ahead
* of time, as well as the bucket width.
*/
template <typename T>
class Histogram {
public:
typedef T ValueType;
typedef detail::Bucket<T> Bucket;
Histogram(ValueType bucketSize, ValueType min, ValueType max)
: buckets_(bucketSize, min, max, Bucket()) {}
/* Add a data point to the histogram */
void addValue(ValueType value) {
Bucket& bucket = buckets_.getByValue(value);
// TODO: It would be nice to handle overflow here.
bucket.sum += value;
bucket.count += 1;
}
/*
* Remove a data point to the histogram
*
* Note that this method does not actually verify that this exact data point
* had previously been added to the histogram; it merely subtracts the
* requested value from the appropriate bucket's sum.
*/
void removeValue(ValueType value) {
Bucket& bucket = buckets_.getByValue(value);
// TODO: It would be nice to handle overflow here.
bucket.sum -= value;
bucket.count -= 1;
}
/* Remove all data points from the histogram */
void clear() {
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
buckets_.getByIndex(i).clear();
}
}
/* Merge two histogram data together */
void merge(Histogram &hist) {
// the two histogram bucket definitions must match to support
// a merge.
if (getBucketSize() != hist.getBucketSize() ||
getMin() != hist.getMin() ||
getMax() != hist.getMax() ||
getNumBuckets() != hist.getNumBuckets() ) {
throw std::invalid_argument("Cannot merge from input histogram.");
}
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
buckets_.getByIndex(i) += hist.buckets_.getByIndex(i);
}
}
/* Copy bucket values from another histogram */
void copy(Histogram &hist) {
// the two histogram bucket definition must match
if (getBucketSize() != hist.getBucketSize() ||
getMin() != hist.getMin() ||
getMax() != hist.getMax() ||
getNumBuckets() != hist.getNumBuckets() ) {
throw std::invalid_argument("Cannot copy from input histogram.");
}
for (int i = 0; i < buckets_.getNumBuckets(); i++) {
buckets_.getByIndex(i) = hist.buckets_.getByIndex(i);
}
}
/* Returns the bucket size of each bucket in the histogram. */
ValueType getBucketSize() const {
return buckets_.getBucketSize();
}
/* Returns the min value at which bucketing begins. */
ValueType getMin() const {
return buckets_.getMin();
}
/* Returns the max value at which bucketing ends. */
ValueType getMax() const {
return buckets_.getMax();
}
/* Returns the number of buckets */
unsigned int getNumBuckets() const {
return buckets_.getNumBuckets();
}
/* Returns the specified bucket (for reading only!) */
const Bucket& getBucketByIndex(int idx) const {
return buckets_.getByIndex(idx);
}
/*
* Returns the minimum threshold for the bucket at the given index.
*
* The bucket at the specified index will store values in the range
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
* max is smaller than bucketMin + bucketSize.
*/
ValueType getBucketMin(unsigned int idx) const {
return buckets_.getBucketMin(idx);
}
/*
* Returns the maximum threshold for the bucket at the given index.
*
* The bucket at the specified index will store values in the range
* [bucketMin, bucketMin + bucketSize), or [bucketMin, max), if the overall
* max is smaller than bucketMin + bucketSize.
*/
ValueType getBucketMax(unsigned int idx) const {
return buckets_.getBucketMax(idx);
}
/*
* Get the bucket that the specified percentile falls into
*
* The lowest and highest percentile data points in returned bucket will be
* returned in the lowPct and highPct arguments, if they are non-NULL.
*/
unsigned int getPercentileBucketIdx(double pct,
double* lowPct = NULL,
double* highPct = NULL) const {
// We unfortunately can't use lambdas here yet;
// Some users of this code are still built with gcc-4.4.
CountFromBucket countFn;
return buckets_.getPercentileBucketIdx(pct, countFn, lowPct, highPct);
}
/**
* Estimate the value at the specified percentile.
*
* @param pct The desired percentile to find, as a value from 0.0 to 1.0.
*
* @return Returns an estimate for N, where N is the number where exactly pct
* percentage of the data points in the histogram are less than N.
*/
ValueType getPercentileEstimate(double pct) const {
CountFromBucket countFn;
AvgFromBucket avgFn;
return buckets_.getPercentileEstimate(pct, countFn, avgFn);
}
/*
* Get a human-readable string describing the histogram contents
*/
std::string debugString() const;
/*
* Write the histogram contents in tab-separated values (TSV) format.
* Format is "min max count sum".
*/
void toTSV(std::ostream& out, bool skipEmptyBuckets = true) const;
private:
struct CountFromBucket {
uint64_t operator()(const Bucket& bucket) const {
return bucket.count;
}
};
struct AvgFromBucket {
ValueType operator()(const Bucket& bucket) const {
if (bucket.count == 0) {
return ValueType(0);
}
// Cast bucket.count to a signed integer type. This ensures that we
// perform division properly here: If bucket.sum is a signed integer
// type but we divide by an unsigned number, unsigned division will be
// performed and bucket.sum will be converted to unsigned first.
// If bucket.sum is unsigned, the code will still do unsigned division
// correctly.
//
// The only downside is if bucket.count is large enough to be negative
// when treated as signed. That should be extremely unlikely, though.
return bucket.sum / static_cast<int64_t>(bucket.count);
}
};
detail::HistogramBuckets<ValueType, Bucket> buckets_;
};
} // folly
#include "folly/Histogram-inl.h"
#endif // FOLLY_HISTOGRAM_H_
Apache License
Version 2.0, January 2004
http://www.apache.org/licenses/
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
1. Definitions.
"License" shall mean the terms and conditions for use, reproduction,
and distribution as defined by Sections 1 through 9 of this document.
"Licensor" shall mean the copyright owner or entity authorized by
the copyright owner that is granting the License.
"Legal Entity" shall mean the union of the acting entity and all
other entities that control, are controlled by, or are under common
control with that entity. For the purposes of this definition,
"control" means (i) the power, direct or indirect, to cause the
direction or management of such entity, whether by contract or
otherwise, or (ii) ownership of fifty percent (50%) or more of the
outstanding shares, or (iii) beneficial ownership of such entity.
"You" (or "Your") shall mean an individual or Legal Entity
exercising permissions granted by this License.
"Source" form shall mean the preferred form for making modifications,
including but not limited to software source code, documentation
source, and configuration files.
"Object" form shall mean any form resulting from mechanical
transformation or translation of a Source form, including but
not limited to compiled object code, generated documentation,
and conversions to other media types.
"Work" shall mean the work of authorship, whether in Source or
Object form, made available under the License, as indicated by a
copyright notice that is included in or attached to the work
(an example is provided in the Appendix below).
"Derivative Works" shall mean any work, whether in Source or Object
form, that is based on (or derived from) the Work and for which the
editorial revisions, annotations, elaborations, or other modifications
represent, as a whole, an original work of authorship. For the purposes
of this License, Derivative Works shall not include works that remain
separable from, or merely link (or bind by name) to the interfaces of,
the Work and Derivative Works thereof.
"Contribution" shall mean any work of authorship, including
the original version of the Work and any modifications or additions
to that Work or Derivative Works thereof, that is intentionally
submitted to Licensor for inclusion in the Work by the copyright owner
or by an individual or Legal Entity authorized to submit on behalf of
the copyright owner. For the purposes of this definition, "submitted"
means any form of electronic, verbal, or written communication sent
to the Licensor or its representatives, including but not limited to
communication on electronic mailing lists, source code control systems,
and issue tracking systems that are managed by, or on behalf of, the
Licensor for the purpose of discussing and improving the Work, but
excluding communication that is conspicuously marked or otherwise
designated in writing by the copyright owner as "Not a Contribution."
"Contributor" shall mean Licensor and any individual or Legal Entity
on behalf of whom a Contribution has been received by Licensor and
subsequently incorporated within the Work.
2. Grant of Copyright License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
copyright license to reproduce, prepare Derivative Works of,
publicly display, publicly perform, sublicense, and distribute the
Work and such Derivative Works in Source or Object form.
3. Grant of Patent License. Subject to the terms and conditions of
this License, each Contributor hereby grants to You a perpetual,
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
(except as stated in this section) patent license to make, have made,
use, offer to sell, sell, import, and otherwise transfer the Work,
where such license applies only to those patent claims licensable
by such Contributor that are necessarily infringed by their
Contribution(s) alone or by combination of their Contribution(s)
with the Work to which such Contribution(s) was submitted. If You
institute patent litigation against any entity (including a
cross-claim or counterclaim in a lawsuit) alleging that the Work
or a Contribution incorporated within the Work constitutes direct
or contributory patent infringement, then any patent licenses
granted to You under this License for that Work shall terminate
as of the date such litigation is filed.
4. Redistribution. You may reproduce and distribute copies of the
Work or Derivative Works thereof in any medium, with or without
modifications, and in Source or Object form, provided that You
meet the following conditions:
(a) You must give any other recipients of the Work or
Derivative Works a copy of this License; and
(b) You must cause any modified files to carry prominent notices
stating that You changed the files; and
(c) You must retain, in the Source form of any Derivative Works
that You distribute, all copyright, patent, trademark, and
attribution notices from the Source form of the Work,
excluding those notices that do not pertain to any part of
the Derivative Works; and
(d) If the Work includes a "NOTICE" text file as part of its
distribution, then any Derivative Works that You distribute must
include a readable copy of the attribution notices contained
within such NOTICE file, excluding those notices that do not
pertain to any part of the Derivative Works, in at least one
of the following places: within a NOTICE text file distributed
as part of the Derivative Works; within the Source form or
documentation, if provided along with the Derivative Works; or,
within a display generated by the Derivative Works, if and
wherever such third-party notices normally appear. The contents
of the NOTICE file are for informational purposes only and
do not modify the License. You may add Your own attribution
notices within Derivative Works that You distribute, alongside
or as an addendum to the NOTICE text from the Work, provided
that such additional attribution notices cannot be construed
as modifying the License.
You may add Your own copyright statement to Your modifications and
may provide additional or different license terms and conditions
for use, reproduction, or distribution of Your modifications, or
for any such Derivative Works as a whole, provided Your use,
reproduction, and distribution of the Work otherwise complies with
the conditions stated in this License.
5. Submission of Contributions. Unless You explicitly state otherwise,
any Contribution intentionally submitted for inclusion in the Work
by You to the Licensor shall be under the terms and conditions of
this License, without any additional terms or conditions.
Notwithstanding the above, nothing herein shall supersede or modify
the terms of any separate license agreement you may have executed
with Licensor regarding such Contributions.
6. Trademarks. This License does not grant permission to use the trade
names, trademarks, service marks, or product names of the Licensor,
except as required for reasonable and customary use in describing the
origin of the Work and reproducing the content of the NOTICE file.
7. Disclaimer of Warranty. Unless required by applicable law or
agreed to in writing, Licensor provides the Work (and each
Contributor provides its Contributions) on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
implied, including, without limitation, any warranties or conditions
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
PARTICULAR PURPOSE. You are solely responsible for determining the
appropriateness of using or redistributing the Work and assume any
risks associated with Your exercise of permissions under this License.
8. Limitation of Liability. In no event and under no legal theory,
whether in tort (including negligence), contract, or otherwise,
unless required by applicable law (such as deliberate and grossly
negligent acts) or agreed to in writing, shall any Contributor be
liable to You for damages, including any direct, indirect, special,
incidental, or consequential damages of any character arising as a
result of this License or out of the use or inability to use the
Work (including but not limited to damages for loss of goodwill,
work stoppage, computer failure or malfunction, or any and all
other commercial damages or losses), even if such Contributor
has been advised of the possibility of such damages.
9. Accepting Warranty or Additional Liability. While redistributing
the Work or Derivative Works thereof, You may choose to offer,
and charge a fee for, acceptance of support, warranty, indemnity,
or other liability obligations and/or rights consistent with this
License. However, in accepting such obligations, You may act only
on Your own behalf and on Your sole responsibility, not on behalf
of any other Contributor, and only if You agree to indemnify,
defend, and hold each Contributor harmless for any liability
incurred by, or claims asserted against, such Contributor by reason
of your accepting any such warranty or additional liability.
END OF TERMS AND CONDITIONS
Folly: Facebook Open-source LibrarY
-----------------------------------
Folly is an open-source C++ library developed and used at Facebook.
Dependencies
------------
- double-conversion (http://code.google.com/p/double-conversion/)
By default, the build tooling for double-conversion does not build
any libraries, which folly requires. To build the necessary libraries
copy folly/SConstruct.double-conversion to your double-conversion
source directory before building:
[double-conversion/] scons -f SConstruct.double-conversion
Then set CPPFLAGS/LDFLAGS so that folly can find your double-conversion
build:
[folly/] LDFLAGS=-L<double-conversion>/ CPPFLAGS=-I<double-conversion>/src/
configure ...
- googletest (Google C++ Testing Framework)
Grab gtest 1.6.0 from:
http://googletest.googlecode.com/files/gtest-1.6.0.zip
Unzip it inside of the test/ subdirectory.
- additional platform specific dependencies:
Ubuntu 12.04 64-bit
- g++
- automake
- autoconf
- libtool
- libboost1.46-all-dev
- libgoogle-glog-dev
This package has been removed from 12.04 -- use the one from 11.10
- gflags (packages need to be downloaded from below)
http://gflags.googlecode.com/files/libgflags-dev_2.0-1_amd64.deb
http://gflags.googlecode.com/files/libgflags0_2.0-1_amd64.deb
- scons (for double-conversion)
Fedora 17 64-bit
- gcc
- gcc-c++
- autoconf
- automake
- boost-devel
- libtool
- glog-devel
- gflags-devel
- scons (for double-conversion)
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// Spooky Hash
// A 128-bit noncryptographic hash, for checksums and table lookup
// By Bob Jenkins. Public domain.
// Oct 31 2010: published framework, disclaimer ShortHash isn't right
// Nov 7 2010: disabled ShortHash
// Oct 31 2011: replace End, ShortMix, ShortEnd, enable ShortHash again
// April 10 2012: buffer overflow on platforms without unaligned reads
// July 12 2012: was passing out variables in final to in/out in short
// July 30 2012: I reintroduced the buffer overflow
#include "folly/SpookyHash.h"
#include <cstring>
#define ALLOW_UNALIGNED_READS 1
namespace folly {
namespace hash {
//
// short hash ... it could be used on any message,
// but it's used by Spooky just for short messages.
//
void SpookyHash::Short(
const void *message,
size_t length,
uint64_t *hash1,
uint64_t *hash2)
{
uint64_t buf[2*sc_numVars];
union
{
const uint8_t *p8;
uint32_t *p32;
uint64_t *p64;
size_t i;
} u;
u.p8 = (const uint8_t *)message;
if (!ALLOW_UNALIGNED_READS && (u.i & 0x7))
{
memcpy(buf, message, length);
u.p64 = buf;
}
size_t remainder = length%32;
uint64_t a=*hash1;
uint64_t b=*hash2;
uint64_t c=sc_const;
uint64_t d=sc_const;
if (length > 15)
{
const uint64_t *end = u.p64 + (length/32)*4;
// handle all complete sets of 32 bytes
for (; u.p64 < end; u.p64 += 4)
{
c += u.p64[0];
d += u.p64[1];
ShortMix(a,b,c,d);
a += u.p64[2];
b += u.p64[3];
}
//Handle the case of 16+ remaining bytes.
if (remainder >= 16)
{
c += u.p64[0];
d += u.p64[1];
ShortMix(a,b,c,d);
u.p64 += 2;
remainder -= 16;
}
}
// Handle the last 0..15 bytes, and its length
d = ((uint64_t)length) << 56;
switch (remainder)
{
case 15:
d += ((uint64_t)u.p8[14]) << 48;
case 14:
d += ((uint64_t)u.p8[13]) << 40;
case 13:
d += ((uint64_t)u.p8[12]) << 32;
case 12:
d += u.p32[2];
c += u.p64[0];
break;
case 11:
d += ((uint64_t)u.p8[10]) << 16;
case 10:
d += ((uint64_t)u.p8[9]) << 8;
case 9:
d += (uint64_t)u.p8[8];
case 8:
c += u.p64[0];
break;
case 7:
c += ((uint64_t)u.p8[6]) << 48;
case 6:
c += ((uint64_t)u.p8[5]) << 40;
case 5:
c += ((uint64_t)u.p8[4]) << 32;
case 4:
c += u.p32[0];
break;
case 3:
c += ((uint64_t)u.p8[2]) << 16;
case 2:
c += ((uint64_t)u.p8[1]) << 8;
case 1:
c += (uint64_t)u.p8[0];
break;
case 0:
c += sc_const;
d += sc_const;
}
ShortEnd(a,b,c,d);
*hash1 = a;
*hash2 = b;
}
// do the whole hash in one call
void SpookyHash::Hash128(
const void *message,
size_t length,
uint64_t *hash1,
uint64_t *hash2)
{
if (length < sc_bufSize)
{
Short(message, length, hash1, hash2);
return;
}
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
uint64_t buf[sc_numVars];
uint64_t *end;
union
{
const uint8_t *p8;
uint64_t *p64;
size_t i;
} u;
size_t remainder;
h0=h3=h6=h9 = *hash1;
h1=h4=h7=h10 = *hash2;
h2=h5=h8=h11 = sc_const;
u.p8 = (const uint8_t *)message;
end = u.p64 + (length/sc_blockSize)*sc_numVars;
// handle all whole sc_blockSize blocks of bytes
if (ALLOW_UNALIGNED_READS || ((u.i & 0x7) == 0))
{
while (u.p64 < end)
{
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
else
{
while (u.p64 < end)
{
memcpy(buf, u.p64, sc_blockSize);
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
// handle the last partial block of sc_blockSize bytes
remainder = (length - ((const uint8_t *)end-(const uint8_t *)message));
memcpy(buf, end, remainder);
memset(((uint8_t *)buf)+remainder, 0, sc_blockSize-remainder);
((uint8_t *)buf)[sc_blockSize-1] = remainder;
Mix(buf, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
// do some final mixing
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
*hash1 = h0;
*hash2 = h1;
}
// init spooky state
void SpookyHash::Init(uint64_t seed1, uint64_t seed2)
{
m_length = 0;
m_remainder = 0;
m_state[0] = seed1;
m_state[1] = seed2;
}
// add a message fragment to the state
void SpookyHash::Update(const void *message, size_t length)
{
uint64_t h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11;
size_t newLength = length + m_remainder;
uint8_t remainder;
union
{
const uint8_t *p8;
uint64_t *p64;
size_t i;
} u;
const uint64_t *end;
// Is this message fragment too short? If it is, stuff it away.
if (newLength < sc_bufSize)
{
memcpy(&((uint8_t *)m_data)[m_remainder], message, length);
m_length = length + m_length;
m_remainder = (uint8_t)newLength;
return;
}
// init the variables
if (m_length < sc_bufSize)
{
h0=h3=h6=h9 = m_state[0];
h1=h4=h7=h10 = m_state[1];
h2=h5=h8=h11 = sc_const;
}
else
{
h0 = m_state[0];
h1 = m_state[1];
h2 = m_state[2];
h3 = m_state[3];
h4 = m_state[4];
h5 = m_state[5];
h6 = m_state[6];
h7 = m_state[7];
h8 = m_state[8];
h9 = m_state[9];
h10 = m_state[10];
h11 = m_state[11];
}
m_length = length + m_length;
// if we've got anything stuffed away, use it now
if (m_remainder)
{
uint8_t prefix = sc_bufSize-m_remainder;
memcpy(&(((uint8_t *)m_data)[m_remainder]), message, prefix);
u.p64 = m_data;
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
Mix(&u.p64[sc_numVars], h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p8 = ((const uint8_t *)message) + prefix;
length -= prefix;
}
else
{
u.p8 = (const uint8_t *)message;
}
// handle all whole blocks of sc_blockSize bytes
end = u.p64 + (length/sc_blockSize)*sc_numVars;
remainder = (uint8_t)(length-((const uint8_t *)end-u.p8));
if (ALLOW_UNALIGNED_READS || (u.i & 0x7) == 0)
{
while (u.p64 < end)
{
Mix(u.p64, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
else
{
while (u.p64 < end)
{
memcpy(m_data, u.p8, sc_blockSize);
Mix(m_data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
u.p64 += sc_numVars;
}
}
// stuff away the last few bytes
m_remainder = remainder;
memcpy(m_data, end, remainder);
// stuff away the variables
m_state[0] = h0;
m_state[1] = h1;
m_state[2] = h2;
m_state[3] = h3;
m_state[4] = h4;
m_state[5] = h5;
m_state[6] = h6;
m_state[7] = h7;
m_state[8] = h8;
m_state[9] = h9;
m_state[10] = h10;
m_state[11] = h11;
}
// report the hash for the concatenation of all message fragments so far
void SpookyHash::Final(uint64_t *hash1, uint64_t *hash2)
{
// init the variables
if (m_length < sc_bufSize)
{
*hash1 = m_state[0];
*hash2 = m_state[1];
Short( m_data, m_length, hash1, hash2);
return;
}
const uint64_t *data = (const uint64_t *)m_data;
uint8_t remainder = m_remainder;
uint64_t h0 = m_state[0];
uint64_t h1 = m_state[1];
uint64_t h2 = m_state[2];
uint64_t h3 = m_state[3];
uint64_t h4 = m_state[4];
uint64_t h5 = m_state[5];
uint64_t h6 = m_state[6];
uint64_t h7 = m_state[7];
uint64_t h8 = m_state[8];
uint64_t h9 = m_state[9];
uint64_t h10 = m_state[10];
uint64_t h11 = m_state[11];
if (remainder >= sc_blockSize)
{
// m_data can contain two blocks; handle any whole first block
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
data += sc_numVars;
remainder -= sc_blockSize;
}
// mix in the last partial block, and the length mod sc_blockSize
memset(&((uint8_t *)data)[remainder], 0, (sc_blockSize-remainder));
((uint8_t *)data)[sc_blockSize-1] = remainder;
Mix(data, h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
// do some final mixing
End(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
*hash1 = h0;
*hash2 = h1;
}
} // namespace hash
} // namespace folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//
// SpookyHash: a 128-bit noncryptographic hash function
// By Bob Jenkins, public domain
// Oct 31 2010: alpha, framework + SpookyHash::Mix appears right
// Oct 31 2011: alpha again, Mix only good to 2^^69 but rest appears right
// Dec 31 2011: beta, improved Mix, tested it for 2-bit deltas
// Feb 2 2012: production, same bits as beta
// Feb 5 2012: adjusted definitions of uint* to be more portable
// Mar 30 2012: 3 bytes/cycle, not 4. Alpha was 4 but wasn't thorough enough.
//
// Up to 3 bytes/cycle for long messages. Reasonably fast for short messages.
// All 1 or 2 bit deltas achieve avalanche within 1% bias per output bit.
//
// This was developed for and tested on 64-bit x86-compatible processors.
// It assumes the processor is little-endian. There is a macro
// controlling whether unaligned reads are allowed (by default they are).
// This should be an equally good hash on big-endian machines, but it will
// compute different results on them than on little-endian machines.
//
// Google's CityHash has similar specs to SpookyHash, and CityHash is faster
// on some platforms. MD4 and MD5 also have similar specs, but they are orders
// of magnitude slower. CRCs are two or more times slower, but unlike
// SpookyHash, they have nice math for combining the CRCs of pieces to form
// the CRCs of wholes. There are also cryptographic hashes, but those are even
// slower than MD5.
//
#ifndef FOLLY_SPOOKYHASH_H_
#define FOLLY_SPOOKYHASH_H_
#include <cstddef>
#include <cstdint>
namespace folly {
namespace hash {
class SpookyHash
{
public:
//
// SpookyHash: hash a single message in one call, produce 128-bit output
//
static void Hash128(
const void *message, // message to hash
size_t length, // length of message in bytes
uint64_t *hash1, // in/out: in seed 1, out hash value 1
uint64_t *hash2); // in/out: in seed 2, out hash value 2
//
// Hash64: hash a single message in one call, return 64-bit output
//
static uint64_t Hash64(
const void *message, // message to hash
size_t length, // length of message in bytes
uint64_t seed) // seed
{
uint64_t hash1 = seed;
Hash128(message, length, &hash1, &seed);
return hash1;
}
//
// Hash32: hash a single message in one call, produce 32-bit output
//
static uint32_t Hash32(
const void *message, // message to hash
size_t length, // length of message in bytes
uint32_t seed) // seed
{
uint64_t hash1 = seed, hash2 = seed;
Hash128(message, length, &hash1, &hash2);
return (uint32_t)hash1;
}
//
// Init: initialize the context of a SpookyHash
//
void Init(
uint64_t seed1, // any 64-bit value will do, including 0
uint64_t seed2); // different seeds produce independent hashes
//
// Update: add a piece of a message to a SpookyHash state
//
void Update(
const void *message, // message fragment
size_t length); // length of message fragment in bytes
//
// Final: compute the hash for the current SpookyHash state
//
// This does not modify the state; you can keep updating it afterward
//
// The result is the same as if SpookyHash() had been called with
// all the pieces concatenated into one message.
//
void Final(
uint64_t *hash1, // out only: first 64 bits of hash value.
uint64_t *hash2); // out only: second 64 bits of hash value.
//
// left rotate a 64-bit value by k bytes
//
static inline uint64_t Rot64(uint64_t x, int k)
{
return (x << k) | (x >> (64 - k));
}
//
// This is used if the input is 96 bytes long or longer.
//
// The internal state is fully overwritten every 96 bytes.
// Every input bit appears to cause at least 128 bits of entropy
// before 96 other bytes are combined, when run forward or backward
// For every input bit,
// Two inputs differing in just that input bit
// Where "differ" means xor or subtraction
// And the base value is random
// When run forward or backwards one Mix
// I tried 3 pairs of each; they all differed by at least 212 bits.
//
static inline void Mix(
const uint64_t *data,
uint64_t &s0, uint64_t &s1, uint64_t &s2, uint64_t &s3,
uint64_t &s4, uint64_t &s5, uint64_t &s6, uint64_t &s7,
uint64_t &s8, uint64_t &s9, uint64_t &s10,uint64_t &s11)
{
s0 += data[0]; s2 ^= s10; s11 ^= s0; s0 = Rot64(s0,11); s11 += s1;
s1 += data[1]; s3 ^= s11; s0 ^= s1; s1 = Rot64(s1,32); s0 += s2;
s2 += data[2]; s4 ^= s0; s1 ^= s2; s2 = Rot64(s2,43); s1 += s3;
s3 += data[3]; s5 ^= s1; s2 ^= s3; s3 = Rot64(s3,31); s2 += s4;
s4 += data[4]; s6 ^= s2; s3 ^= s4; s4 = Rot64(s4,17); s3 += s5;
s5 += data[5]; s7 ^= s3; s4 ^= s5; s5 = Rot64(s5,28); s4 += s6;
s6 += data[6]; s8 ^= s4; s5 ^= s6; s6 = Rot64(s6,39); s5 += s7;
s7 += data[7]; s9 ^= s5; s6 ^= s7; s7 = Rot64(s7,57); s6 += s8;
s8 += data[8]; s10 ^= s6; s7 ^= s8; s8 = Rot64(s8,55); s7 += s9;
s9 += data[9]; s11 ^= s7; s8 ^= s9; s9 = Rot64(s9,54); s8 += s10;
s10 += data[10]; s0 ^= s8; s9 ^= s10; s10 = Rot64(s10,22); s9 += s11;
s11 += data[11]; s1 ^= s9; s10 ^= s11; s11 = Rot64(s11,46); s10 += s0;
}
//
// Mix all 12 inputs together so that h0, h1 are a hash of them all.
//
// For two inputs differing in just the input bits
// Where "differ" means xor or subtraction
// And the base value is random, or a counting value starting at that bit
// The final result will have each bit of h0, h1 flip
// For every input bit,
// with probability 50 +- .3%
// For every pair of input bits,
// with probability 50 +- 3%
//
// This does not rely on the last Mix() call having already mixed some.
// Two iterations was almost good enough for a 64-bit result, but a
// 128-bit result is reported, so End() does three iterations.
//
static inline void EndPartial(
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
{
h11+= h1; h2 ^= h11; h1 = Rot64(h1,44);
h0 += h2; h3 ^= h0; h2 = Rot64(h2,15);
h1 += h3; h4 ^= h1; h3 = Rot64(h3,34);
h2 += h4; h5 ^= h2; h4 = Rot64(h4,21);
h3 += h5; h6 ^= h3; h5 = Rot64(h5,38);
h4 += h6; h7 ^= h4; h6 = Rot64(h6,33);
h5 += h7; h8 ^= h5; h7 = Rot64(h7,10);
h6 += h8; h9 ^= h6; h8 = Rot64(h8,13);
h7 += h9; h10^= h7; h9 = Rot64(h9,38);
h8 += h10; h11^= h8; h10= Rot64(h10,53);
h9 += h11; h0 ^= h9; h11= Rot64(h11,42);
h10+= h0; h1 ^= h10; h0 = Rot64(h0,54);
}
static inline void End(
uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3,
uint64_t &h4, uint64_t &h5, uint64_t &h6, uint64_t &h7,
uint64_t &h8, uint64_t &h9, uint64_t &h10,uint64_t &h11)
{
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
EndPartial(h0,h1,h2,h3,h4,h5,h6,h7,h8,h9,h10,h11);
}
//
// The goal is for each bit of the input to expand into 128 bits of
// apparent entropy before it is fully overwritten.
// n trials both set and cleared at least m bits of h0 h1 h2 h3
// n: 2 m: 29
// n: 3 m: 46
// n: 4 m: 57
// n: 5 m: 107
// n: 6 m: 146
// n: 7 m: 152
// when run forwards or backwards
// for all 1-bit and 2-bit diffs
// with diffs defined by either xor or subtraction
// with a base of all zeros plus a counter, or plus another bit, or random
//
static inline void ShortMix(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
{
h2 = Rot64(h2,50); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,52); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,30); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,41); h1 += h2; h3 ^= h1;
h2 = Rot64(h2,54); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,48); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,38); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,37); h1 += h2; h3 ^= h1;
h2 = Rot64(h2,62); h2 += h3; h0 ^= h2;
h3 = Rot64(h3,34); h3 += h0; h1 ^= h3;
h0 = Rot64(h0,5); h0 += h1; h2 ^= h0;
h1 = Rot64(h1,36); h1 += h2; h3 ^= h1;
}
//
// Mix all 4 inputs together so that h0, h1 are a hash of them all.
//
// For two inputs differing in just the input bits
// Where "differ" means xor or subtraction
// And the base value is random, or a counting value starting at that bit
// The final result will have each bit of h0, h1 flip
// For every input bit,
// with probability 50 +- .3% (it is probably better than that)
// For every pair of input bits,
// with probability 50 +- .75% (the worst case is approximately that)
//
static inline void ShortEnd(uint64_t &h0, uint64_t &h1, uint64_t &h2, uint64_t &h3)
{
h3 ^= h2; h2 = Rot64(h2,15); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,52); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,26); h1 += h0;
h2 ^= h1; h1 = Rot64(h1,51); h2 += h1;
h3 ^= h2; h2 = Rot64(h2,28); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,9); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,47); h1 += h0;
h2 ^= h1; h1 = Rot64(h1,54); h2 += h1;
h3 ^= h2; h2 = Rot64(h2,32); h3 += h2;
h0 ^= h3; h3 = Rot64(h3,25); h0 += h3;
h1 ^= h0; h0 = Rot64(h0,63); h1 += h0;
}
private:
//
// Short is used for messages under 192 bytes in length
// Short has a low startup cost, the normal mode is good for long
// keys, the cost crossover is at about 192 bytes. The two modes were
// held to the same quality bar.
//
static void Short(
const void *message, // message (array of bytes, not necessarily aligned)
size_t length, // length of message (in bytes)
uint64_t *hash1, // in/out: in the seed, out the hash value
uint64_t *hash2); // in/out: in the seed, out the hash value
// number of uint64_t's in internal state
static const size_t sc_numVars = 12;
// size of the internal state
static const size_t sc_blockSize = sc_numVars*8;
// size of buffer of unhashed data, in bytes
static const size_t sc_bufSize = 2*sc_blockSize;
//
// sc_const: a constant which:
// * is not zero
// * is odd
// * is a not-very-regular mix of 1's and 0's
// * does not need any other special mathematical properties
//
static const uint64_t sc_const = 0xdeadbeefdeadbeefLL;
uint64_t m_data[2*sc_numVars]; // unhashed data, for partial messages
uint64_t m_state[sc_numVars]; // internal state of the hash
size_t m_length; // total length of the input so far
uint8_t m_remainder; // length of unhashed data stashed in m_data
};
} // namespace hash
} // namespace folly
#endif
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_STLALLOCATOR_H_
#define FOLLY_STLALLOCATOR_H_
#include "folly/Traits.h"
#include <memory>
#include <limits>
#include <utility>
#include <exception>
#include <stdexcept>
#include <cstddef>
namespace folly {
/**
* Wrap a SimpleAllocator into a STL-compliant allocator.
*
* The SimpleAllocator must provide two methods:
* void* allocate(size_t size);
* void deallocate(void* ptr);
* which, respectively, allocate a block of size bytes (aligned to the maximum
* alignment required on your system), throwing std::bad_alloc if the
* allocation can't be satisfied, and free a previously allocated block.
*
* Note that the following allocator resembles the standard allocator
* quite well:
*
* class MallocAllocator {
* public:
* void* allocate(size_t size) {
* void* p = malloc(size);
* if (!p) throw std::bad_alloc();
* return p;
* }
* void deallocate(void* p) {
* free(p);
* }
* };
*
* author: Tudor Bosman <tudorb@fb.com>
*/
// This would be so much simpler with std::allocator_traits, but gcc 4.6.2
// doesn't support it
template <class Alloc, class T> class StlAllocator;
template <class Alloc> class StlAllocator<Alloc, void> {
public:
typedef void value_type;
typedef void* pointer;
typedef const void* const_pointer;
template <class U> struct rebind {
typedef StlAllocator<Alloc, U> other;
};
};
template <class Alloc, class T>
class StlAllocator {
public:
typedef T value_type;
typedef T* pointer;
typedef const T* const_pointer;
typedef T& reference;
typedef const T& const_reference;
typedef ptrdiff_t difference_type;
typedef size_t size_type;
StlAllocator() : alloc_(nullptr) { }
explicit StlAllocator(Alloc* alloc) : alloc_(alloc) { }
template <class U> StlAllocator(const StlAllocator<Alloc, U>& other)
: alloc_(other.alloc()) { }
T* allocate(size_t n, const void* hint = nullptr) {
return static_cast<T*>(alloc_->allocate(n * sizeof(T)));
}
void deallocate(T* p, size_t n) {
alloc_->deallocate(p);
}
size_t max_size() const {
return std::numeric_limits<size_t>::max();
}
T* address(T& x) const {
return std::addressof(x);
}
const T* address(const T& x) const {
return std::addressof(x);
}
template <class... Args>
void construct(T* p, Args&&... args) {
new (p) T(std::forward<Args>(args)...);
}
void destroy(T* p) {
p->~T();
}
Alloc* alloc() const {
return alloc_;
}
template <class U> struct rebind {
typedef StlAllocator<Alloc, U> other;
};
bool operator!=(const StlAllocator<Alloc, T>& other) const {
return alloc_ != other.alloc_;
}
bool operator==(const StlAllocator<Alloc, T>& other) const {
return alloc_ == other.alloc_;
}
private:
Alloc* alloc_;
};
/*
* Helper classes/functions for creating a unique_ptr using a custom allocator
*
* @author: Marcelo Juchem <marcelo@fb.com>
*/
// A deleter implementation based on std::default_delete,
// which uses a custom allocator to free memory
template <typename Allocator>
class allocator_delete {
typedef typename std::remove_reference<Allocator>::type allocator_type;
public:
allocator_delete() = default;
explicit allocator_delete(const allocator_type& allocator):
allocator_(allocator)
{}
explicit allocator_delete(allocator_type&& allocator):
allocator_(std::move(allocator))
{}
template <typename U>
allocator_delete(const allocator_delete<U>& other):
allocator_(other.get_allocator())
{}
allocator_type& get_allocator() const {
return allocator_;
}
void operator()(typename allocator_type::pointer p) const {
if (!p) {
return;
}
allocator_.destroy(p);
allocator_.deallocate(p, 1);
}
private:
mutable allocator_type allocator_;
};
template <typename T, typename Allocator>
class is_simple_allocator {
FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(has_destroy, destroy);
typedef typename std::remove_const<
typename std::remove_reference<Allocator>::type
>::type allocator;
typedef typename std::remove_reference<T>::type value_type;
typedef value_type* pointer;
public:
constexpr static bool value = !has_destroy<allocator, void(pointer)>::value
&& !has_destroy<allocator, void(void*)>::value;
};
template <typename T, typename Allocator>
typename std::enable_if<
is_simple_allocator<T, Allocator>::value,
folly::StlAllocator<
typename std::remove_reference<Allocator>::type,
typename std::remove_reference<T>::type
>
>::type make_stl_allocator(Allocator&& allocator) {
return folly::StlAllocator<
typename std::remove_reference<Allocator>::type,
typename std::remove_reference<T>::type
>(&allocator);
}
template <typename T, typename Allocator>
typename std::enable_if<
!is_simple_allocator<T, Allocator>::value,
typename std::remove_reference<Allocator>::type
>::type make_stl_allocator(Allocator&& allocator) {
return std::move(allocator);
}
template <typename T, typename Allocator>
struct AllocatorUniquePtr {
typedef std::unique_ptr<T,
folly::allocator_delete<
typename std::conditional<
is_simple_allocator<T, Allocator>::value,
folly::StlAllocator<typename std::remove_reference<Allocator>::type, T>,
typename std::remove_reference<Allocator>::type
>::type
>
> type;
};
template <typename T, typename Allocator, typename ...Args>
typename AllocatorUniquePtr<T, Allocator>::type allocate_unique(
Allocator&& allocator, Args&&... args
) {
auto stlAllocator = folly::make_stl_allocator<T>(
std::forward<Allocator>(allocator)
);
auto p = stlAllocator.allocate(1);
try {
stlAllocator.construct(p, std::forward<Args>(args)...);
return {p,
folly::allocator_delete<decltype(stlAllocator)>(std::move(stlAllocator))
};
} catch (...) {
stlAllocator.deallocate(p, 1);
throw;
}
}
template <typename T, typename Allocator, typename ...Args>
std::shared_ptr<T> allocate_shared(Allocator&& allocator, Args&&... args) {
return std::allocate_shared<T>(
folly::make_stl_allocator<T>(std::forward<Allocator>(allocator)),
std::forward<Args>(args)...
);
}
} // namespace folly
#endif /* FOLLY_STLALLOCATOR_H_ */
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* Wrapper around the eventfd system call, as defined in <sys/eventfd.h>
* in glibc 2.9+.
*
* @author Tudor Bosman (tudorb@fb.com)
*/
#ifndef FOLLY_BASE_EVENTFD_H_
#define FOLLY_BASE_EVENTFD_H_
#ifndef __linux__
#error This file may be compiled on Linux only.
#endif
#include <sys/syscall.h>
#include <unistd.h>
#include <fcntl.h>
// Use existing __NR_eventfd2 if already defined
// Values from the Linux kernel source:
// arch/x86/include/asm/unistd_{32,64}.h
#ifndef __NR_eventfd2
#if defined(__x86_64__)
#define __NR_eventfd2 290
#elif defined(__i386__)
#define __NR_eventfd2 328
#else
#error "Can't define __NR_eventfd2 for your architecture."
#endif
#endif
#ifndef EFD_SEMAPHORE
#define EFD_SEMAPHORE 1
#endif
/* from linux/fcntl.h - this conflicts with fcntl.h so include just the #define
* we need
*/
#ifndef O_CLOEXEC
#define O_CLOEXEC 02000000 /* set close_on_exec */
#endif
#ifndef EFD_CLOEXEC
#define EFD_CLOEXEC O_CLOEXEC
#endif
#ifndef EFD_NONBLOCK
#define EFD_NONBLOCK O_NONBLOCK
#endif
namespace folly {
// http://www.kernel.org/doc/man-pages/online/pages/man2/eventfd.2.html
inline int eventfd(unsigned int initval, int flags) {
// Use the eventfd2 system call, as in glibc 2.9+
// (requires kernel 2.6.30+)
return syscall(__NR_eventfd2, initval, flags);
}
} // namespace folly
#endif /* FOLLY_BASE_EVENTFD_H_ */
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_FILE_H_
#error This file may only be included from folly/experimental/File.h
#endif
#include <algorithm>
namespace folly {
inline File::File(int fd, bool ownsFd) : fd_(fd), ownsFd_(ownsFd) { }
inline File::~File() {
closeNoThrow(); // ignore error
}
inline void File::release() {
fd_ = -1;
ownsFd_ = false;
}
inline void File::swap(File& other) {
using std::swap;
swap(fd_, other.fd_);
swap(ownsFd_, other.ownsFd_);
}
inline File::File(File&& other) : fd_(other.fd_), ownsFd_(other.ownsFd_) {
other.release();
}
inline File& File::operator=(File&& other) {
File(std::move(other)).swap(*this);
return *this;
}
inline void swap(File& a, File& b) {
a.swap(b);
}
} // namespace folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/File.h"
#include <system_error>
#include <glog/logging.h>
namespace folly {
File::File(const char* name, int flags, mode_t mode)
: fd_(::open(name, flags, mode)), ownsFd_(false) {
if (fd_ == -1) {
throw std::system_error(errno, std::system_category(), "open() failed");
}
ownsFd_ = true;
}
void File::close() {
if (!closeNoThrow()) {
throw std::system_error(errno, std::system_category(), "close() failed");
}
}
bool File::closeNoThrow() {
int r = ownsFd_ ? ::close(fd_) : 0;
release();
return r == 0;
}
} // namespace folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_FILE_H_
#define FOLLY_FILE_H_
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
namespace folly {
/**
* A File represents an open file.
*/
class File {
public:
/**
* Create a File object from an existing file descriptor.
* Takes ownership of the file descriptor if ownsFd is true.
*/
/* implicit */ File(int fd, bool ownsFd=false);
/**
* Open and create a file object. Throws on error.
*/
/* implicit */ File(const char* name, int flags=O_RDONLY, mode_t mode=0644);
~File();
/**
* Return the file descriptor, or -1 if the file was closed.
*/
int fd() const { return fd_; }
/**
* If we own the file descriptor, close the file and throw on error.
* Otherwise, do nothing.
*/
void close();
/**
* Closes the file (if owned). Returns true on success, false (and sets
* errno) on error.
*/
bool closeNoThrow();
/**
* Releases the file descriptor; no longer owned by this File.
*/
void release();
/**
* Swap this File with another.
*/
void swap(File& other);
// movable
File(File&&);
File& operator=(File&&);
private:
// not copyable
File(const File&) = delete;
File& operator=(const File&) = delete;
int fd_;
bool ownsFd_;
};
void swap(File& a, File& b);
} // namespace folly
#include "folly/experimental/File-inl.h"
#endif /* FOLLY_FILE_H_ */
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/exception_tracer/StackTrace.h"
#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
#define UNW_LOCAL_ONLY 1
#include <libunwind.h>
struct Context {
StackTrace* trace;
size_t skip;
size_t capacity;
};
static int checkError(const char* name, int err) {
if (err < 0) {
fprintf(stderr, "libunwind error: %s %d\n", name, err);
return -EINVAL;
}
return 0;
}
static int addIP(struct Context* ctx, unw_cursor_t* cursor) {
if (ctx->skip) {
--ctx->skip;
return 0;
}
unw_word_t ip;
int r = unw_get_reg(cursor, UNW_REG_IP, &ip);
int err = checkError("unw_get_reg", r);
if (err) return err;
if (ctx->trace->frameCount == ctx->capacity) {
size_t newCapacity = (ctx->capacity < 8 ? 8 : ctx->capacity * 1.5);
uintptr_t* newBlock =
realloc(ctx->trace->frameIPs, newCapacity * sizeof(uintptr_t));
if (!newBlock) {
return -ENOMEM;
}
ctx->trace->frameIPs = newBlock;
ctx->capacity = newCapacity;
}
ctx->trace->frameIPs[ctx->trace->frameCount++] = ip;
return 0; /* success */
}
int getCurrentStackTrace(size_t skip, StackTrace* trace) {
trace->frameIPs = NULL;
trace->frameCount = 0;
struct Context ctx;
ctx.trace = trace;
ctx.skip = skip;
ctx.capacity = 0;
unw_context_t uctx;
int r = unw_getcontext(&uctx);
int err = checkError("unw_get_context", r);
if (err) return err;
unw_cursor_t cursor;
r = unw_init_local(&cursor, &uctx);
err = checkError("unw_init_local", r);
if (err) return err;
while ((r = unw_step(&cursor)) > 0) {
if ((err = addIP(&ctx, &cursor)) != 0) {
destroyStackTrace(trace);
return err;
}
}
err = checkError("unw_step", r);
if (err) return err;
return 0;
}
void destroyStackTrace(StackTrace* trace) {
free(trace->frameIPs);
trace->frameIPs = NULL;
trace->frameCount = 0;
}
int pushCurrentStackTrace(size_t skip, StackTraceStack** head) {
StackTraceStack* newHead = malloc(sizeof(StackTraceStack));
if (!newHead) {
return -ENOMEM;
}
int err;
if ((err = getCurrentStackTrace(skip, &newHead->trace)) != 0) {
free(newHead);
return -ENOMEM;
}
newHead->next = *head;
*head = newHead;
return 0;
}
void popStackTrace(StackTraceStack** head) {
StackTraceStack* oldHead = *head;
*head = oldHead->next;
destroyStackTrace(&oldHead->trace);
free(oldHead);
}
void clearStack(StackTraceStack** head) {
while (*head) {
popStackTrace(head);
}
}
int moveTop(StackTraceStack** from, StackTraceStack** to) {
StackTraceStack* top = *from;
if (!top) {
return -EINVAL;
}
*from = top->next;
top->next = *to;
*to = top;
return 0;
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_CURSOR_H
#define FOLLY_CURSOR_H
#include <assert.h>
#include <stdexcept>
#include <string.h>
#include <type_traits>
#include <memory>
#include "folly/Bits.h"
#include "folly/experimental/io/IOBuf.h"
#include "folly/Likely.h"
/**
* Cursor class for fast iteration over IOBuf chains.
*
* Cursor - Read-only access
*
* RWPrivateCursor - Read-write access, assumes private access to IOBuf chain
* RWUnshareCursor - Read-write access, calls unshare on write (COW)
* Appender - Write access, assumes private access to IOBuf chian
*
* Note that RW cursors write in the preallocated part of buffers (that is,
* between the buffer's data() and tail()), while Appenders append to the end
* of the buffer (between the buffer's tail() and bufferEnd()). Appenders
* automatically adjust the buffer pointers, so you may only use one
* Appender with a buffer chain; for this reason, Appenders assume private
* access to the buffer (you need to call unshare() yourself if necessary).
**/
namespace folly { namespace io {
namespace detail {
template <class Derived, typename BufType>
class CursorBase {
public:
const uint8_t* data() const {
return crtBuf_->data() + offset_;
}
// Space available in the current IOBuf. May be 0; use peek() instead which
// will always point to a non-empty chunk of data or at the end of the
// chain.
size_t length() const {
return crtBuf_->length() - offset_;
}
Derived& operator+=(size_t offset) {
Derived* p = static_cast<Derived*>(this);
p->skip(offset);
return *p;
}
template <class T>
typename std::enable_if<std::is_integral<T>::value, T>::type
read() {
T val;
pull(&val, sizeof(T));
return val;
}
template <class T>
T readBE() {
return Endian::big(read<T>());
}
template <class T>
T readLE() {
return Endian::little(read<T>());
}
explicit CursorBase(BufType* buf)
: crtBuf_(buf)
, offset_(0)
, buffer_(buf) {}
// Make all the templated classes friends for copy constructor.
template <class D, typename B> friend class CursorBase;
template <class T>
explicit CursorBase(const T& cursor) {
crtBuf_ = cursor.crtBuf_;
offset_ = cursor.offset_;
buffer_ = cursor.buffer_;
}
// reset cursor to point to a new buffer.
void reset(BufType* buf) {
crtBuf_ = buf;
buffer_ = buf;
offset_ = 0;
}
/**
* Return the available data in the current buffer.
* If you want to gather more data from the chain into a contiguous region
* (for hopefully zero-copy access), use gather() before peek().
*/
std::pair<const uint8_t*, size_t> peek() {
// Ensure that we're pointing to valid data
size_t available = length();
while (UNLIKELY(available == 0 && tryAdvanceBuffer())) {
available = length();
}
return std::make_pair(data(), available);
}
void pull(void* buf, size_t length) {
if (UNLIKELY(pullAtMost(buf, length) != length)) {
throw std::out_of_range("underflow");
}
}
void clone(std::unique_ptr<folly::IOBuf>& buf, size_t length) {
if (UNLIKELY(cloneAtMost(buf, length) != length)) {
throw std::out_of_range("underflow");
}
}
void skip(size_t length) {
if (UNLIKELY(skipAtMost(length) != length)) {
throw std::out_of_range("underflow");
}
}
size_t pullAtMost(void* buf, size_t len) {
uint8_t* p = reinterpret_cast<uint8_t*>(buf);
size_t copied = 0;
for (;;) {
// Fast path: it all fits in one buffer.
size_t available = length();
if (LIKELY(available >= len)) {
memcpy(p, data(), len);
offset_ += len;
return copied + len;
}
memcpy(p, data(), available);
copied += available;
if (UNLIKELY(!tryAdvanceBuffer())) {
return copied;
}
p += available;
len -= available;
}
}
size_t cloneAtMost(std::unique_ptr<folly::IOBuf>& buf, size_t len) {
buf.reset(nullptr);
std::unique_ptr<folly::IOBuf> tmp;
size_t copied = 0;
for (;;) {
// Fast path: it all fits in one buffer.
size_t available = length();
if (LIKELY(available >= len)) {
tmp = crtBuf_->cloneOne();
tmp->trimStart(offset_);
tmp->trimEnd(tmp->length() - len);
offset_ += len;
if (!buf) {
buf = std::move(tmp);
} else {
buf->prependChain(std::move(tmp));
}
return copied + len;
}
tmp = crtBuf_->cloneOne();
tmp->trimStart(offset_);
if (!buf) {
buf = std::move(tmp);
} else {
buf->prependChain(std::move(tmp));
}
copied += available;
if (UNLIKELY(!tryAdvanceBuffer())) {
return copied;
}
len -= available;
}
}
size_t skipAtMost(size_t len) {
size_t skipped = 0;
for (;;) {
// Fast path: it all fits in one buffer.
size_t available = length();
if (LIKELY(available >= len)) {
offset_ += len;
return skipped + len;
}
skipped += available;
if (UNLIKELY(!tryAdvanceBuffer())) {
return skipped;
}
len -= available;
}
}
protected:
BufType* crtBuf_;
size_t offset_;
~CursorBase(){}
bool tryAdvanceBuffer() {
BufType* nextBuf = crtBuf_->next();
if (UNLIKELY(nextBuf == buffer_)) {
offset_ = crtBuf_->length();
return false;
}
offset_ = 0;
crtBuf_ = nextBuf;
static_cast<Derived*>(this)->advanceDone();
return true;
}
private:
void advanceDone() {
}
BufType* buffer_;
};
template <class Derived>
class Writable {
public:
template <class T>
typename std::enable_if<std::is_integral<T>::value>::type
write(T value) {
const uint8_t* u8 = reinterpret_cast<const uint8_t*>(&value);
push(u8, sizeof(T));
}
template <class T>
void writeBE(T value) {
write(Endian::big(value));
}
template <class T>
void writeLE(T value) {
write(Endian::little(value));
}
void push(const uint8_t* buf, size_t len) {
Derived* d = static_cast<Derived*>(this);
if (d->pushAtMost(buf, len) != len) {
throw std::out_of_range("overflow");
}
}
};
} // namespace detail
class Cursor : public detail::CursorBase<Cursor, const IOBuf> {
public:
explicit Cursor(const IOBuf* buf)
: detail::CursorBase<Cursor, const IOBuf>(buf) {}
template <class CursorType>
explicit Cursor(CursorType& cursor)
: detail::CursorBase<Cursor, const IOBuf>(cursor) {}
};
enum class CursorAccess {
PRIVATE,
UNSHARE
};
template <CursorAccess access>
class RWCursor
: public detail::CursorBase<RWCursor<access>, IOBuf>,
public detail::Writable<RWCursor<access>> {
friend class detail::CursorBase<RWCursor<access>, IOBuf>;
public:
explicit RWCursor(IOBuf* buf)
: detail::CursorBase<RWCursor<access>, IOBuf>(buf),
maybeShared_(true) {}
template <class CursorType>
explicit RWCursor(CursorType& cursor)
: detail::CursorBase<RWCursor<access>, IOBuf>(cursor),
maybeShared_(true) {}
/**
* Gather at least n bytes contiguously into the current buffer,
* by coalescing subsequent buffers from the chain as necessary.
*/
void gather(size_t n) {
this->crtBuf_->gather(this->offset_ + n);
}
size_t pushAtMost(const uint8_t* buf, size_t len) {
size_t copied = 0;
for (;;) {
// Fast path: the current buffer is big enough.
size_t available = this->length();
if (LIKELY(available >= len)) {
if (access == CursorAccess::UNSHARE) {
maybeUnshare();
}
memcpy(writableData(), buf, len);
this->offset_ += len;
return copied + len;
}
if (access == CursorAccess::UNSHARE) {
maybeUnshare();
}
memcpy(writableData(), buf, available);
copied += available;
if (UNLIKELY(!this->tryAdvanceBuffer())) {
return copied;
}
buf += available;
len -= available;
}
}
void insert(std::unique_ptr<folly::IOBuf> buf) {
folly::IOBuf* nextBuf;
if (this->offset_ == 0) {
// Can just prepend
nextBuf = buf.get();
this->crtBuf_->prependChain(std::move(buf));
} else {
std::unique_ptr<folly::IOBuf> remaining;
if (this->crtBuf_->length() - this->offset_ > 0) {
// Need to split current IOBuf in two.
remaining = this->crtBuf_->cloneOne();
remaining->trimStart(this->offset_);
nextBuf = remaining.get();
buf->prependChain(std::move(remaining));
} else {
// Can just append
nextBuf = this->crtBuf_->next();
}
this->crtBuf_->trimEnd(this->length());
this->crtBuf_->appendChain(std::move(buf));
}
// Jump past the new links
this->offset_ = 0;
this->crtBuf_ = nextBuf;
}
uint8_t* writableData() {
return this->crtBuf_->writableData() + this->offset_;
}
private:
void maybeUnshare() {
if (UNLIKELY(maybeShared_)) {
this->crtBuf_->unshareOne();
maybeShared_ = false;
}
}
void advanceDone() {
maybeShared_ = true;
}
bool maybeShared_;
};
typedef RWCursor<CursorAccess::PRIVATE> RWPrivateCursor;
typedef RWCursor<CursorAccess::UNSHARE> RWUnshareCursor;
/**
* Append to the end of a buffer chain, growing the chain (by allocating new
* buffers) in increments of at least growth bytes every time. Won't grow
* (and push() and ensure() will throw) if growth == 0.
*
* TODO(tudorb): add a flavor of Appender that reallocates one IOBuf instead
* of chaining.
*/
class Appender : public detail::Writable<Appender> {
public:
Appender(IOBuf* buf, uint32_t growth)
: buffer_(buf),
crtBuf_(buf->prev()),
growth_(growth) {
}
uint8_t* writableData() {
return crtBuf_->writableTail();
}
size_t length() const {
return crtBuf_->tailroom();
}
/**
* Mark n bytes (must be <= length()) as appended, as per the
* IOBuf::append() method.
*/
void append(size_t n) {
crtBuf_->append(n);
}
/**
* Ensure at least n contiguous bytes available to write.
* Postcondition: length() >= n.
*/
void ensure(uint32_t n) {
if (LIKELY(length() >= n)) {
return;
}
// Waste the rest of the current buffer and allocate a new one.
// Don't make it too small, either.
if (growth_ == 0) {
throw std::out_of_range("can't grow buffer chain");
}
n = std::max(n, growth_);
buffer_->prependChain(IOBuf::create(n));
crtBuf_ = buffer_->prev();
}
size_t pushAtMost(const uint8_t* buf, size_t len) {
size_t copied = 0;
for (;;) {
// Fast path: it all fits in one buffer.
size_t available = length();
if (LIKELY(available >= len)) {
memcpy(writableData(), buf, len);
append(len);
return copied + len;
}
memcpy(writableData(), buf, available);
append(available);
copied += available;
if (UNLIKELY(!tryGrowChain())) {
return copied;
}
buf += available;
len -= available;
}
}
private:
bool tryGrowChain() {
assert(crtBuf_->next() == buffer_);
if (growth_ == 0) {
return false;
}
buffer_->prependChain(IOBuf::create(growth_));
crtBuf_ = buffer_->prev();
return true;
}
IOBuf* buffer_;
IOBuf* crtBuf_;
uint32_t growth_;
};
}} // folly::io
#endif // FOLLY_CURSOR_H
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#define __STDC_LIMIT_MACROS
#include "folly/experimental/io/IOBuf.h"
#include "folly/Malloc.h"
#include "folly/Likely.h"
#include <stdexcept>
#include <assert.h>
#include <stdint.h>
#include <stdlib.h>
using std::unique_ptr;
namespace folly {
const uint32_t IOBuf::kMaxIOBufSize;
// Note: Applying offsetof() to an IOBuf is legal according to C++11, since
// IOBuf is a standard-layout class. However, this isn't legal with earlier
// C++ standards, which require that offsetof() only be used with POD types.
//
// This code compiles with g++ 4.6, but not with g++ 4.4 or earlier versions.
const uint32_t IOBuf::kMaxInternalDataSize =
kMaxIOBufSize - offsetof(folly::IOBuf, int_.buf);
IOBuf::SharedInfo::SharedInfo()
: freeFn(NULL),
userData(NULL) {
// Use relaxed memory ordering here. Since we are creating a new SharedInfo,
// no other threads should be referring to it yet.
refcount.store(1, std::memory_order_relaxed);
}
IOBuf::SharedInfo::SharedInfo(FreeFunction fn, void* arg)
: freeFn(fn),
userData(arg) {
// Use relaxed memory ordering here. Since we are creating a new SharedInfo,
// no other threads should be referring to it yet.
refcount.store(1, std::memory_order_relaxed);
}
void* IOBuf::operator new(size_t size) {
// Since IOBuf::create() manually allocates space for some IOBuf objects
// using malloc(), override operator new so that all IOBuf objects are
// always allocated using malloc(). This way operator delete can always know
// that free() is the correct way to deallocate the memory.
void* ptr = malloc(size);
// operator new is not allowed to return NULL
if (UNLIKELY(ptr == NULL)) {
throw std::bad_alloc();
}
return ptr;
}
void* IOBuf::operator new(size_t size, void* ptr) {
assert(size <= kMaxIOBufSize);
return ptr;
}
void IOBuf::operator delete(void* ptr) {
// For small buffers, IOBuf::create() manually allocates the space for the
// IOBuf object using malloc(). Therefore we override delete to ensure that
// the IOBuf space is freed using free() rather than a normal delete.
free(ptr);
}
unique_ptr<IOBuf> IOBuf::create(uint32_t capacity) {
// If the desired capacity is less than kMaxInternalDataSize,
// just allocate a single region large enough for both the IOBuf header and
// the data.
if (capacity <= kMaxInternalDataSize) {
void* buf = malloc(kMaxIOBufSize);
if (UNLIKELY(buf == NULL)) {
throw std::bad_alloc();
}
uint8_t* bufEnd = static_cast<uint8_t*>(buf) + kMaxIOBufSize;
unique_ptr<IOBuf> iobuf(new(buf) IOBuf(bufEnd));
assert(iobuf->capacity() >= capacity);
return iobuf;
}
// Allocate an external buffer
uint8_t* buf;
SharedInfo* sharedInfo;
uint32_t actualCapacity;
allocExtBuffer(capacity, &buf, &sharedInfo, &actualCapacity);
// Allocate the IOBuf header
try {
return unique_ptr<IOBuf>(new IOBuf(kExtAllocated, 0,
buf, actualCapacity,
buf, 0,
sharedInfo));
} catch (...) {
free(buf);
throw;
}
}
unique_ptr<IOBuf> IOBuf::takeOwnership(void* buf, uint32_t capacity,
uint32_t length,
FreeFunction freeFn,
void* userData,
bool freeOnError) {
SharedInfo* sharedInfo = NULL;
try {
sharedInfo = new SharedInfo(freeFn, userData);
uint8_t* bufPtr = static_cast<uint8_t*>(buf);
return unique_ptr<IOBuf>(new IOBuf(kExtUserSupplied, kFlagFreeSharedInfo,
bufPtr, capacity,
bufPtr, length,
sharedInfo));
} catch (...) {
delete sharedInfo;
if (freeOnError) {
if (freeFn) {
try {
freeFn(buf, userData);
} catch (...) {
// The user's free function is not allowed to throw.
abort();
}
} else {
free(buf);
}
}
throw;
}
}
unique_ptr<IOBuf> IOBuf::wrapBuffer(const void* buf, uint32_t capacity) {
// We cast away the const-ness of the buffer here.
// This is okay since IOBuf users must use unshare() to create a copy of
// this buffer before writing to the buffer.
uint8_t* bufPtr = static_cast<uint8_t*>(const_cast<void*>(buf));
return unique_ptr<IOBuf>(new IOBuf(kExtUserSupplied, kFlagUserOwned,
bufPtr, capacity,
bufPtr, capacity,
NULL));
}
IOBuf::IOBuf(uint8_t* end)
: next_(this),
prev_(this),
data_(int_.buf),
length_(0),
flags_(0) {
assert(end - int_.buf == kMaxInternalDataSize);
assert(end - reinterpret_cast<uint8_t*>(this) == kMaxIOBufSize);
}
IOBuf::IOBuf(ExtBufTypeEnum type,
uint32_t flags,
uint8_t* buf,
uint32_t capacity,
uint8_t* data,
uint32_t length,
SharedInfo* sharedInfo)
: next_(this),
prev_(this),
data_(data),
length_(length),
flags_(kFlagExt | flags) {
ext_.capacity = capacity;
ext_.type = type;
ext_.buf = buf;
ext_.sharedInfo = sharedInfo;
assert(data >= buf);
assert(data + length <= buf + capacity);
assert(static_cast<bool>(flags & kFlagUserOwned) ==
(sharedInfo == NULL));
}
IOBuf::~IOBuf() {
// Destroying an IOBuf destroys the entire chain.
// Users of IOBuf should only explicitly delete the head of any chain.
// The other elements in the chain will be automatically destroyed.
while (next_ != this) {
// Since unlink() returns unique_ptr() and we don't store it,
// it will automatically delete the unlinked element.
(void)next_->unlink();
}
if (flags_ & kFlagExt) {
decrementRefcount();
}
}
bool IOBuf::empty() const {
const IOBuf* current = this;
do {
if (current->length() != 0) {
return false;
}
current = current->next_;
} while (current != this);
return true;
}
uint32_t IOBuf::countChainElements() const {
uint32_t numElements = 1;
for (IOBuf* current = next_; current != this; current = current->next_) {
++numElements;
}
return numElements;
}
uint64_t IOBuf::computeChainDataLength() const {
uint64_t fullLength = length_;
for (IOBuf* current = next_; current != this; current = current->next_) {
fullLength += current->length_;
}
return fullLength;
}
void IOBuf::prependChain(unique_ptr<IOBuf>&& iobuf) {
// Take ownership of the specified IOBuf
IOBuf* other = iobuf.release();
// Remember the pointer to the tail of the other chain
IOBuf* otherTail = other->prev_;
// Hook up prev_->next_ to point at the start of the other chain,
// and other->prev_ to point at prev_
prev_->next_ = other;
other->prev_ = prev_;
// Hook up otherTail->next_ to point at us,
// and prev_ to point back at otherTail,
otherTail->next_ = this;
prev_ = otherTail;
}
unique_ptr<IOBuf> IOBuf::clone() const {
unique_ptr<IOBuf> newHead(cloneOne());
for (IOBuf* current = next_; current != this; current = current->next_) {
newHead->prependChain(current->cloneOne());
}
return newHead;
}
unique_ptr<IOBuf> IOBuf::cloneOne() const {
if (flags_ & kFlagExt) {
unique_ptr<IOBuf> iobuf(new IOBuf(static_cast<ExtBufTypeEnum>(ext_.type),
flags_, ext_.buf, ext_.capacity,
data_, length_,
ext_.sharedInfo));
if (ext_.sharedInfo) {
ext_.sharedInfo->refcount.fetch_add(1, std::memory_order_acq_rel);
}
return iobuf;
} else {
// We have an internal data buffer that cannot be shared
// Allocate a new IOBuf and copy the data into it.
unique_ptr<IOBuf> iobuf(IOBuf::create(kMaxInternalDataSize));
assert((iobuf->flags_ & kFlagExt) == 0);
iobuf->data_ += headroom();
memcpy(iobuf->data_, data_, length_);
iobuf->length_ = length_;
return iobuf;
}
}
void IOBuf::unshareOneSlow() {
// Internal buffers are always unshared, so unshareOneSlow() can only be
// called for external buffers
assert(flags_ & kFlagExt);
// Allocate a new buffer for the data
uint8_t* buf;
SharedInfo* sharedInfo;
uint32_t actualCapacity;
allocExtBuffer(ext_.capacity, &buf, &sharedInfo, &actualCapacity);
// Copy the data
// Maintain the same amount of headroom. Since we maintained the same
// minimum capacity we also maintain at least the same amount of tailroom.
uint32_t headlen = headroom();
memcpy(buf + headlen, data_, length_);
// Release our reference on the old buffer
decrementRefcount();
// Make sure kFlagExt is set, and kFlagUserOwned and kFlagFreeSharedInfo
// are not set.
flags_ = kFlagExt;
// Update the buffer pointers to point to the new buffer
data_ = buf + headlen;
ext_.buf = buf;
ext_.sharedInfo = sharedInfo;
}
void IOBuf::unshareChained() {
// unshareChained() should only be called if we are part of a chain of
// multiple IOBufs. The caller should have already verified this.
assert(isChained());
IOBuf* current = this;
while (true) {
if (current->isSharedOne()) {
// we have to unshare
break;
}
current = current->next_;
if (current == this) {
// None of the IOBufs in the chain are shared,
// so return without doing anything
return;
}
}
// We have to unshare. Let coalesceSlow() do the work.
coalesceSlow();
}
void IOBuf::coalesceSlow(size_t maxLength) {
// coalesceSlow() should only be called if we are part of a chain of multiple
// IOBufs. The caller should have already verified this.
assert(isChained());
assert(length_ < maxLength);
// Compute the length of the entire chain
uint64_t newLength = 0;
IOBuf* end = this;
do {
newLength += end->length_;
end = end->next_;
} while (newLength < maxLength && end != this);
uint64_t newHeadroom = headroom();
uint64_t newTailroom = end->prev_->tailroom();
coalesceAndReallocate(newHeadroom, newLength, end, newTailroom);
// We should be only element left in the chain now
assert(length_ >= maxLength || !isChained());
}
void IOBuf::coalesceAndReallocate(size_t newHeadroom,
size_t newLength,
IOBuf* end,
size_t newTailroom) {
uint64_t newCapacity = newLength + newHeadroom + newTailroom;
if (newCapacity > UINT32_MAX) {
throw std::overflow_error("IOBuf chain too large to coalesce");
}
// Allocate space for the coalesced buffer.
// We always convert to an external buffer, even if we happened to be an
// internal buffer before.
uint8_t* newBuf;
SharedInfo* newInfo;
uint32_t actualCapacity;
allocExtBuffer(newCapacity, &newBuf, &newInfo, &actualCapacity);
// Copy the data into the new buffer
uint8_t* newData = newBuf + newHeadroom;
uint8_t* p = newData;
IOBuf* current = this;
size_t remaining = newLength;
do {
assert(current->length_ <= remaining);
remaining -= current->length_;
memcpy(p, current->data_, current->length_);
p += current->length_;
current = current->next_;
} while (current != end);
assert(remaining == 0);
// Point at the new buffer
if (flags_ & kFlagExt) {
decrementRefcount();
}
// Make sure kFlagExt is set, and kFlagUserOwned and kFlagFreeSharedInfo
// are not set.
flags_ = kFlagExt;
ext_.capacity = actualCapacity;
ext_.type = kExtAllocated;
ext_.buf = newBuf;
ext_.sharedInfo = newInfo;
data_ = newData;
length_ = newLength;
// Separate from the rest of our chain.
// Since we don't store the unique_ptr returned by separateChain(),
// this will immediately delete the returned subchain.
if (isChained()) {
(void)separateChain(next_, current->prev_);
}
}
void IOBuf::decrementRefcount() {
assert(flags_ & kFlagExt);
// Externally owned buffers don't have a SharedInfo object and aren't managed
// by the reference count
if (flags_ & kFlagUserOwned) {
assert(ext_.sharedInfo == NULL);
return;
}
// Decrement the refcount
uint32_t newcnt = ext_.sharedInfo->refcount.fetch_sub(
1, std::memory_order_acq_rel);
// Note that fetch_sub() returns the value before we decremented.
// If it is 1, we were the only remaining user; if it is greater there are
// still other users.
if (newcnt > 1) {
return;
}
// We were the last user. Free the buffer
if (ext_.sharedInfo->freeFn != NULL) {
try {
ext_.sharedInfo->freeFn(ext_.buf, ext_.sharedInfo->userData);
} catch (...) {
// The user's free function should never throw. Otherwise we might
// throw from the IOBuf destructor. Other code paths like coalesce()
// also assume that decrementRefcount() cannot throw.
abort();
}
} else {
free(ext_.buf);
}
// Free the SharedInfo if it was allocated separately.
//
// This is only used by takeOwnership().
//
// To avoid this special case handling in decrementRefcount(), we could have
// takeOwnership() set a custom freeFn() that calls the user's free function
// then frees the SharedInfo object. (This would require that
// takeOwnership() store the user's free function with its allocated
// SharedInfo object.) However, handling this specially with a flag seems
// like it shouldn't be problematic.
if (flags_ & kFlagFreeSharedInfo) {
delete ext_.sharedInfo;
}
}
void IOBuf::reserveSlow(uint32_t minHeadroom, uint32_t minTailroom) {
size_t newCapacity = (size_t)length_ + minHeadroom + minTailroom;
CHECK_LT(newCapacity, UINT32_MAX);
// We'll need to reallocate the buffer.
// There are a few options.
// - If we have enough total room, move the data around in the buffer
// and adjust the data_ pointer.
// - If we're using an internal buffer, we'll switch to an external
// buffer with enough headroom and tailroom.
// - If we have enough headroom (headroom() >= minHeadroom) but not too much
// (so we don't waste memory), we can try one of two things, depending on
// whether we use jemalloc or not:
// - If using jemalloc, we can try to expand in place, avoiding a memcpy()
// - If not using jemalloc and we don't have too much to copy,
// we'll use realloc() (note that realloc might have to copy
// headroom + data + tailroom, see smartRealloc in folly/Malloc.h)
// - Otherwise, bite the bullet and reallocate.
if (headroom() + tailroom() >= minHeadroom + minTailroom) {
uint8_t* newData = writableBuffer() + minHeadroom;
memmove(newData, data_, length_);
data_ = newData;
return;
}
size_t newAllocatedCapacity = goodExtBufferSize(newCapacity);
uint8_t* newBuffer = nullptr;
uint32_t newHeadroom = 0;
uint32_t oldHeadroom = headroom();
if ((flags_ & kFlagExt) && length_ != 0 && oldHeadroom >= minHeadroom) {
if (usingJEMalloc()) {
size_t headSlack = oldHeadroom - minHeadroom;
// We assume that tailroom is more useful and more important than
// tailroom (not least because realloc / rallocm allow us to grow the
// buffer at the tail, but not at the head) So, if we have more headroom
// than we need, we consider that "wasted". We arbitrarily define "too
// much" headroom to be 25% of the capacity.
if (headSlack * 4 <= newCapacity) {
size_t allocatedCapacity = capacity() + sizeof(SharedInfo);
void* p = ext_.buf;
if (allocatedCapacity >= jemallocMinInPlaceExpandable) {
int r = rallocm(&p, &newAllocatedCapacity, newAllocatedCapacity,
0, ALLOCM_NO_MOVE);
if (r == ALLOCM_SUCCESS) {
newBuffer = static_cast<uint8_t*>(p);
newHeadroom = oldHeadroom;
} else if (r == ALLOCM_ERR_OOM) {
// shouldn't happen as we don't actually allocate new memory
// (due to ALLOCM_NO_MOVE)
throw std::bad_alloc();
}
// if ALLOCM_ERR_NOT_MOVED, do nothing, fall back to
// malloc/memcpy/free
}
}
} else { // Not using jemalloc
size_t copySlack = capacity() - length_;
if (copySlack * 2 <= length_) {
void* p = realloc(ext_.buf, newAllocatedCapacity);
if (UNLIKELY(p == nullptr)) {
throw std::bad_alloc();
}
newBuffer = static_cast<uint8_t*>(p);
newHeadroom = oldHeadroom;
}
}
}
// None of the previous reallocation strategies worked (or we're using
// an internal buffer). malloc/copy/free.
if (newBuffer == nullptr) {
void* p = malloc(newAllocatedCapacity);
if (UNLIKELY(p == nullptr)) {
throw std::bad_alloc();
}
newBuffer = static_cast<uint8_t*>(p);
memcpy(newBuffer + minHeadroom, data_, length_);
if (flags_ & kFlagExt) {
free(ext_.buf);
}
newHeadroom = minHeadroom;
}
SharedInfo* info;
uint32_t cap;
initExtBuffer(newBuffer, newAllocatedCapacity, &info, &cap);
flags_ = kFlagExt;
ext_.capacity = cap;
ext_.type = kExtAllocated;
ext_.buf = newBuffer;
ext_.sharedInfo = info;
data_ = newBuffer + newHeadroom;
// length_ is unchanged
}
void IOBuf::allocExtBuffer(uint32_t minCapacity,
uint8_t** bufReturn,
SharedInfo** infoReturn,
uint32_t* capacityReturn) {
size_t mallocSize = goodExtBufferSize(minCapacity);
uint8_t* buf = static_cast<uint8_t*>(malloc(mallocSize));
if (UNLIKELY(buf == NULL)) {
throw std::bad_alloc();
}
initExtBuffer(buf, mallocSize, infoReturn, capacityReturn);
*bufReturn = buf;
}
size_t IOBuf::goodExtBufferSize(uint32_t minCapacity) {
// Determine how much space we should allocate. We'll store the SharedInfo
// for the external buffer just after the buffer itself. (We store it just
// after the buffer rather than just before so that the code can still just
// use free(ext_.buf) to free the buffer.)
size_t minSize = static_cast<size_t>(minCapacity) + sizeof(SharedInfo);
// Add room for padding so that the SharedInfo will be aligned on an 8-byte
// boundary.
minSize = (minSize + 7) & ~7;
// Use goodMallocSize() to bump up the capacity to a decent size to request
// from malloc, so we can use all of the space that malloc will probably give
// us anyway.
return goodMallocSize(minSize);
}
void IOBuf::initExtBuffer(uint8_t* buf, size_t mallocSize,
SharedInfo** infoReturn,
uint32_t* capacityReturn) {
// Find the SharedInfo storage at the end of the buffer
// and construct the SharedInfo.
uint8_t* infoStart = (buf + mallocSize) - sizeof(SharedInfo);
SharedInfo* sharedInfo = new(infoStart) SharedInfo;
size_t actualCapacity = infoStart - buf;
// On the unlikely possibility that the actual capacity is larger than can
// fit in a uint32_t after adding room for the refcount and calling
// goodMallocSize(), truncate downwards if necessary.
if (actualCapacity >= UINT32_MAX) {
*capacityReturn = UINT32_MAX;
} else {
*capacityReturn = actualCapacity;
}
*infoReturn = sharedInfo;
}
fbstring IOBuf::moveToFbString() {
// Externally allocated buffers (malloc) are just fine, everything else needs
// to be turned into one.
if (flags_ != kFlagExt || // not malloc()-ed
headroom() != 0 || // malloc()-ed block doesn't start at beginning
tailroom() == 0 || // no room for NUL terminator
isShared() || // shared
isChained()) { // chained
// We might as well get rid of all head and tailroom if we're going
// to reallocate; we need 1 byte for NUL terminator.
coalesceAndReallocate(0, computeChainDataLength(), this, 1);
}
// Ensure NUL terminated
*writableTail() = 0;
fbstring str(reinterpret_cast<char*>(writableData()),
length(), capacity(),
AcquireMallocatedString());
// Reset to internal buffer.
flags_ = 0;
clear();
return str;
}
} // folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_IO_IOBUF_H_
#define FOLLY_IO_IOBUF_H_
#include <glog/logging.h>
#include <atomic>
#include <cassert>
#include <cinttypes>
#include <cstddef>
#include <cstring>
#include <memory>
#include <limits>
#include <type_traits>
#include "folly/FBString.h"
namespace folly {
/**
* An IOBuf is a pointer to a buffer of data.
*
* IOBuf objects are intended to be used primarily for networking code, and are
* modelled somewhat after FreeBSD's mbuf data structure, and Linux's sk_buff
* structure.
*
* IOBuf objects facilitate zero-copy network programming, by allowing multiple
* IOBuf objects to point to the same underlying buffer of data, using a
* reference count to track when the buffer is no longer needed and can be
* freed.
*
*
* Data Layout
* -----------
*
* The IOBuf itself is a small object containing a pointer to the buffer and
* information about which segment of the buffer contains valid data.
*
* The data layout looks like this:
*
* +-------+
* | IOBuf |
* +-------+
* /
* |
* v
* +------------+--------------------+-----------+
* | headroom | data | tailroom |
* +------------+--------------------+-----------+
* ^ ^ ^ ^
* buffer() data() tail() bufferEnd()
*
* The length() method returns the length of the valid data; capacity()
* returns the entire capacity of the buffer (from buffer() to bufferEnd()).
* The headroom() and tailroom() methods return the amount of unused capacity
* available before and after the data.
*
*
* Buffer Sharing
* --------------
*
* The buffer itself is reference counted, and multiple IOBuf objects may point
* to the same buffer. Each IOBuf may point to a different section of valid
* data within the underlying buffer. For example, if multiple protocol
* requests are read from the network into a single buffer, a separate IOBuf
* may be created for each request, all sharing the same underlying buffer.
*
* In other words, when multiple IOBufs share the same underlying buffer, the
* data() and tail() methods on each IOBuf may point to a different segment of
* the data. However, the buffer() and bufferEnd() methods will point to the
* same location for all IOBufs sharing the same underlying buffer.
*
* +-----------+ +---------+
* | IOBuf 1 | | IOBuf 2 |
* +-----------+ +---------+
* | | _____/ |
* data | tail |/ data | tail
* v v v
* +-------------------------------------+
* | | | | |
* +-------------------------------------+
*
* If you only read data from an IOBuf, you don't need to worry about other
* IOBuf objects possibly sharing the same underlying buffer. However, if you
* ever write to the buffer you need to first ensure that no other IOBufs point
* to the same buffer. The unshare() method may be used to ensure that you
* have an unshared buffer.
*
*
* IOBuf Chains
* ------------
*
* IOBuf objects also contain pointers to next and previous IOBuf objects.
* This can be used to represent a single logical piece of data that its stored
* in non-contiguous chunks in separate buffers.
*
* A single IOBuf object can only belong to one chain at a time.
*
* IOBuf chains are always circular. The "prev" pointer in the head of the
* chain points to the tail of the chain. However, it is up to the user to
* decide which IOBuf is the head. Internally the IOBuf code does not care
* which element is the head.
*
* The lifetime of all IOBufs in the chain are linked: when one element in the
* chain is deleted, all other chained elements are also deleted. Conceptually
* it is simplest to treat this as if the head of the chain owns all other
* IOBufs in the chain. When you delete the head of the chain, it will delete
* the other elements as well. For this reason, prependChain() and
* appendChain() take ownership of of the new elements being added to this
* chain.
*
* When the coalesce() method is used to coalesce an entire IOBuf chain into a
* single IOBuf, all other IOBufs in the chain are eliminated and automatically
* deleted. The unshare() method may coalesce the chain; if it does it will
* similarly delete all IOBufs eliminated from the chain.
*
* As discussed in the following section, it is up to the user to maintain a
* lock around the entire IOBuf chain if multiple threads need to access the
* chain. IOBuf does not provide any internal locking.
*
*
* Synchronization
* ---------------
*
* When used in multithread programs, a single IOBuf object should only be used
* in a single thread at a time. If a caller uses a single IOBuf across
* multiple threads the caller is responsible for using an external lock to
* synchronize access to the IOBuf.
*
* Two separate IOBuf objects may be accessed concurrently in separate threads
* without locking, even if they point to the same underlying buffer. The
* buffer reference count is always accessed atomically, and no other
* operations should affect other IOBufs that point to the same data segment.
* The caller is responsible for using unshare() to ensure that the data buffer
* is not shared by other IOBufs before writing to it, and this ensures that
* the data itself is not modified in one thread while also being accessed from
* another thread.
*
* For IOBuf chains, no two IOBufs in the same chain should be accessed
* simultaneously in separate threads. The caller must maintain a lock around
* the entire chain if the chain, or individual IOBufs in the chain, may be
* accessed by multiple threads.
*
*
* IOBuf Object Allocation/Sharing
* -------------------------------
*
* IOBuf objects themselves are always allocated on the heap. The IOBuf
* constructors are private, so IOBuf objects may not be created on the stack.
* In part this is done since some IOBuf objects use small-buffer optimization
* and contain the buffer data immediately after the IOBuf object itself. The
* coalesce() and unshare() methods also expect to be able to delete subsequent
* IOBuf objects in the chain if they are no longer needed due to coalescing.
*
* The IOBuf structure also does not provide room for an intrusive refcount on
* the IOBuf object itself, only the underlying data buffer is reference
* counted. If users want to share the same IOBuf object between multiple
* parts of the code, they are responsible for managing this sharing on their
* own. (For example, by using a shared_ptr. Alternatively, users always have
* the option of using clone() to create a second IOBuf that points to the same
* underlying buffer.)
*
* With jemalloc, allocating small objects like IOBuf objects should be
* relatively fast, and the cost of allocating IOBuf objects on the heap and
* cloning new IOBufs should be relatively cheap.
*/
namespace detail {
// Is T a unique_ptr<> to a standard-layout type?
template <class T, class Enable=void> struct IsUniquePtrToSL
: public std::false_type { };
template <class T, class D>
struct IsUniquePtrToSL<
std::unique_ptr<T, D>,
typename std::enable_if<std::is_standard_layout<T>::value>::type>
: public std::true_type { };
} // namespace detail
class IOBuf {
public:
typedef void (*FreeFunction)(void* buf, void* userData);
/**
* Allocate a new IOBuf object with the requested capacity.
*
* Returns a new IOBuf object that must be (eventually) deleted by the
* caller. The returned IOBuf may actually have slightly more capacity than
* requested.
*
* The data pointer will initially point to the start of the newly allocated
* buffer, and will have a data length of 0.
*
* Throws std::bad_alloc on error.
*/
static std::unique_ptr<IOBuf> create(uint32_t capacity);
/**
* Create a new IOBuf pointing to an existing data buffer.
*
* The new IOBuffer will assume ownership of the buffer, and free it by
* calling the specified FreeFunction when the last IOBuf pointing to this
* buffer is destroyed. The function will be called with a pointer to the
* buffer as the first argument, and the supplied userData value as the
* second argument. The free function must never throw exceptions.
*
* If no FreeFunction is specified, the buffer will be freed using free().
*
* The IOBuf data pointer will initially point to the start of the buffer,
*
* In the first version of this function, the length of data is unspecified
* and is initialized to the capacity of the buffer
*
* In the second version, the user specifies the valid length of data
* in the buffer
*
* On error, std::bad_alloc will be thrown. If freeOnError is true (the
* default) the buffer will be freed before throwing the error.
*/
static std::unique_ptr<IOBuf> takeOwnership(void* buf, uint32_t capacity,
FreeFunction freeFn = NULL,
void* userData = NULL,
bool freeOnError = true) {
return takeOwnership(buf, capacity, capacity, freeFn,
userData, freeOnError);
}
static std::unique_ptr<IOBuf> takeOwnership(void* buf, uint32_t capacity,
uint32_t length,
FreeFunction freeFn = NULL,
void* userData = NULL,
bool freeOnError = true);
/**
* Create a new IOBuf pointing to an existing data buffer made up of
* count objects of a given standard-layout type.
*
* This is dangerous -- it is essentially equivalent to doing
* reinterpret_cast<unsigned char*> on your data -- but it's often useful
* for serialization / deserialization.
*
* The new IOBuffer will assume ownership of the buffer, and free it
* appropriately (by calling the UniquePtr's custom deleter, or by calling
* delete or delete[] appropriately if there is no custom deleter)
* when the buffer is destroyed. The custom deleter, if any, must never
* throw exceptions.
*
* The IOBuf data pointer will initially point to the start of the buffer,
* and the length will be the full capacity of the buffer (count *
* sizeof(T)).
*
* On error, std::bad_alloc will be thrown, and the buffer will be freed
* before throwing the error.
*/
template <class UniquePtr>
static typename std::enable_if<detail::IsUniquePtrToSL<UniquePtr>::value,
std::unique_ptr<IOBuf>>::type
takeOwnership(UniquePtr&& buf, size_t count=1);
/**
* Create a new IOBuf object that points to an existing user-owned buffer.
*
* This should only be used when the caller knows the lifetime of the IOBuf
* object ahead of time and can ensure that all IOBuf objects that will point
* to this buffer will be destroyed before the buffer itself is destroyed.
*
* This buffer will not be freed automatically when the last IOBuf
* referencing it is destroyed. It is the caller's responsibility to free
* the buffer after the last IOBuf has been destroyed.
*
* The IOBuf data pointer will initially point to the start of the buffer,
* and the length will be the full capacity of the buffer.
*
* An IOBuf created using wrapBuffer() will always be reported as shared.
* unshare() may be used to create a writable copy of the buffer.
*
* On error, std::bad_alloc will be thrown.
*/
static std::unique_ptr<IOBuf> wrapBuffer(const void* buf, uint32_t capacity);
/**
* Convenience function to create a new IOBuf object that copies data from a
* user-supplied buffer, optionally allocating a given amount of
* headroom and tailroom.
*/
static std::unique_ptr<IOBuf> copyBuffer(const void* buf, uint32_t size,
uint32_t headroom=0,
uint32_t minTailroom=0);
/**
* Convenience function to create a new IOBuf object that copies data from a
* user-supplied string, optionally allocating a given amount of
* headroom and tailroom.
*
* Beware when attempting to invoke this function with a constant string
* literal and a headroom argument: you will likely end up invoking the
* version of copyBuffer() above. IOBuf::copyBuffer("hello", 3) will treat
* the first argument as a const void*, and will invoke the version of
* copyBuffer() above, with the size argument of 3.
*/
static std::unique_ptr<IOBuf> copyBuffer(const std::string& buf,
uint32_t headroom=0,
uint32_t minTailroom=0);
/**
* A version of copyBuffer() that returns a null pointer if the input string
* is empty.
*/
static std::unique_ptr<IOBuf> maybeCopyBuffer(const std::string& buf,
uint32_t headroom=0,
uint32_t minTailroom=0);
/**
* Convenience function to free a chain of IOBufs held by a unique_ptr.
*/
static void destroy(std::unique_ptr<IOBuf>&& data) {
auto destroyer = std::move(data);
}
/**
* Destroy this IOBuf.
*
* Deleting an IOBuf will automatically destroy all IOBufs in the chain.
* (See the comments above regarding the ownership model of IOBuf chains.
* All subsequent IOBufs in the chain are considered to be owned by the head
* of the chain. Users should only explicitly delete the head of a chain.)
*
* When each individual IOBuf is destroyed, it will release its reference
* count on the underlying buffer. If it was the last user of the buffer,
* the buffer will be freed.
*/
~IOBuf();
/**
* Check whether the chain is empty (i.e., whether the IOBufs in the
* chain have a total data length of zero).
*
* This method is semantically equivalent to
* i->computeChainDataLength()==0
* but may run faster because it can short-circuit as soon as it
* encounters a buffer with length()!=0
*/
bool empty() const;
/**
* Get the pointer to the start of the data.
*/
const uint8_t* data() const {
return data_;
}
/**
* Get a writable pointer to the start of the data.
*
* The caller is responsible for calling unshare() first to ensure that it is
* actually safe to write to the buffer.
*/
uint8_t* writableData() {
return data_;
}
/**
* Get the pointer to the end of the data.
*/
const uint8_t* tail() const {
return data_ + length_;
}
/**
* Get a writable pointer to the end of the data.
*
* The caller is responsible for calling unshare() first to ensure that it is
* actually safe to write to the buffer.
*/
uint8_t* writableTail() {
return data_ + length_;
}
/**
* Get the data length.
*/
uint32_t length() const {
return length_;
}
/**
* Get the amount of head room.
*
* Returns the number of bytes in the buffer before the start of the data.
*/
uint32_t headroom() const {
return data_ - buffer();
}
/**
* Get the amount of tail room.
*
* Returns the number of bytes in the buffer after the end of the data.
*/
uint32_t tailroom() const {
return bufferEnd() - tail();
}
/**
* Get the pointer to the start of the buffer.
*
* Note that this is the pointer to the very beginning of the usable buffer,
* not the start of valid data within the buffer. Use the data() method to
* get a pointer to the start of the data within the buffer.
*/
const uint8_t* buffer() const {
return (flags_ & kFlagExt) ? ext_.buf : int_.buf;
}
/**
* Get a writable pointer to the start of the buffer.
*
* The caller is responsible for calling unshare() first to ensure that it is
* actually safe to write to the buffer.
*/
uint8_t* writableBuffer() {
return (flags_ & kFlagExt) ? ext_.buf : int_.buf;
}
/**
* Get the pointer to the end of the buffer.
*
* Note that this is the pointer to the very end of the usable buffer,
* not the end of valid data within the buffer. Use the tail() method to
* get a pointer to the end of the data within the buffer.
*/
const uint8_t* bufferEnd() const {
return (flags_ & kFlagExt) ?
ext_.buf + ext_.capacity :
int_.buf + kMaxInternalDataSize;
}
/**
* Get the total size of the buffer.
*
* This returns the total usable length of the buffer. Use the length()
* method to get the length of the actual valid data in this IOBuf.
*/
uint32_t capacity() const {
return (flags_ & kFlagExt) ? ext_.capacity : kMaxInternalDataSize;
}
/**
* Get a pointer to the next IOBuf in this chain.
*/
IOBuf* next() {
return next_;
}
const IOBuf* next() const {
return next_;
}
/**
* Get a pointer to the previous IOBuf in this chain.
*/
IOBuf* prev() {
return prev_;
}
const IOBuf* prev() const {
return prev_;
}
/**
* Shift the data forwards in the buffer.
*
* This shifts the data pointer forwards in the buffer to increase the
* headroom. This is commonly used to increase the headroom in a newly
* allocated buffer.
*
* The caller is responsible for ensuring that there is sufficient
* tailroom in the buffer before calling advance().
*
* If there is a non-zero data length, advance() will use memmove() to shift
* the data forwards in the buffer. In this case, the caller is responsible
* for making sure the buffer is unshared, so it will not affect other IOBufs
* that may be sharing the same underlying buffer.
*/
void advance(uint32_t amount) {
// In debug builds, assert if there is a problem.
assert(amount <= tailroom());
if (length_ > 0) {
memmove(data_ + amount, data_, length_);
}
data_ += amount;
}
/**
* Shift the data backwards in the buffer.
*
* The caller is responsible for ensuring that there is sufficient headroom
* in the buffer before calling retreat().
*
* If there is a non-zero data length, retreat() will use memmove() to shift
* the data backwards in the buffer. In this case, the caller is responsible
* for making sure the buffer is unshared, so it will not affect other IOBufs
* that may be sharing the same underlying buffer.
*/
void retreat(uint32_t amount) {
// In debug builds, assert if there is a problem.
assert(amount <= headroom());
if (length_ > 0) {
memmove(data_ - amount, data_, length_);
}
data_ -= amount;
}
/**
* Adjust the data pointer to include more valid data at the beginning.
*
* This moves the data pointer backwards to include more of the available
* buffer. The caller is responsible for ensuring that there is sufficient
* headroom for the new data. The caller is also responsible for populating
* this section with valid data.
*
* This does not modify any actual data in the buffer.
*/
void prepend(uint32_t amount) {
CHECK(amount <= headroom());
data_ -= amount;
length_ += amount;
}
/**
* Adjust the tail pointer to include more valid data at the end.
*
* This moves the tail pointer forwards to include more of the available
* buffer. The caller is responsible for ensuring that there is sufficient
* tailroom for the new data. The caller is also responsible for populating
* this section with valid data.
*
* This does not modify any actual data in the buffer.
*/
void append(uint32_t amount) {
CHECK(amount <= tailroom());
length_ += amount;
}
/**
* Adjust the data pointer forwards to include less valid data.
*
* This moves the data pointer forwards so that the first amount bytes are no
* longer considered valid data. The caller is responsible for ensuring that
* amount is less than or equal to the actual data length.
*
* This does not modify any actual data in the buffer.
*/
void trimStart(uint32_t amount) {
CHECK(amount <= length_);
data_ += amount;
length_ -= amount;
}
/**
* Adjust the tail pointer backwards to include less valid data.
*
* This moves the tail pointer backwards so that the last amount bytes are no
* longer considered valid data. The caller is responsible for ensuring that
* amount is less than or equal to the actual data length.
*
* This does not modify any actual data in the buffer.
*/
void trimEnd(uint32_t amount) {
CHECK(amount <= length_);
length_ -= amount;
}
/**
* Clear the buffer.
*
* Postcondition: headroom() == 0, length() == 0, tailroom() == capacity()
*/
void clear() {
data_ = writableBuffer();
length_ = 0;
}
/**
* Ensure that this buffer has at least minHeadroom headroom bytes and at
* least minTailroom tailroom bytes. The buffer must be writable
* (you must call unshare() before this, if necessary).
*
* Postcondition: headroom() >= minHeadroom, tailroom() >= minTailroom,
* the data (between data() and data() + length()) is preserved.
*/
void reserve(uint32_t minHeadroom, uint32_t minTailroom) {
// Maybe we don't need to do anything.
if (headroom() >= minHeadroom && tailroom() >= minTailroom) {
return;
}
// If the buffer is empty but we have enough total room (head + tail),
// move the data_ pointer around.
if (length() == 0 &&
headroom() + tailroom() >= minHeadroom + minTailroom) {
data_ = writableBuffer() + minHeadroom;
return;
}
// Bah, we have to do actual work.
reserveSlow(minHeadroom, minTailroom);
}
/**
* Return true if this IOBuf is part of a chain of multiple IOBufs, or false
* if this is the only IOBuf in its chain.
*/
bool isChained() const {
assert((next_ == this) == (prev_ == this));
return next_ != this;
}
/**
* Get the number of IOBufs in this chain.
*
* Beware that this method has to walk the entire chain.
* Use isChained() if you just want to check if this IOBuf is part of a chain
* or not.
*/
uint32_t countChainElements() const;
/**
* Get the length of all the data in this IOBuf chain.
*
* Beware that this method has to walk the entire chain.
*/
uint64_t computeChainDataLength() const;
/**
* Insert another IOBuf chain immediately before this IOBuf.
*
* For example, if there are two IOBuf chains (A, B, C) and (D, E, F),
* and B->prependChain(D) is called, the (D, E, F) chain will be subsumed
* and become part of the chain starting at A, which will now look like
* (A, D, E, F, B, C)
*
* Note that since IOBuf chains are circular, head->prependChain(other) can
* be used to append the other chain at the very end of the chain pointed to
* by head. For example, if there are two IOBuf chains (A, B, C) and
* (D, E, F), and A->prependChain(D) is called, the chain starting at A will
* now consist of (A, B, C, D, E, F)
*
* The elements in the specified IOBuf chain will become part of this chain,
* and will be owned by the head of this chain. When this chain is
* destroyed, all elements in the supplied chain will also be destroyed.
*
* For this reason, appendChain() only accepts an rvalue-reference to a
* unique_ptr(), to make it clear that it is taking ownership of the supplied
* chain. If you have a raw pointer, you can pass in a new temporary
* unique_ptr around the raw pointer. If you have an existing,
* non-temporary unique_ptr, you must call std::move(ptr) to make it clear
* that you are destroying the original pointer.
*/
void prependChain(std::unique_ptr<IOBuf>&& iobuf);
/**
* Append another IOBuf chain immediately after this IOBuf.
*
* For example, if there are two IOBuf chains (A, B, C) and (D, E, F),
* and B->appendChain(D) is called, the (D, E, F) chain will be subsumed
* and become part of the chain starting at A, which will now look like
* (A, B, D, E, F, C)
*
* The elements in the specified IOBuf chain will become part of this chain,
* and will be owned by the head of this chain. When this chain is
* destroyed, all elements in the supplied chain will also be destroyed.
*
* For this reason, appendChain() only accepts an rvalue-reference to a
* unique_ptr(), to make it clear that it is taking ownership of the supplied
* chain. If you have a raw pointer, you can pass in a new temporary
* unique_ptr around the raw pointer. If you have an existing,
* non-temporary unique_ptr, you must call std::move(ptr) to make it clear
* that you are destroying the original pointer.
*/
void appendChain(std::unique_ptr<IOBuf>&& iobuf) {
// Just use prependChain() on the next element in our chain
next_->prependChain(std::move(iobuf));
}
/**
* Remove this IOBuf from its current chain.
*
* Since ownership of all elements an IOBuf chain is normally maintained by
* the head of the chain, unlink() transfers ownership of this IOBuf from the
* chain and gives it to the caller. A new unique_ptr to the IOBuf is
* returned to the caller. The caller must store the returned unique_ptr (or
* call release() on it) to take ownership, otherwise the IOBuf will be
* immediately destroyed.
*
* Since unlink transfers ownership of the IOBuf to the caller, be careful
* not to call unlink() on the head of a chain if you already maintain
* ownership on the head of the chain via other means. The pop() method
* is a better choice for that situation.
*/
std::unique_ptr<IOBuf> unlink() {
next_->prev_ = prev_;
prev_->next_ = next_;
prev_ = this;
next_ = this;
return std::unique_ptr<IOBuf>(this);
}
/**
* Remove this IOBuf from its current chain and return a unique_ptr to
* the IOBuf that formerly followed it in the chain.
*/
std::unique_ptr<IOBuf> pop() {
IOBuf *next = next_;
next_->prev_ = prev_;
prev_->next_ = next_;
prev_ = this;
next_ = this;
return std::unique_ptr<IOBuf>((next == this) ? NULL : next);
}
/**
* Remove a subchain from this chain.
*
* Remove the subchain starting at head and ending at tail from this chain.
*
* Returns a unique_ptr pointing to head. (In other words, ownership of the
* head of the subchain is transferred to the caller.) If the caller ignores
* the return value and lets the unique_ptr be destroyed, the subchain will
* be immediately destroyed.
*
* The subchain referenced by the specified head and tail must be part of the
* same chain as the current IOBuf, but must not contain the current IOBuf.
* However, the specified head and tail may be equal to each other (i.e.,
* they may be a subchain of length 1).
*/
std::unique_ptr<IOBuf> separateChain(IOBuf* head, IOBuf* tail) {
assert(head != this);
assert(tail != this);
head->prev_->next_ = tail->next_;
tail->next_->prev_ = head->prev_;
head->prev_ = tail;
tail->next_ = head;
return std::unique_ptr<IOBuf>(head);
}
/**
* Return true if at least one of the IOBufs in this chain are shared,
* or false if all of the IOBufs point to unique buffers.
*
* Use isSharedOne() to only check this IOBuf rather than the entire chain.
*/
bool isShared() const {
const IOBuf* current = this;
while (true) {
if (current->isSharedOne()) {
return true;
}
current = current->next_;
if (current == this) {
return false;
}
}
}
/**
* Return true if other IOBufs are also pointing to the buffer used by this
* IOBuf, and false otherwise.
*
* If this IOBuf points at a buffer owned by another (non-IOBuf) part of the
* code (i.e., if the IOBuf was created using wrapBuffer(), or was cloned
* from such an IOBuf), it is always considered shared.
*
* This only checks the current IOBuf, and not other IOBufs in the chain.
*/
bool isSharedOne() const {
// If this is a user-owned buffer, it is always considered shared
if (flags_ & kFlagUserOwned) {
return true;
}
if (flags_ & kFlagExt) {
return ext_.sharedInfo->refcount.load(std::memory_order_acquire) > 1;
} else {
return false;
}
}
/**
* Ensure that this IOBuf has a unique buffer that is not shared by other
* IOBufs.
*
* unshare() operates on an entire chain of IOBuf objects. If the chain is
* shared, it may also coalesce the chain when making it unique. If the
* chain is coalesced, subsequent IOBuf objects in the current chain will be
* automatically deleted.
*
* Note that buffers owned by other (non-IOBuf) users are automatically
* considered shared.
*
* Throws std::bad_alloc on error. On error the IOBuf chain will be
* unmodified.
*
* Currently unshare may also throw std::overflow_error if it tries to
* coalesce. (TODO: In the future it would be nice if unshare() were smart
* enough not to coalesce the entire buffer if the data is too large.
* However, in practice this seems unlikely to become an issue.)
*/
void unshare() {
if (isChained()) {
unshareChained();
} else {
unshareOne();
}
}
/**
* Ensure that this IOBuf has a unique buffer that is not shared by other
* IOBufs.
*
* unshareOne() operates on a single IOBuf object. This IOBuf will have a
* unique buffer after unshareOne() returns, but other IOBufs in the chain
* may still be shared after unshareOne() returns.
*
* Throws std::bad_alloc on error. On error the IOBuf will be unmodified.
*/
void unshareOne() {
if (isSharedOne()) {
unshareOneSlow();
}
}
/**
* Coalesce this IOBuf chain into a single buffer.
*
* This method moves all of the data in this IOBuf chain into a single
* contiguous buffer, if it is not already in one buffer. After coalesce()
* returns, this IOBuf will be a chain of length one. Other IOBufs in the
* chain will be automatically deleted.
*
* After coalescing, the IOBuf will have at least as much headroom as the
* first IOBuf in the chain, and at least as much tailroom as the last IOBuf
* in the chain.
*
* Throws std::bad_alloc on error. On error the IOBuf chain will be
* unmodified. Throws std::overflow_error if the length of the entire chain
* larger than can be described by a uint32_t capacity.
*/
void coalesce() {
if (!isChained()) {
return;
}
coalesceSlow();
}
/**
* Ensure that this chain has at least maxLength bytes available as a
* contiguous memory range.
*
* This method coalesces whole buffers in the chain into this buffer as
* necessary until this buffer's length() is at least maxLength.
*
* After coalescing, the IOBuf will have at least as much headroom as the
* first IOBuf in the chain, and at least as much tailroom as the last IOBuf
* that was coalesced.
*
* Throws std::bad_alloc on error. On error the IOBuf chain will be
* unmodified. Throws std::overflow_error if the length of the coalesced
* portion of the chain is larger than can be described by a uint32_t
* capacity. (Although maxLength is uint32_t, gather() doesn't split
* buffers, so coalescing whole buffers may result in a capacity that can't
* be described in uint32_t.
*
* Upon return, either enough of the chain was coalesced into a contiguous
* region, or the entire chain was coalesced. That is,
* length() >= maxLength || !isChained() is true.
*/
void gather(uint32_t maxLength) {
if (!isChained() || length_ >= maxLength) {
return;
}
coalesceSlow(maxLength);
}
/**
* Return a new IOBuf chain sharing the same data as this chain.
*
* The new IOBuf chain will normally point to the same underlying data
* buffers as the original chain. (The one exception to this is if some of
* the IOBufs in this chain contain small internal data buffers which cannot
* be shared.)
*/
std::unique_ptr<IOBuf> clone() const;
/**
* Return a new IOBuf with the same data as this IOBuf.
*
* The new IOBuf returned will not be part of a chain (even if this IOBuf is
* part of a larger chain).
*/
std::unique_ptr<IOBuf> cloneOne() const;
// Overridden operator new and delete.
// These directly use malloc() and free() to allocate the space for IOBuf
// objects. This is needed since IOBuf::create() manually uses malloc when
// allocating IOBuf objects with an internal buffer.
void* operator new(size_t size);
void* operator new(size_t size, void* ptr);
void operator delete(void* ptr);
/**
* Destructively convert this IOBuf to a fbstring efficiently.
* We rely on fbstring's AcquireMallocatedString constructor to
* transfer memory.
*/
fbstring moveToFbString();
private:
enum FlagsEnum {
kFlagExt = 0x1,
kFlagUserOwned = 0x2,
kFlagFreeSharedInfo = 0x4,
};
// Values for the ExternalBuf type field.
// We currently don't really use this for anything, other than to have it
// around for debugging purposes. We store it at the moment just because we
// have the 4 extra bytes in the ExternalBuf struct that would just be
// padding otherwise.
enum ExtBufTypeEnum {
kExtAllocated = 0,
kExtUserSupplied = 1,
kExtUserOwned = 2,
};
struct SharedInfo {
SharedInfo();
SharedInfo(FreeFunction fn, void* arg);
// A pointer to a function to call to free the buffer when the refcount
// hits 0. If this is NULL, free() will be used instead.
FreeFunction freeFn;
void* userData;
std::atomic<uint32_t> refcount;
};
struct ExternalBuf {
uint32_t capacity;
uint32_t type;
uint8_t* buf;
// SharedInfo may be NULL if kFlagUserOwned is set. It is non-NULL
// in all other cases.
SharedInfo* sharedInfo;
};
struct InternalBuf {
uint8_t buf[] __attribute__((aligned));
};
// The maximum size for an IOBuf object, including any internal data buffer
static const uint32_t kMaxIOBufSize = 256;
static const uint32_t kMaxInternalDataSize;
// Forbidden copy constructor and assignment opererator
IOBuf(IOBuf const &);
IOBuf& operator=(IOBuf const &);
/**
* Create a new IOBuf with internal data.
*
* end is a pointer to the end of the IOBuf's internal data buffer.
*/
explicit IOBuf(uint8_t* end);
/**
* Create a new IOBuf pointing to an external buffer.
*
* The caller is responsible for holding a reference count for this new
* IOBuf. The IOBuf constructor does not automatically increment the
* reference count.
*/
IOBuf(ExtBufTypeEnum type, uint32_t flags,
uint8_t* buf, uint32_t capacity,
uint8_t* data, uint32_t length,
SharedInfo* sharedInfo);
void unshareOneSlow();
void unshareChained();
void coalesceSlow(size_t maxLength=std::numeric_limits<size_t>::max());
// newLength must be the entire length of the buffers between this and
// end (no truncation)
void coalesceAndReallocate(
size_t newHeadroom,
size_t newLength,
IOBuf* end,
size_t newTailroom);
void decrementRefcount();
void reserveSlow(uint32_t minHeadroom, uint32_t minTailroom);
static size_t goodExtBufferSize(uint32_t minCapacity);
static void initExtBuffer(uint8_t* buf, size_t mallocSize,
SharedInfo** infoReturn,
uint32_t* capacityReturn);
static void allocExtBuffer(uint32_t minCapacity,
uint8_t** bufReturn,
SharedInfo** infoReturn,
uint32_t* capacityReturn);
/*
* Member variables
*/
/*
* Links to the next and the previous IOBuf in this chain.
*
* The chain is circularly linked (the last element in the chain points back
* at the head), and next_ and prev_ can never be NULL. If this IOBuf is the
* only element in the chain, next_ and prev_ will both point to this.
*/
IOBuf* next_;
IOBuf* prev_;
/*
* A pointer to the start of the data referenced by this IOBuf, and the
* length of the data.
*
* This may refer to any subsection of the actual buffer capacity.
*/
uint8_t* data_;
uint32_t length_;
uint32_t flags_;
union {
ExternalBuf ext_;
InternalBuf int_;
};
struct DeleterBase {
virtual ~DeleterBase() { }
virtual void dispose(void* p) = 0;
};
template <class UniquePtr>
struct UniquePtrDeleter : public DeleterBase {
typedef typename UniquePtr::pointer Pointer;
typedef typename UniquePtr::deleter_type Deleter;
explicit UniquePtrDeleter(Deleter deleter) : deleter_(std::move(deleter)){ }
void dispose(void* p) {
try {
deleter_(static_cast<Pointer>(p));
delete this;
} catch (...) {
abort();
}
}
private:
Deleter deleter_;
};
static void freeUniquePtrBuffer(void* ptr, void* userData) {
static_cast<DeleterBase*>(userData)->dispose(ptr);
}
};
template <class UniquePtr>
typename std::enable_if<detail::IsUniquePtrToSL<UniquePtr>::value,
std::unique_ptr<IOBuf>>::type
IOBuf::takeOwnership(UniquePtr&& buf, size_t count) {
size_t size = count * sizeof(typename UniquePtr::element_type);
CHECK_LT(size, size_t(std::numeric_limits<uint32_t>::max()));
auto deleter = new UniquePtrDeleter<UniquePtr>(buf.get_deleter());
return takeOwnership(buf.release(),
size,
&IOBuf::freeUniquePtrBuffer,
deleter);
}
inline std::unique_ptr<IOBuf> IOBuf::copyBuffer(
const void* data, uint32_t size, uint32_t headroom,
uint32_t minTailroom) {
uint32_t capacity = headroom + size + minTailroom;
std::unique_ptr<IOBuf> buf = create(capacity);
buf->advance(headroom);
memcpy(buf->writableData(), data, size);
buf->append(size);
return buf;
}
inline std::unique_ptr<IOBuf> IOBuf::copyBuffer(const std::string& buf,
uint32_t headroom,
uint32_t minTailroom) {
return copyBuffer(buf.data(), buf.size(), headroom, minTailroom);
}
inline std::unique_ptr<IOBuf> IOBuf::maybeCopyBuffer(const std::string& buf,
uint32_t headroom,
uint32_t minTailroom) {
if (buf.empty()) {
return nullptr;
}
return copyBuffer(buf.data(), buf.size(), headroom, minTailroom);
}
} // folly
#endif // FOLLY_IO_IOBUF_H_
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/IOBufQueue.h"
#include <string.h>
#include <stdexcept>
using std::make_pair;
using std::pair;
using std::unique_ptr;
namespace {
using folly::IOBuf;
const size_t MIN_ALLOC_SIZE = 2000;
const size_t MAX_ALLOC_SIZE = 8000; // Must fit within a uint32_t
/**
* Convenience function to append chain src to chain dst.
*/
void
appendToChain(unique_ptr<IOBuf>& dst, unique_ptr<IOBuf>&& src) {
if (dst == NULL) {
dst = std::move(src);
} else {
dst->prev()->appendChain(std::move(src));
}
}
} // anonymous namespace
namespace folly {
IOBufQueue::IOBufQueue(const Options& options)
: options_(options),
chainLength_(0) {
}
IOBufQueue::IOBufQueue(IOBufQueue&& other)
: options_(other.options_),
chainLength_(other.chainLength_),
head_(std::move(other.head_)) {
other.chainLength_ = 0;
}
IOBufQueue& IOBufQueue::operator=(IOBufQueue&& other) {
if (&other != this) {
options_ = other.options_;
chainLength_ = other.chainLength_;
head_ = std::move(other.head_);
other.chainLength_ = 0;
}
return *this;
}
std::pair<void*, uint32_t>
IOBufQueue::headroom() {
if (head_) {
return std::make_pair(head_->writableBuffer(), head_->headroom());
} else {
return std::make_pair(nullptr, 0);
}
}
void
IOBufQueue::markPrepended(uint32_t n) {
if (n == 0) {
return;
}
assert(head_);
head_->prepend(n);
if (options_.cacheChainLength) {
chainLength_ += n;
}
}
void
IOBufQueue::prepend(const void* buf, uint32_t n) {
auto p = headroom();
if (n > p.second) {
throw std::overflow_error("Not enough room to prepend");
}
memcpy(static_cast<char*>(p.first) + p.second - n, buf, n);
markPrepended(n);
}
void
IOBufQueue::append(unique_ptr<IOBuf>&& buf) {
if (!buf) {
return;
}
if (options_.cacheChainLength) {
chainLength_ += buf->computeChainDataLength();
}
appendToChain(head_, std::move(buf));
}
void
IOBufQueue::append(IOBufQueue& other) {
if (!other.head_) {
return;
}
if (options_.cacheChainLength) {
if (other.options_.cacheChainLength) {
chainLength_ += other.chainLength_;
} else {
chainLength_ += other.head_->computeChainDataLength();
}
}
appendToChain(head_, std::move(other.head_));
other.chainLength_ = 0;
}
void
IOBufQueue::append(const void* buf, size_t len) {
auto src = static_cast<const uint8_t*>(buf);
while (len != 0) {
if ((head_ == NULL) || head_->prev()->isSharedOne() ||
(head_->prev()->tailroom() == 0)) {
appendToChain(head_, std::move(
IOBuf::create(std::max(MIN_ALLOC_SIZE,
std::min(len, MAX_ALLOC_SIZE)))));
}
IOBuf* last = head_->prev();
uint32_t copyLen = std::min(len, (size_t)last->tailroom());
memcpy(last->writableTail(), src, copyLen);
src += copyLen;
last->append(copyLen);
if (options_.cacheChainLength) {
chainLength_ += copyLen;
}
len -= copyLen;
}
}
void
IOBufQueue::wrapBuffer(const void* buf, size_t len, uint32_t blockSize) {
auto src = static_cast<const uint8_t*>(buf);
while (len != 0) {
size_t n = std::min(len, size_t(blockSize));
append(IOBuf::wrapBuffer(src, n));
src += n;
len -= n;
}
}
pair<void*,uint32_t>
IOBufQueue::preallocate(uint32_t min, uint32_t newAllocationSize,
uint32_t max) {
if (head_ != NULL) {
// If there's enough space left over at the end of the queue, use that.
IOBuf* last = head_->prev();
if (!last->isSharedOne()) {
uint32_t avail = last->tailroom();
if (avail >= min) {
return make_pair(last->writableTail(), std::min(max, avail));
}
}
}
// Allocate a new buffer of the requested max size.
unique_ptr<IOBuf> newBuf(IOBuf::create(std::max(min, newAllocationSize)));
appendToChain(head_, std::move(newBuf));
IOBuf* last = head_->prev();
return make_pair(last->writableTail(),
std::min(max, last->tailroom()));
}
void
IOBufQueue::postallocate(uint32_t n) {
head_->prev()->append(n);
if (options_.cacheChainLength) {
chainLength_ += n;
}
}
unique_ptr<IOBuf>
IOBufQueue::split(size_t n) {
unique_ptr<IOBuf> result;
while (n != 0) {
if (head_ == NULL) {
throw std::underflow_error(
"Attempt to remove more bytes than are present in IOBufQueue");
} else if (head_->length() <= n) {
n -= head_->length();
if (options_.cacheChainLength) {
chainLength_ -= head_->length();
}
unique_ptr<IOBuf> remainder = head_->pop();
appendToChain(result, std::move(head_));
head_ = std::move(remainder);
} else {
unique_ptr<IOBuf> clone = head_->cloneOne();
clone->trimEnd(clone->length() - n);
appendToChain(result, std::move(clone));
head_->trimStart(n);
if (options_.cacheChainLength) {
chainLength_ -= n;
}
break;
}
}
return std::move(result);
}
void IOBufQueue::trimStart(size_t amount) {
while (amount > 0) {
if (!head_) {
throw std::underflow_error(
"Attempt to trim more bytes than are present in IOBufQueue");
}
if (head_->length() > amount) {
head_->trimStart(amount);
if (options_.cacheChainLength) {
chainLength_ -= amount;
}
break;
}
amount -= head_->length();
if (options_.cacheChainLength) {
chainLength_ -= head_->length();
}
head_ = head_->pop();
}
}
void IOBufQueue::trimEnd(size_t amount) {
while (amount > 0) {
if (!head_) {
throw std::underflow_error(
"Attempt to trim more bytes than are present in IOBufQueue");
}
if (head_->prev()->length() > amount) {
head_->prev()->trimEnd(amount);
if (options_.cacheChainLength) {
chainLength_ -= amount;
}
break;
}
amount -= head_->prev()->length();
if (options_.cacheChainLength) {
chainLength_ -= head_->prev()->length();
}
unique_ptr<IOBuf> b = head_->prev()->unlink();
// Null queue if we unlinked the head.
if (b.get() == head_.get()) {
head_.reset();
}
}
}
} // folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_IO_IOBUF_QUEUE_H
#define FOLLY_IO_IOBUF_QUEUE_H
#include "folly/experimental/io/IOBuf.h"
#include <stdexcept>
#include <string>
namespace folly {
/**
* An IOBufQueue encapsulates a chain of IOBufs and provides
* convenience functions to append data to the back of the chain
* and remove data from the front.
*
* You may also prepend data into the headroom of the first buffer in the
* chain, if any.
*/
class IOBufQueue {
public:
struct Options {
Options() : cacheChainLength(false) { }
bool cacheChainLength;
};
/**
* Commonly used Options, currently the only possible value other than
* the default.
*/
static Options cacheChainLength() {
Options options;
options.cacheChainLength = true;
return options;
}
explicit IOBufQueue(const Options& options = Options());
/**
* Return a space to prepend bytes and the amount of headroom available.
*/
std::pair<void*, uint32_t> headroom();
/**
* Indicate that n bytes from the headroom have been used.
*/
void markPrepended(uint32_t n);
/**
* Prepend an existing range; throws std::overflow_error if not enough
* room.
*/
void prepend(const void* buf, uint32_t n);
/**
* Add a buffer or buffer chain to the end of this queue. The
* queue takes ownership of buf.
*/
void append(std::unique_ptr<folly::IOBuf>&& buf);
/**
* Add a queue to the end of this queue. The queue takes ownership of
* all buffers from the other queue.
*/
void append(IOBufQueue& other);
void append(IOBufQueue&& other) {
append(other); // call lvalue reference overload, above
}
/**
* Copy len bytes, starting at buf, to the end of this queue.
* The caller retains ownership of the source data.
*/
void append(const void* buf, size_t len);
/**
* Copy a string to the end of this queue.
* The caller retains ownership of the source data.
*/
void append(const std::string& buf) {
append(buf.data(), buf.length());
}
/**
* Append a chain of IOBuf objects that point to consecutive regions
* within buf.
*
* Just like IOBuf::wrapBuffer, this should only be used when the caller
* knows ahead of time and can ensure that all IOBuf objects that will point
* to this buffer will be destroyed before the buffer itself is destroyed;
* all other caveats from wrapBuffer also apply.
*
* Every buffer except for the last will wrap exactly blockSize bytes.
* Importantly, this method may be used to wrap buffers larger than 4GB.
*/
void wrapBuffer(const void* buf, size_t len,
uint32_t blockSize=(1U << 31)); // default block size: 2GB
/**
* Obtain a writable block of contiguous bytes at the end of this
* queue, allocating more space if necessary. The amount of space
* reserved will be at least min. If min contiguous space is not
* available at the end of the queue, and IOBuf with size newAllocationSize
* is appended to the chain and returned. The actual available space
* may be larger than newAllocationSize, but will be truncated to max,
* if specified.
*
* If the caller subsequently writes anything into the returned space,
* it must call the postallocate() method.
*
* @return The starting address of the block and the length in bytes.
*
* @note The point of the preallocate()/postallocate() mechanism is
* to support I/O APIs such as Thrift's TAsyncSocket::ReadCallback
* that request a buffer from the application and then, in a later
* callback, tell the application how much of the buffer they've
* filled with data.
*/
std::pair<void*,uint32_t> preallocate(
uint32_t min, uint32_t newAllocationSize,
uint32_t max = std::numeric_limits<uint32_t>::max());
/**
* Tell the queue that the caller has written data into the first n
* bytes provided by the previous preallocate() call.
*
* @note n should be less than or equal to the size returned by
* preallocate(). If n is zero, the caller may skip the call
* to postallocate(). If n is nonzero, the caller must not
* invoke any other non-const methods on this IOBufQueue between
* the call to preallocate and the call to postallocate().
*/
void postallocate(uint32_t n);
/**
* Obtain a writable block of n contiguous bytes, allocating more space
* if necessary, and mark it as used. The caller can fill it later.
*/
void* allocate(uint32_t n) {
void* p = preallocate(n, n).first;
postallocate(n);
return p;
}
/**
* Split off the first n bytes of the queue into a separate IOBuf chain,
* and transfer ownership of the new chain to the caller. The IOBufQueue
* retains ownership of everything after the split point.
*
* @warning If the split point lies in the middle of some IOBuf within
* the chain, this function may, as an implementation detail,
* clone that IOBuf.
*
* @throws std::underflow_error if n exceeds the number of bytes
* in the queue.
*/
std::unique_ptr<folly::IOBuf> split(size_t n);
/**
* Similar to IOBuf::trimStart, but works on the whole queue. Will
* pop off buffers that have been completely trimmed.
*/
void trimStart(size_t amount);
/**
* Similar to IOBuf::trimEnd, but works on the whole queue. Will
* pop off buffers that have been completely trimmed.
*/
void trimEnd(size_t amount);
/**
* Transfer ownership of the queue's entire IOBuf chain to the caller.
*/
std::unique_ptr<folly::IOBuf> move() {
chainLength_ = 0;
return std::move(head_);
}
/**
* Access
*/
const folly::IOBuf* front() const {
return head_.get();
}
/**
* Total chain length, only valid if cacheLength was specified in the
* constructor.
*/
size_t chainLength() const {
if (!options_.cacheChainLength) {
throw std::invalid_argument("IOBufQueue: chain length not cached");
}
return chainLength_;
}
const Options& options() const {
return options_;
}
/** Movable */
IOBufQueue(IOBufQueue&&);
IOBufQueue& operator=(IOBufQueue&&);
private:
static const size_t kChainLengthNotCached = (size_t)-1;
/** Not copyable */
IOBufQueue(const IOBufQueue&) = delete;
IOBufQueue& operator=(const IOBufQueue&) = delete;
Options options_;
size_t chainLength_;
/** Everything that has been appended but not yet discarded or moved out */
std::unique_ptr<folly::IOBuf> head_;
};
} // folly
#endif // FOLLY_IO_IOBUF_QUEUE_H
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_IO_STREAM_H_
#error This file may only be included from Stream.h
#endif
#include <string.h>
#include <glog/logging.h>
namespace folly {
template <class Stream>
InputByteStreamSplitter<Stream>::InputByteStreamSplitter(
char delimiter, Stream stream)
: done_(false),
delimiter_(delimiter),
stream_(std::move(stream)) {
}
template <class Stream>
bool InputByteStreamSplitter<Stream>::operator()(ByteRange& chunk) {
DCHECK(!buffer_ || buffer_->length() == 0);
chunk.clear();
if (rest_.empty()) {
if (done_) {
return false;
} else if (!stream_(rest_)) {
done_ = true;
return false;
}
}
auto p = static_cast<const unsigned char*>(memchr(rest_.data(), delimiter_,
rest_.size()));
if (p) {
chunk.assign(rest_.data(), p);
rest_.assign(p + 1, rest_.end());
return true;
}
// Incomplete line read, copy to buffer
if (!buffer_) {
static const size_t kDefaultLineSize = 256;
// Arbitrarily assume that we have half of a line in rest_, and
// get enough room for twice that.
buffer_ = IOBuf::create(std::max(kDefaultLineSize, 2 * rest_.size()));
} else {
buffer_->reserve(0, rest_.size());
}
memcpy(buffer_->writableTail(), rest_.data(), rest_.size());
buffer_->append(rest_.size());
while (stream_(rest_)) {
auto p = static_cast<const unsigned char*>(
memchr(rest_.data(), delimiter_, rest_.size()));
if (p) {
// Copy everything up to the delimiter and return it
size_t n = p - rest_.data();
buffer_->reserve(0, n);
memcpy(buffer_->writableTail(), rest_.data(), n);
buffer_->append(n);
chunk.reset(buffer_->data(), buffer_->length());
buffer_->trimStart(buffer_->length());
rest_.assign(p + 1, rest_.end());
return true;
}
// Nope, copy the entire chunk that we read
buffer_->reserve(0, rest_.size());
memcpy(buffer_->writableTail(), rest_.data(), rest_.size());
buffer_->append(rest_.size());
}
// Incomplete last line
done_ = true;
rest_.clear();
chunk.reset(buffer_->data(), buffer_->length());
buffer_->trimStart(buffer_->length());
return true;
}
} // namespace folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/Stream.h"
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdexcept>
#include <system_error>
#include "folly/String.h"
namespace folly {
FileInputByteStream::FileInputByteStream(int fd, bool ownsFd, size_t bufferSize)
: fd_(fd),
ownsFd_(ownsFd),
buffer_(IOBuf::create(bufferSize)) {
}
FileInputByteStream::FileInputByteStream(int fd, bool ownsFd,
std::unique_ptr<IOBuf>&& buffer)
: fd_(fd),
ownsFd_(ownsFd),
buffer_(std::move(buffer)) {
buffer_->clear();
}
bool FileInputByteStream::operator()(ByteRange& chunk) {
ssize_t n = ::read(fd_, buffer_->writableTail(), buffer_->capacity());
if (n == -1) {
throw std::system_error(errno, std::system_category(), "read failed");
}
chunk.reset(buffer_->tail(), n);
return (n != 0);
}
FileInputByteStream::FileInputByteStream(FileInputByteStream&& other)
: fd_(other.fd_),
ownsFd_(other.ownsFd_),
buffer_(std::move(other.buffer_)) {
other.fd_ = -1;
other.ownsFd_ = false;
}
FileInputByteStream& FileInputByteStream::operator=(
FileInputByteStream&& other) {
if (&other != this) {
closeNoThrow();
fd_ = other.fd_;
ownsFd_ = other.ownsFd_;
buffer_ = std::move(other.buffer_);
other.fd_ = -1;
other.ownsFd_ = false;
}
return *this;
}
FileInputByteStream::~FileInputByteStream() {
closeNoThrow();
}
void FileInputByteStream::closeNoThrow() {
if (!ownsFd_) {
return;
}
ownsFd_ = false;
if (::close(fd_) == -1) {
PLOG(ERROR) << "close failed";
}
}
InputByteStreamSplitter<FileInputByteStream> byLine(
const char* fileName, char delim) {
int fd = ::open(fileName, O_RDONLY);
if (fd == -1) {
throw std::system_error(errno, std::system_category(), "open failed");
}
return makeInputByteStreamSplitter(delim, FileInputByteStream(fd, true));
}
} // namespace folly
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_IO_STREAM_H_
#define FOLLY_IO_STREAM_H_
#include <boost/iterator/iterator_facade.hpp>
#include <glog/logging.h>
#include "folly/Range.h"
#include "folly/FBString.h"
#include "folly/experimental/io/IOBuf.h"
namespace folly {
/**
* An InputByteStream is a functional object with the following signature:
*
* bool operator()(ByteRange& data);
*
* Input byte streams must be movable.
*
* The stream returns false at EOF; otherwise, it returns true and sets data to
* the next chunk of data from the stream. The memory that data points to must
* remain valid until the next call to the stream. In case of error, the
* stream throws an exception.
*
* The meaning of a "chunk" is left up to the stream implementation. Some
* streams return chunks limited to the size of an internal buffer. Other
* streams return the entire input as one (potentially huge) ByteRange.
* Others assign meaning to chunks: StreamSplitter returns "lines" -- sequences
* of bytes between delimiters. This ambiguity is intentional; resolving it
* would significantly increase the complexity of the code.
*
* An OutputByteStream is an object with the following signature:
*
* void operator()(ByteRange data);
* void close();
*
* Output byte streams must be movable.
*
* The stream appends a chunk of data to the stream when calling operator().
* close() closes the stream, allowing us to detect any errors before
* destroying the stream object (to avoid throwing exceptions from the
* destructor). The destructor must close the stream if close() was not
* explicitly called, and abort the program if closing the stream caused
* an error.
*
* Just like with input byte streams, the meaning of a "chunk" is left up
* to the stream implementation. Some streams will just append all chunks
* as given; others might assign meaning to chunks and (for example) append
* delimiters between chunks.
*/
template <class Stream> class InputByteStreamIterator;
/**
* Convenient base class template to derive all streams from; provides begin()
* and end() for iterator access. This class makes use of the curriously
* recurring template pattern; your stream class S may derive from
* InputByteStreamBase<S>.
*
* Deriving from InputByteStreamBase<S> is not required, but is convenient.
*/
template <class Derived>
class InputByteStreamBase {
public:
InputByteStreamIterator<Derived> begin() {
return InputByteStreamIterator<Derived>(static_cast<Derived&>(*this));
}
InputByteStreamIterator<Derived> end() {
return InputByteStreamIterator<Derived>();
}
InputByteStreamBase() { }
InputByteStreamBase(InputByteStreamBase&&) = default;
InputByteStreamBase& operator=(InputByteStreamBase&&) = default;
private:
InputByteStreamBase(const InputByteStreamBase&) = delete;
InputByteStreamBase& operator=(const InputByteStreamBase&) = delete;
};
/**
* Stream iterator
*/
template <class Stream>
class InputByteStreamIterator
: public boost::iterator_facade<
InputByteStreamIterator<Stream>,
const ByteRange,
boost::single_pass_traversal_tag> {
public:
InputByteStreamIterator() : stream_(nullptr) { }
explicit InputByteStreamIterator(Stream& stream) : stream_(&stream) {
increment();
}
private:
friend class boost::iterator_core_access;
void increment() {
DCHECK(stream_);
if (stream_ && !(*stream_)(chunk_)) {
stream_ = nullptr;
}
}
// This is a single pass iterator, so all we care about is that
// equal forms an equivalence class on the subset of iterators that it's
// defined on. In our case, only identical (same object) iterators and
// past-the-end iterators compare equal. (so that it != end() works)
bool equal(const InputByteStreamIterator& other) const {
return (this == &other) || (!stream_ && !other.stream_);
}
const ByteRange& dereference() const {
DCHECK(stream_);
return chunk_;
}
Stream* stream_;
ByteRange chunk_;
};
/**
* Stream that read()s from a file.
*/
class FileInputByteStream : public InputByteStreamBase<FileInputByteStream> {
public:
static const size_t kDefaultBufferSize = 4096;
explicit FileInputByteStream(int fd,
bool ownsFd = false,
size_t bufferSize = kDefaultBufferSize);
FileInputByteStream(int fd, bool ownsFd, std::unique_ptr<IOBuf>&& buffer);
FileInputByteStream(FileInputByteStream&& other);
FileInputByteStream& operator=(FileInputByteStream&& other);
~FileInputByteStream();
bool operator()(ByteRange& chunk);
private:
void closeNoThrow();
int fd_;
bool ownsFd_;
std::unique_ptr<IOBuf> buffer_;
};
/**
* Split a stream on a delimiter. Returns "lines" between delimiters;
* the delimiters are not included in the returned string.
*
* Note that the InputByteStreamSplitter acts as a stream itself, and you can
* iterate over it.
*/
template <class Stream>
class InputByteStreamSplitter
: public InputByteStreamBase<InputByteStreamSplitter<Stream>> {
public:
InputByteStreamSplitter(char delimiter, Stream stream);
bool operator()(ByteRange& chunk);
InputByteStreamSplitter(InputByteStreamSplitter&&) = default;
InputByteStreamSplitter& operator=(InputByteStreamSplitter&&) = default;
private:
InputByteStreamSplitter(const InputByteStreamSplitter&) = delete;
InputByteStreamSplitter& operator=(const InputByteStreamSplitter&) = delete;
bool done_;
char delimiter_;
Stream stream_;
std::unique_ptr<IOBuf> buffer_;
ByteRange rest_;
};
/**
* Shortcut to create a stream splitter around a stream and deduce
* the type of the template argument.
*/
template <class Stream>
InputByteStreamSplitter<Stream> makeInputByteStreamSplitter(
char delimiter, Stream stream) {
return InputByteStreamSplitter<Stream>(delimiter, std::move(stream));
}
/**
* Create a stream that splits a file into chunks (default: lines, with
* '\n' as the delimiter)
*/
InputByteStreamSplitter<FileInputByteStream> byLine(
const char* fileName, char delim='\n');
// overload for std::string
inline InputByteStreamSplitter<FileInputByteStream> byLine(
const std::string& fileName, char delim='\n') {
return byLine(fileName.c_str(), delim);
}
// overload for fbstring
inline InputByteStreamSplitter<FileInputByteStream> byLine(
const fbstring& fileName, char delim='\n') {
return byLine(fileName.c_str(), delim);
}
} // namespace folly
#include "folly/experimental/io/Stream-inl.h"
#endif /* FOLLY_IO_STREAM_H_ */
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#ifndef FOLLY_IO_TYPEDIOBUF_H_
#define FOLLY_IO_TYPEDIOBUF_H_
#include <algorithm>
#include <iterator>
#include <type_traits>
#include "folly/experimental/io/IOBuf.h"
namespace folly {
/**
* Wrapper class to handle a IOBuf as a typed buffer (to a standard layout
* class).
*
* This class punts on alignment, and assumes that you know what you're doing.
*
* All methods are wrappers around the corresponding IOBuf methods. The
* TypedIOBuf object is stateless, so it's perfectly okay to access the
* underlying IOBuf in between TypedIOBuf method calls.
*/
template <class T>
class TypedIOBuf {
static_assert(std::is_standard_layout<T>::value, "must be standard layout");
public:
typedef T value_type;
typedef value_type& reference;
typedef const value_type& const_reference;
typedef uint32_t size_type;
typedef value_type* iterator;
typedef const value_type* const_iterator;
explicit TypedIOBuf(IOBuf* buf) : buf_(buf) { }
IOBuf* ioBuf() {
return buf_;
}
const IOBuf* ioBuf() const {
return buf_;
}
bool empty() const {
return buf_->empty();
}
const T* data() const {
return cast(buf_->data());
}
T* writableData() {
return cast(buf_->writableData());
}
const T* tail() const {
return cast(buf_->tail());
}
T* writableTail() {
return cast(buf_->writableTail());
}
uint32_t length() const {
return sdiv(buf_->length());
}
uint32_t size() const { return length(); }
uint32_t headroom() const {
return sdiv(buf_->headroom());
}
uint32_t tailroom() const {
return sdiv(buf_->tailroom());
}
const T* buffer() const {
return cast(buf_->buffer());
}
T* writableBuffer() {
return cast(buf_->writableBuffer());
}
const T* bufferEnd() const {
return cast(buf_->bufferEnd());
}
uint32_t capacity() const {
return sdiv(buf_->capacity());
}
void advance(uint32_t n) {
buf_->advance(smul(n));
}
void retreat(uint32_t n) {
buf_->retreat(smul(n));
}
void prepend(uint32_t n) {
buf_->prepend(smul(n));
}
void append(uint32_t n) {
buf_->append(smul(n));
}
void trimStart(uint32_t n) {
buf_->trimStart(smul(n));
}
void trimEnd(uint32_t n) {
buf_->trimEnd(smul(n));
}
void clear() {
buf_->clear();
}
void reserve(uint32_t minHeadroom, uint32_t minTailroom) {
buf_->reserve(smul(minHeadroom), smul(minTailroom));
}
void reserve(uint32_t minTailroom) { reserve(0, minTailroom); }
const T* cbegin() const { return data(); }
const T* cend() const { return tail(); }
const T* begin() const { return cbegin(); }
const T* end() const { return cend(); }
T* begin() { return writableData(); }
T* end() { return writableTail(); }
const T& front() const {
assert(!empty());
return *begin();
}
T& front() {
assert(!empty());
return *begin();
}
const T& back() const {
assert(!empty());
return end()[-1];
}
T& back() {
assert(!empty());
return end()[-1];
}
/**
* Simple wrapper to make it easier to treat this TypedIOBuf as an array of
* T.
*/
const T& operator[](ssize_t idx) const {
assert(idx >= 0 && idx < length());
return data()[idx];
}
/**
* Append one element.
*/
void push(const T& data) {
push(&data, &data + 1);
}
void push_back(const T& data) { push(data); }
/**
* Append multiple elements in a sequence; will call distance().
*/
template <class IT>
void push(IT begin, IT end) {
auto n = std::distance(begin, end);
reserve(headroom(), n);
std::copy(begin, end, writableTail());
append(n);
}
// Movable
TypedIOBuf(TypedIOBuf&&) = default;
TypedIOBuf& operator=(TypedIOBuf&&) = default;
private:
// Non-copyable
TypedIOBuf(const TypedIOBuf&) = delete;
TypedIOBuf& operator=(const TypedIOBuf&) = delete;
// cast to T*
static T* cast(uint8_t* p) {
return reinterpret_cast<T*>(p);
}
static const T* cast(const uint8_t* p) {
return reinterpret_cast<const T*>(p);
}
// divide by size
static uint32_t sdiv(uint32_t n) {
return n / sizeof(T);
}
// multiply by size
static uint32_t smul(uint32_t n) {
// In debug mode, check for overflow
assert((uint64_t(n) * sizeof(T)) < (uint64_t(1) << 32));
return n * sizeof(T);
}
IOBuf* buf_;
};
} // namespace folly
#endif /* FOLLY_IO_TYPEDIOBUF_H_ */
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/IOBuf.h"
#include <gflags/gflags.h>
#include <boost/random.hpp>
#include <gtest/gtest.h>
#include "folly/Benchmark.h"
#include "folly/Range.h"
#include "folly/experimental/io/Cursor.h"
DECLARE_bool(benchmark);
using folly::IOBuf;
using std::unique_ptr;
using namespace folly::io;
TEST(IOBuf, RWCursor) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
unique_ptr<IOBuf> iobuf2(IOBuf::create(20));
iobuf2->append(20);
IOBuf* iob2ptr = iobuf2.get();
iobuf1->prependChain(std::move(iobuf2));
EXPECT_TRUE(iobuf1->isChained());
RWPrivateCursor wcursor(iobuf1.get());
Cursor rcursor(iobuf1.get());
wcursor.writeLE((uint64_t)1);
wcursor.writeLE((uint64_t)1);
wcursor.writeLE((uint64_t)1);
wcursor.write((uint8_t)1);
EXPECT_EQ(1, rcursor.readLE<uint64_t>());
rcursor.skip(8);
EXPECT_EQ(1, rcursor.readLE<uint32_t>());
rcursor.skip(0);
EXPECT_EQ(0, rcursor.read<uint8_t>());
EXPECT_EQ(0, rcursor.read<uint8_t>());
EXPECT_EQ(0, rcursor.read<uint8_t>());
EXPECT_EQ(0, rcursor.read<uint8_t>());
EXPECT_EQ(1, rcursor.read<uint8_t>());
}
TEST(IOBuf, skip) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
RWPrivateCursor wcursor(iobuf1.get());
wcursor.write((uint8_t)1);
wcursor.write((uint8_t)2);
Cursor cursor(iobuf1.get());
cursor.skip(1);
EXPECT_EQ(2, cursor.read<uint8_t>());
}
TEST(IOBuf, reset) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
RWPrivateCursor wcursor(iobuf1.get());
wcursor.write((uint8_t)1);
wcursor.write((uint8_t)2);
wcursor.reset(iobuf1.get());
EXPECT_EQ(1, wcursor.read<uint8_t>());
}
TEST(IOBuf, copy_assign_convert) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
RWPrivateCursor wcursor(iobuf1.get());
RWPrivateCursor cursor2(wcursor);
RWPrivateCursor cursor3(iobuf1.get());
wcursor.write((uint8_t)1);
cursor3 = wcursor;
wcursor.write((uint8_t)2);
Cursor cursor4(wcursor);
RWPrivateCursor cursor5(wcursor);
wcursor.write((uint8_t)3);
EXPECT_EQ(1, cursor2.read<uint8_t>());
EXPECT_EQ(2, cursor3.read<uint8_t>());
EXPECT_EQ(3, cursor4.read<uint8_t>());
}
TEST(IOBuf, overloading) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
RWPrivateCursor wcursor(iobuf1.get());
wcursor += 1;
wcursor.write((uint8_t)1);
Cursor cursor(iobuf1.get());
cursor += 1;
EXPECT_EQ(1, cursor.read<uint8_t>());
}
TEST(IOBuf, endian) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(20));
iobuf1->append(20);
RWPrivateCursor wcursor(iobuf1.get());
Cursor rcursor(iobuf1.get());
uint16_t v = 1;
int16_t vu = -1;
wcursor.writeBE(v);
wcursor.writeBE(vu);
// Try a couple combinations to ensure they were generated correctly
wcursor.writeBE(vu);
wcursor.writeLE(vu);
wcursor.writeLE(vu);
wcursor.writeLE(v);
EXPECT_EQ(v, rcursor.readBE<uint16_t>());
}
TEST(IOBuf, Cursor) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(1));
iobuf1->append(1);
RWPrivateCursor c(iobuf1.get());
c.write((uint8_t)40); // OK
try {
c.write((uint8_t)10); // Bad write, checked should except.
EXPECT_EQ(true, false);
} catch (...) {
}
}
TEST(IOBuf, UnshareCursor) {
uint8_t buf = 0;
unique_ptr<IOBuf> iobuf1(IOBuf::wrapBuffer(&buf, 1));
unique_ptr<IOBuf> iobuf2(IOBuf::wrapBuffer(&buf, 1));
RWUnshareCursor c1(iobuf1.get());
RWUnshareCursor c2(iobuf2.get());
c1.write((uint8_t)10); // This should duplicate the two buffers.
uint8_t t = c2.read<uint8_t>();
EXPECT_EQ(0, t);
iobuf1 = IOBuf::wrapBuffer(&buf, 1);
iobuf2 = IOBuf::wrapBuffer(&buf, 1);
RWPrivateCursor c3(iobuf1.get());
RWPrivateCursor c4(iobuf2.get());
c3.write((uint8_t)10); // This should _not_ duplicate the two buffers.
t = c4.read<uint8_t>();
EXPECT_EQ(10, t);
}
namespace {
void append(std::unique_ptr<IOBuf>& buf, folly::StringPiece data) {
EXPECT_LE(data.size(), buf->tailroom());
memcpy(buf->writableData(), data.data(), data.size());
buf->append(data.size());
}
void append(Appender& appender, folly::StringPiece data) {
appender.push(reinterpret_cast<const uint8_t*>(data.data()), data.size());
}
std::string toString(const IOBuf& buf) {
std::string str;
Cursor cursor(&buf);
std::pair<const uint8_t*, size_t> p;
while ((p = cursor.peek()).second) {
str.append(reinterpret_cast<const char*>(p.first), p.second);
cursor.skip(p.second);
}
return str;
}
} // namespace
TEST(IOBuf, PullAndPeek) {
std::unique_ptr<IOBuf> iobuf1(IOBuf::create(10));
append(iobuf1, "he");
std::unique_ptr<IOBuf> iobuf2(IOBuf::create(10));
append(iobuf2, "llo ");
std::unique_ptr<IOBuf> iobuf3(IOBuf::create(10));
append(iobuf3, "world");
iobuf1->prependChain(std::move(iobuf2));
iobuf1->prependChain(std::move(iobuf3));
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
char buf[12];
memset(buf, 0, sizeof(buf));
Cursor(iobuf1.get()).pull(buf, 11);
EXPECT_EQ("hello world", std::string(buf));
memset(buf, 0, sizeof(buf));
EXPECT_EQ(11, Cursor(iobuf1.get()).pullAtMost(buf, 20));
EXPECT_EQ("hello world", std::string(buf));
EXPECT_THROW({Cursor(iobuf1.get()).pull(buf, 20);},
std::out_of_range);
{
RWPrivateCursor cursor(iobuf1.get());
auto p = cursor.peek();
EXPECT_EQ("he", std::string(reinterpret_cast<const char*>(p.first),
p.second));
cursor.skip(p.second);
p = cursor.peek();
EXPECT_EQ("llo ", std::string(reinterpret_cast<const char*>(p.first),
p.second));
cursor.skip(p.second);
p = cursor.peek();
EXPECT_EQ("world", std::string(reinterpret_cast<const char*>(p.first),
p.second));
cursor.skip(p.second);
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
}
{
RWPrivateCursor cursor(iobuf1.get());
cursor.gather(11);
auto p = cursor.peek();
EXPECT_EQ("hello world", std::string(reinterpret_cast<const
char*>(p.first), p.second));
EXPECT_EQ(1, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
}
}
TEST(IOBuf, cloneAndInsert) {
std::unique_ptr<IOBuf> iobuf1(IOBuf::create(10));
append(iobuf1, "he");
std::unique_ptr<IOBuf> iobuf2(IOBuf::create(10));
append(iobuf2, "llo ");
std::unique_ptr<IOBuf> iobuf3(IOBuf::create(10));
append(iobuf3, "world");
iobuf1->prependChain(std::move(iobuf2));
iobuf1->prependChain(std::move(iobuf3));
EXPECT_EQ(3, iobuf1->countChainElements());
EXPECT_EQ(11, iobuf1->computeChainDataLength());
std::unique_ptr<IOBuf> cloned;
Cursor(iobuf1.get()).clone(cloned, 3);
EXPECT_EQ(2, cloned->countChainElements());
EXPECT_EQ(3, cloned->computeChainDataLength());
EXPECT_EQ(11, Cursor(iobuf1.get()).cloneAtMost(cloned, 20));
EXPECT_EQ(3, cloned->countChainElements());
EXPECT_EQ(11, cloned->computeChainDataLength());
EXPECT_THROW({Cursor(iobuf1.get()).clone(cloned, 20);},
std::out_of_range);
{
// Check that inserting in the middle of an iobuf splits
RWPrivateCursor cursor(iobuf1.get());
Cursor(iobuf1.get()).clone(cloned, 3);
EXPECT_EQ(2, cloned->countChainElements());
EXPECT_EQ(3, cloned->computeChainDataLength());
cursor.skip(1);
cursor.insert(std::move(cloned));
EXPECT_EQ(6, iobuf1->countChainElements());
EXPECT_EQ(14, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly
cursor.read<uint8_t>();
}
{
// Check that inserting at the end doesn't create empty buf
RWPrivateCursor cursor(iobuf1.get());
Cursor(iobuf1.get()).clone(cloned, 1);
EXPECT_EQ(1, cloned->countChainElements());
EXPECT_EQ(1, cloned->computeChainDataLength());
cursor.skip(1);
cursor.insert(std::move(cloned));
EXPECT_EQ(7, iobuf1->countChainElements());
EXPECT_EQ(15, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly
cursor.read<uint8_t>();
}
{
// Check that inserting at the beginning doesn't create empty buf
RWPrivateCursor cursor(iobuf1.get());
Cursor(iobuf1.get()).clone(cloned, 1);
EXPECT_EQ(1, cloned->countChainElements());
EXPECT_EQ(1, cloned->computeChainDataLength());
cursor.insert(std::move(cloned));
EXPECT_EQ(8, iobuf1->countChainElements());
EXPECT_EQ(16, iobuf1->computeChainDataLength());
// Check that nextBuf got set correctly
cursor.read<uint8_t>();
}
}
TEST(IOBuf, Appender) {
std::unique_ptr<IOBuf> head(IOBuf::create(10));
append(head, "hello");
Appender app(head.get(), 10);
uint32_t cap = head->capacity();
uint32_t len1 = app.length();
EXPECT_EQ(cap - 5, len1);
app.ensure(len1); // won't grow
EXPECT_EQ(len1, app.length());
app.ensure(len1 + 1); // will grow
EXPECT_LE(len1 + 1, app.length());
append(app, " world");
EXPECT_EQ("hello world", toString(*head));
}
int benchmark_size = 1000;
unique_ptr<IOBuf> iobuf_benchmark;
unique_ptr<IOBuf> iobuf_read_benchmark;
template <class CursClass>
void runBenchmark() {
CursClass c(iobuf_benchmark.get());
for(int i = 0; i < benchmark_size; i++) {
c.write((uint8_t)0);
}
}
BENCHMARK(rwPrivateCursorBenchmark, iters) {
while (--iters) {
runBenchmark<RWPrivateCursor>();
}
}
BENCHMARK(rwUnshareCursorBenchmark, iters) {
while (--iters) {
runBenchmark<RWUnshareCursor>();
}
}
BENCHMARK(cursorBenchmark, iters) {
while (--iters) {
Cursor c(iobuf_read_benchmark.get());
for(int i = 0; i < benchmark_size ; i++) {
c.read<uint8_t>();
}
}
}
BENCHMARK(skipBenchmark, iters) {
uint8_t buf;
while (--iters) {
Cursor c(iobuf_read_benchmark.get());
for(int i = 0; i < benchmark_size ; i++) {
c.peek();
c.skip(1);
}
}
}
// fbmake opt
// _bin/folly/experimental/io/test/iobuf_cursor_test -benchmark
//
// Benchmark Iters Total t t/iter iter/sec
// ---------------------------------------------------------------------------
// rwPrivateCursorBenchmark 100000 142.9 ms 1.429 us 683.5 k
// rwUnshareCursorBenchmark 100000 309.3 ms 3.093 us 315.7 k
// cursorBenchmark 100000 741.4 ms 7.414 us 131.7 k
// skipBenchmark 100000 738.9 ms 7.389 us 132.2 k
//
// uname -a:
//
// Linux dev2159.snc6.facebook.com 2.6.33-7_fbk15_104e4d0 #1 SMP
// Tue Oct 19 22:40:30 PDT 2010 x86_64 x86_64 x86_64 GNU/Linux
//
// 72GB RAM, 2 CPUs (Intel(R) Xeon(R) CPU L5630 @ 2.13GHz)
// hyperthreading disabled
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
auto ret = RUN_ALL_TESTS();
if (ret == 0 && FLAGS_benchmark) {
iobuf_benchmark = IOBuf::create(benchmark_size);
iobuf_benchmark->append(benchmark_size);
iobuf_read_benchmark = IOBuf::create(1);
for (int i = 0; i < benchmark_size; i++) {
unique_ptr<IOBuf> iobuf2(IOBuf::create(1));
iobuf2->append(1);
iobuf_read_benchmark->prependChain(std::move(iobuf2));
}
folly::runBenchmarks();
}
return ret;
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/IOBufQueue.h"
#include "folly/Range.h"
#include <gflags/gflags.h>
#include <gtest/gtest.h>
#include <iostream>
#include <stdexcept>
#include <string.h>
using folly::IOBuf;
using folly::IOBufQueue;
using folly::StringPiece;
using std::pair;
using std::string;
using std::unique_ptr;
// String Comma Length macro for string literals
#define SCL(x) (x), sizeof(x) - 1
namespace {
IOBufQueue::Options clOptions;
struct Initializer {
Initializer() {
clOptions.cacheChainLength = true;
}
};
Initializer initializer;
unique_ptr<IOBuf>
stringToIOBuf(const char* s, uint32_t len) {
unique_ptr<IOBuf> buf = IOBuf::create(len);
memcpy(buf->writableTail(), s, len);
buf->append(len);
return std::move(buf);
}
void checkConsistency(const IOBufQueue& queue) {
if (queue.options().cacheChainLength) {
size_t len = queue.front() ? queue.front()->computeChainDataLength() : 0;
EXPECT_EQ(len, queue.chainLength());
}
}
}
TEST(IOBufQueue, Simple) {
IOBufQueue queue(clOptions);
EXPECT_EQ(NULL, queue.front());
queue.append(SCL(""));
EXPECT_EQ(NULL, queue.front());
queue.append(unique_ptr<IOBuf>());
EXPECT_EQ(NULL, queue.front());
string emptyString;
queue.append(emptyString);
EXPECT_EQ(NULL, queue.front());
}
TEST(IOBufQueue, Append) {
IOBufQueue queue(clOptions);
queue.append(SCL("Hello"));
IOBufQueue queue2(clOptions);
queue2.append(SCL(", "));
queue2.append(SCL("World"));
checkConsistency(queue);
checkConsistency(queue2);
queue.append(queue2.move());
checkConsistency(queue);
checkConsistency(queue2);
const IOBuf* chain = queue.front();
EXPECT_NE((IOBuf*)NULL, chain);
EXPECT_EQ(12, chain->computeChainDataLength());
EXPECT_EQ(NULL, queue2.front());
}
TEST(IOBufQueue, Append2) {
IOBufQueue queue(clOptions);
queue.append(SCL("Hello"));
IOBufQueue queue2(clOptions);
queue2.append(SCL(", "));
queue2.append(SCL("World"));
checkConsistency(queue);
checkConsistency(queue2);
queue.append(queue2);
checkConsistency(queue);
checkConsistency(queue2);
const IOBuf* chain = queue.front();
EXPECT_NE((IOBuf*)NULL, chain);
EXPECT_EQ(12, chain->computeChainDataLength());
EXPECT_EQ(NULL, queue2.front());
}
TEST(IOBufQueue, Split) {
IOBufQueue queue(clOptions);
queue.append(stringToIOBuf(SCL("Hello")));
queue.append(stringToIOBuf(SCL(",")));
queue.append(stringToIOBuf(SCL(" ")));
queue.append(stringToIOBuf(SCL("")));
queue.append(stringToIOBuf(SCL("World")));
checkConsistency(queue);
EXPECT_EQ(12, queue.front()->computeChainDataLength());
unique_ptr<IOBuf> prefix(queue.split(1));
checkConsistency(queue);
EXPECT_EQ(1, prefix->computeChainDataLength());
EXPECT_EQ(11, queue.front()->computeChainDataLength());
prefix = queue.split(2);
checkConsistency(queue);
EXPECT_EQ(2, prefix->computeChainDataLength());
EXPECT_EQ(9, queue.front()->computeChainDataLength());
prefix = queue.split(3);
checkConsistency(queue);
EXPECT_EQ(3, prefix->computeChainDataLength());
EXPECT_EQ(6, queue.front()->computeChainDataLength());
prefix = queue.split(1);
checkConsistency(queue);
EXPECT_EQ(1, prefix->computeChainDataLength());
EXPECT_EQ(5, queue.front()->computeChainDataLength());
prefix = queue.split(5);
checkConsistency(queue);
EXPECT_EQ(5, prefix->computeChainDataLength());
EXPECT_EQ((IOBuf*)NULL, queue.front());
queue.append(stringToIOBuf(SCL("Hello,")));
queue.append(stringToIOBuf(SCL(" World")));
checkConsistency(queue);
bool exceptionFired = false;
EXPECT_THROW({prefix = queue.split(13);}, std::underflow_error);
checkConsistency(queue);
}
TEST(IOBufQueue, Preallocate) {
IOBufQueue queue(clOptions);
queue.append(string("Hello"));
pair<void*,uint32_t> writable = queue.preallocate(2, 64, 64);
checkConsistency(queue);
EXPECT_NE((void*)NULL, writable.first);
EXPECT_LE(2, writable.second);
EXPECT_GE(64, writable.second);
memcpy(writable.first, SCL(", "));
queue.postallocate(2);
checkConsistency(queue);
EXPECT_EQ(7, queue.front()->computeChainDataLength());
queue.append(SCL("World"));
checkConsistency(queue);
EXPECT_EQ(12, queue.front()->computeChainDataLength());
// There are not 2048 bytes available, this will alloc a new buf
writable = queue.preallocate(2048, 4096);
checkConsistency(queue);
EXPECT_LE(2048, writable.second);
// IOBuf allocates more than newAllocationSize, and we didn't cap it
EXPECT_GE(writable.second, 4096);
queue.postallocate(writable.second);
// queue has no empty space, make sure we allocate at least min, even if
// newAllocationSize < min
writable = queue.preallocate(1024, 1, 1024);
checkConsistency(queue);
EXPECT_EQ(1024, writable.second);
}
TEST(IOBufQueue, Wrap) {
IOBufQueue queue(clOptions);
const char* buf = "hello world goodbye";
size_t len = strlen(buf);
queue.wrapBuffer(buf, len, 6);
auto iob = queue.move();
EXPECT_EQ((len - 1) / 6 + 1, iob->countChainElements());
iob->unshare();
iob->coalesce();
EXPECT_EQ(StringPiece(buf),
StringPiece(reinterpret_cast<const char*>(iob->data()),
iob->length()));
}
TEST(IOBufQueue, trim) {
IOBufQueue queue(clOptions);
unique_ptr<IOBuf> a = IOBuf::create(4);
a->append(4);
queue.append(std::move(a));
checkConsistency(queue);
a = IOBuf::create(6);
a->append(6);
queue.append(std::move(a));
checkConsistency(queue);
a = IOBuf::create(8);
a->append(8);
queue.append(std::move(a));
checkConsistency(queue);
a = IOBuf::create(10);
a->append(10);
queue.append(std::move(a));
checkConsistency(queue);
EXPECT_EQ(4, queue.front()->countChainElements());
EXPECT_EQ(28, queue.front()->computeChainDataLength());
EXPECT_EQ(4, queue.front()->length());
queue.trimStart(1);
checkConsistency(queue);
EXPECT_EQ(4, queue.front()->countChainElements());
EXPECT_EQ(27, queue.front()->computeChainDataLength());
EXPECT_EQ(3, queue.front()->length());
queue.trimStart(5);
checkConsistency(queue);
EXPECT_EQ(3, queue.front()->countChainElements());
EXPECT_EQ(22, queue.front()->computeChainDataLength());
EXPECT_EQ(4, queue.front()->length());
queue.trimEnd(1);
checkConsistency(queue);
EXPECT_EQ(3, queue.front()->countChainElements());
EXPECT_EQ(21, queue.front()->computeChainDataLength());
EXPECT_EQ(9, queue.front()->prev()->length());
queue.trimEnd(20);
checkConsistency(queue);
EXPECT_EQ(1, queue.front()->countChainElements());
EXPECT_EQ(1, queue.front()->computeChainDataLength());
EXPECT_EQ(1, queue.front()->prev()->length());
queue.trimEnd(1);
checkConsistency(queue);
EXPECT_EQ(NULL, queue.front());
EXPECT_THROW(queue.trimStart(2), std::underflow_error);
checkConsistency(queue);
EXPECT_THROW(queue.trimEnd(30), std::underflow_error);
checkConsistency(queue);
}
TEST(IOBufQueue, Prepend) {
folly::IOBufQueue queue;
auto buf = folly::IOBuf::create(10);
buf->advance(5);
queue.append(std::move(buf));
queue.append(SCL(" World"));
queue.prepend(SCL("Hello"));
EXPECT_THROW(queue.prepend(SCL("x")), std::overflow_error);
auto out = queue.move();
out->coalesce();
EXPECT_EQ("Hello World",
StringPiece(reinterpret_cast<const char*>(out->data()),
out->length()));
}
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/IOBuf.h"
#include "folly/experimental/io/TypedIOBuf.h"
// googletest requires std::tr1::tuple, not std::tuple
#include <tr1/tuple>
#include <gflags/gflags.h>
#include <boost/random.hpp>
#include <gtest/gtest.h>
#include "folly/Malloc.h"
#include "folly/Range.h"
using folly::fbstring;
using folly::IOBuf;
using folly::TypedIOBuf;
using folly::StringPiece;
using std::unique_ptr;
void append(std::unique_ptr<IOBuf>& buf, StringPiece str) {
EXPECT_LE(str.size(), buf->tailroom());
memcpy(buf->writableData(), str.data(), str.size());
buf->append(str.size());
}
void prepend(std::unique_ptr<IOBuf>& buf, StringPiece str) {
EXPECT_LE(str.size(), buf->headroom());
memcpy(buf->writableData() - str.size(), str.data(), str.size());
buf->prepend(str.size());
}
TEST(IOBuf, Simple) {
unique_ptr<IOBuf> buf(IOBuf::create(100));
uint32_t cap = buf->capacity();
EXPECT_LE(100, cap);
EXPECT_EQ(0, buf->headroom());
EXPECT_EQ(0, buf->length());
EXPECT_EQ(cap, buf->tailroom());
append(buf, "world");
buf->advance(10);
EXPECT_EQ(10, buf->headroom());
EXPECT_EQ(5, buf->length());
EXPECT_EQ(cap - 15, buf->tailroom());
prepend(buf, "hello ");
EXPECT_EQ(4, buf->headroom());
EXPECT_EQ(11, buf->length());
EXPECT_EQ(cap - 15, buf->tailroom());
const char* p = reinterpret_cast<const char*>(buf->data());
EXPECT_EQ("hello world", std::string(p, buf->length()));
buf->clear();
EXPECT_EQ(0, buf->headroom());
EXPECT_EQ(0, buf->length());
EXPECT_EQ(cap, buf->tailroom());
}
void testAllocSize(uint32_t requestedCapacity) {
unique_ptr<IOBuf> iobuf(IOBuf::create(requestedCapacity));
EXPECT_GE(iobuf->capacity(), requestedCapacity);
}
TEST(IOBuf, AllocSizes) {
// Try with a small allocation size that should fit in the internal buffer
testAllocSize(28);
// Try with a large allocation size that will require an external buffer.
testAllocSize(9000);
// 220 bytes is currently the cutoff
// (It would be nice to use the IOBuf::kMaxInternalDataSize constant,
// but it's private and it doesn't seem worth making it public just for this
// test code.)
testAllocSize(220);
testAllocSize(219);
testAllocSize(221);
}
void deleteArrayBuffer(void *buf, void* arg) {
uint32_t* deleteCount = static_cast<uint32_t*>(arg);
++(*deleteCount);
uint8_t* bufPtr = static_cast<uint8_t*>(buf);
delete[] bufPtr;
}
TEST(IOBuf, TakeOwnership) {
uint32_t size1 = 99;
uint8_t *buf1 = static_cast<uint8_t*>(malloc(size1));
unique_ptr<IOBuf> iobuf1(IOBuf::takeOwnership(buf1, size1));
EXPECT_EQ(buf1, iobuf1->data());
EXPECT_EQ(size1, iobuf1->length());
EXPECT_EQ(buf1, iobuf1->buffer());
EXPECT_EQ(size1, iobuf1->capacity());
uint32_t deleteCount = 0;
uint32_t size2 = 4321;
uint8_t *buf2 = new uint8_t[size2];
unique_ptr<IOBuf> iobuf2(IOBuf::takeOwnership(buf2, size2,
deleteArrayBuffer,
&deleteCount));
EXPECT_EQ(buf2, iobuf2->data());
EXPECT_EQ(size2, iobuf2->length());
EXPECT_EQ(buf2, iobuf2->buffer());
EXPECT_EQ(size2, iobuf2->capacity());
EXPECT_EQ(0, deleteCount);
iobuf2.reset();
EXPECT_EQ(1, deleteCount);
deleteCount = 0;
uint32_t size3 = 3456;
uint8_t *buf3 = new uint8_t[size3];
uint32_t length3 = 48;
unique_ptr<IOBuf> iobuf3(IOBuf::takeOwnership(buf3, size3, length3,
deleteArrayBuffer,
&deleteCount));
EXPECT_EQ(buf3, iobuf3->data());
EXPECT_EQ(length3, iobuf3->length());
EXPECT_EQ(buf3, iobuf3->buffer());
EXPECT_EQ(size3, iobuf3->capacity());
EXPECT_EQ(0, deleteCount);
iobuf3.reset();
EXPECT_EQ(1, deleteCount);
}
TEST(IOBuf, WrapBuffer) {
const uint32_t size1 = 1234;
uint8_t buf1[size1];
unique_ptr<IOBuf> iobuf1(IOBuf::wrapBuffer(buf1, size1));
EXPECT_EQ(buf1, iobuf1->data());
EXPECT_EQ(size1, iobuf1->length());
EXPECT_EQ(buf1, iobuf1->buffer());
EXPECT_EQ(size1, iobuf1->capacity());
uint32_t size2 = 0x1234;
unique_ptr<uint8_t[]> buf2(new uint8_t[size2]);
unique_ptr<IOBuf> iobuf2(IOBuf::wrapBuffer(buf2.get(), size2));
EXPECT_EQ(buf2.get(), iobuf2->data());
EXPECT_EQ(size2, iobuf2->length());
EXPECT_EQ(buf2.get(), iobuf2->buffer());
EXPECT_EQ(size2, iobuf2->capacity());
}
void fillBuf(uint8_t* buf, uint32_t length, boost::mt19937& gen) {
for (uint32_t n = 0; n < length; ++n) {
buf[n] = static_cast<uint8_t>(gen() & 0xff);
}
}
void fillBuf(IOBuf* buf, boost::mt19937& gen) {
buf->unshare();
fillBuf(buf->writableData(), buf->length(), gen);
}
void checkBuf(const uint8_t* buf, uint32_t length, boost::mt19937& gen) {
// Rather than using EXPECT_EQ() to check each character,
// count the number of differences and the first character that differs.
// This way on error we'll report just that information, rather than tons of
// failed checks for each byte in the buffer.
uint32_t numDifferences = 0;
uint32_t firstDiffIndex = 0;
uint8_t firstDiffExpected = 0;
for (uint32_t n = 0; n < length; ++n) {
uint8_t expected = static_cast<uint8_t>(gen() & 0xff);
if (buf[n] == expected) {
continue;
}
if (numDifferences == 0) {
firstDiffIndex = n;
firstDiffExpected = expected;
}
++numDifferences;
}
EXPECT_EQ(0, numDifferences);
if (numDifferences > 0) {
// Cast to int so it will be printed numerically
// rather than as a char if the check fails
EXPECT_EQ(static_cast<int>(buf[firstDiffIndex]),
static_cast<int>(firstDiffExpected));
}
}
void checkBuf(IOBuf* buf, boost::mt19937& gen) {
checkBuf(buf->data(), buf->length(), gen);
}
void checkChain(IOBuf* buf, boost::mt19937& gen) {
IOBuf *current = buf;
do {
checkBuf(current->data(), current->length(), gen);
current = current->next();
} while (current != buf);
}
TEST(IOBuf, Chaining) {
uint32_t fillSeed = 0x12345678;
boost::mt19937 gen(fillSeed);
// An IOBuf with external storage
uint32_t headroom = 123;
unique_ptr<IOBuf> iob1(IOBuf::create(2048));
iob1->advance(headroom);
iob1->append(1500);
fillBuf(iob1.get(), gen);
// An IOBuf with internal storage
unique_ptr<IOBuf> iob2(IOBuf::create(20));
iob2->append(20);
fillBuf(iob2.get(), gen);
// An IOBuf around a buffer it doesn't own
uint8_t localbuf[1234];
fillBuf(localbuf, 1234, gen);
unique_ptr<IOBuf> iob3(IOBuf::wrapBuffer(localbuf, sizeof(localbuf)));
// An IOBuf taking ownership of a user-supplied buffer
uint32_t heapBufSize = 900;
uint8_t* heapBuf = static_cast<uint8_t*>(malloc(heapBufSize));
fillBuf(heapBuf, heapBufSize, gen);
unique_ptr<IOBuf> iob4(IOBuf::takeOwnership(heapBuf, heapBufSize));
// An IOBuf taking ownership of a user-supplied buffer with
// a custom free function
uint32_t arrayBufSize = 321;
uint8_t* arrayBuf = new uint8_t[arrayBufSize];
fillBuf(arrayBuf, arrayBufSize, gen);
uint32_t arrayBufFreeCount = 0;
unique_ptr<IOBuf> iob5(IOBuf::takeOwnership(arrayBuf, arrayBufSize,
deleteArrayBuffer,
&arrayBufFreeCount));
EXPECT_FALSE(iob1->isChained());
EXPECT_FALSE(iob2->isChained());
EXPECT_FALSE(iob3->isChained());
EXPECT_FALSE(iob4->isChained());
EXPECT_FALSE(iob5->isChained());
EXPECT_FALSE(iob1->isSharedOne());
EXPECT_FALSE(iob2->isSharedOne());
EXPECT_TRUE(iob3->isSharedOne()); // since we own the buffer
EXPECT_FALSE(iob4->isSharedOne());
EXPECT_FALSE(iob5->isSharedOne());
// Chain the buffers all together
// Since we are going to relinquish ownership of iob2-5 to the chain,
// store raw pointers to them so we can reference them later.
IOBuf* iob2ptr = iob2.get();
IOBuf* iob3ptr = iob3.get();
IOBuf* iob4ptr = iob4.get();
IOBuf* iob5ptr = iob5.get();
iob1->prependChain(std::move(iob2));
iob1->prependChain(std::move(iob4));
iob2ptr->appendChain(std::move(iob3));
iob1->prependChain(std::move(iob5));
EXPECT_EQ(iob2ptr, iob1->next());
EXPECT_EQ(iob3ptr, iob2ptr->next());
EXPECT_EQ(iob4ptr, iob3ptr->next());
EXPECT_EQ(iob5ptr, iob4ptr->next());
EXPECT_EQ(iob1.get(), iob5ptr->next());
EXPECT_EQ(iob5ptr, iob1->prev());
EXPECT_EQ(iob1.get(), iob2ptr->prev());
EXPECT_EQ(iob2ptr, iob3ptr->prev());
EXPECT_EQ(iob3ptr, iob4ptr->prev());
EXPECT_EQ(iob4ptr, iob5ptr->prev());
EXPECT_TRUE(iob1->isChained());
EXPECT_TRUE(iob2ptr->isChained());
EXPECT_TRUE(iob3ptr->isChained());
EXPECT_TRUE(iob4ptr->isChained());
EXPECT_TRUE(iob5ptr->isChained());
uint64_t fullLength = (iob1->length() + iob2ptr->length() +
iob3ptr->length() + iob4ptr->length() +
iob5ptr->length());
EXPECT_EQ(5, iob1->countChainElements());
EXPECT_EQ(fullLength, iob1->computeChainDataLength());
// Since iob3 is shared, the entire buffer should report itself as shared
EXPECT_TRUE(iob1->isShared());
// Unshare just iob3
iob3ptr->unshareOne();
EXPECT_FALSE(iob3ptr->isSharedOne());
// Now everything in the chain should be unshared.
// Check on all members of the chain just for good measure
EXPECT_FALSE(iob1->isShared());
EXPECT_FALSE(iob2ptr->isShared());
EXPECT_FALSE(iob3ptr->isShared());
EXPECT_FALSE(iob4ptr->isShared());
EXPECT_FALSE(iob5ptr->isShared());
// Clone one of the IOBufs in the chain
unique_ptr<IOBuf> iob4clone = iob4ptr->cloneOne();
gen.seed(fillSeed);
checkBuf(iob1.get(), gen);
checkBuf(iob2ptr, gen);
checkBuf(iob3ptr, gen);
checkBuf(iob4clone.get(), gen);
checkBuf(iob5ptr, gen);
EXPECT_TRUE(iob1->isShared());
EXPECT_TRUE(iob2ptr->isShared());
EXPECT_TRUE(iob3ptr->isShared());
EXPECT_TRUE(iob4ptr->isShared());
EXPECT_TRUE(iob5ptr->isShared());
EXPECT_FALSE(iob1->isSharedOne());
EXPECT_FALSE(iob2ptr->isSharedOne());
EXPECT_FALSE(iob3ptr->isSharedOne());
EXPECT_TRUE(iob4ptr->isSharedOne());
EXPECT_FALSE(iob5ptr->isSharedOne());
// Unshare that clone
EXPECT_TRUE(iob4clone->isSharedOne());
iob4clone->unshare();
EXPECT_FALSE(iob4clone->isSharedOne());
EXPECT_FALSE(iob4ptr->isSharedOne());
EXPECT_FALSE(iob1->isShared());
iob4clone.reset();
// Create a clone of a different IOBuf
EXPECT_FALSE(iob1->isShared());
EXPECT_FALSE(iob3ptr->isSharedOne());
unique_ptr<IOBuf> iob3clone = iob3ptr->cloneOne();
gen.seed(fillSeed);
checkBuf(iob1.get(), gen);
checkBuf(iob2ptr, gen);
checkBuf(iob3clone.get(), gen);
checkBuf(iob4ptr, gen);
checkBuf(iob5ptr, gen);
EXPECT_TRUE(iob1->isShared());
EXPECT_TRUE(iob3ptr->isSharedOne());
EXPECT_FALSE(iob1->isSharedOne());
// Delete the clone and make sure the original is unshared
iob3clone.reset();
EXPECT_FALSE(iob1->isShared());
EXPECT_FALSE(iob3ptr->isSharedOne());
// Clone the entire chain
unique_ptr<IOBuf> chainClone = iob1->clone();
// Verify that the data is correct.
EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
gen.seed(fillSeed);
checkChain(chainClone.get(), gen);
// Check that the buffers report sharing correctly
EXPECT_TRUE(chainClone->isShared());
EXPECT_TRUE(iob1->isShared());
EXPECT_TRUE(iob1->isSharedOne());
// since iob2 has a small internal buffer, it will never be shared
EXPECT_FALSE(iob2ptr->isSharedOne());
EXPECT_TRUE(iob3ptr->isSharedOne());
EXPECT_TRUE(iob4ptr->isSharedOne());
EXPECT_TRUE(iob5ptr->isSharedOne());
// Unshare the cloned chain
chainClone->unshare();
EXPECT_FALSE(chainClone->isShared());
EXPECT_FALSE(iob1->isShared());
// Make sure the unshared result still has the same data
EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
gen.seed(fillSeed);
checkChain(chainClone.get(), gen);
// Destroy this chain
chainClone.reset();
// Clone a new chain
EXPECT_FALSE(iob1->isShared());
chainClone = iob1->clone();
EXPECT_TRUE(iob1->isShared());
EXPECT_TRUE(chainClone->isShared());
// Delete the original chain
iob1.reset();
EXPECT_FALSE(chainClone->isShared());
// Coalesce the chain
//
// Coalescing this chain will create a new buffer and release the last
// refcount on the original buffers we created. Also make sure
// that arrayBufFreeCount increases to one to indicate that arrayBuf was
// freed.
EXPECT_EQ(5, chainClone->countChainElements());
EXPECT_EQ(0, arrayBufFreeCount);
// Buffer lengths: 1500 20 1234 900 321
// Coalesce the first 3 buffers
chainClone->gather(1521);
EXPECT_EQ(3, chainClone->countChainElements());
EXPECT_EQ(0, arrayBufFreeCount);
// Make sure the data is still the same after coalescing
EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
gen.seed(fillSeed);
checkChain(chainClone.get(), gen);
// Coalesce the entire chain
chainClone->coalesce();
EXPECT_EQ(1, chainClone->countChainElements());
EXPECT_EQ(1, arrayBufFreeCount);
// Make sure the data is still the same after coalescing
EXPECT_EQ(fullLength, chainClone->computeChainDataLength());
gen.seed(fillSeed);
checkChain(chainClone.get(), gen);
// Make a new chain to test the unlink and pop operations
iob1 = IOBuf::create(1);
iob1->append(1);
IOBuf *iob1ptr = iob1.get();
iob2 = IOBuf::create(3);
iob2->append(3);
iob2ptr = iob2.get();
iob3 = IOBuf::create(5);
iob3->append(5);
iob3ptr = iob3.get();
iob4 = IOBuf::create(7);
iob4->append(7);
iob4ptr = iob4.get();
iob1->appendChain(std::move(iob2));
iob1->prev()->appendChain(std::move(iob3));
iob1->prev()->appendChain(std::move(iob4));
EXPECT_EQ(4, iob1->countChainElements());
EXPECT_EQ(16, iob1->computeChainDataLength());
// Unlink from the middle of the chain
iob3 = iob3ptr->unlink();
EXPECT_TRUE(iob3.get() == iob3ptr);
EXPECT_EQ(3, iob1->countChainElements());
EXPECT_EQ(11, iob1->computeChainDataLength());
// Unlink from the end of the chain
iob4 = iob1->prev()->unlink();
EXPECT_TRUE(iob4.get() == iob4ptr);
EXPECT_EQ(2, iob1->countChainElements());
EXPECT_TRUE(iob1->next() == iob2ptr);
EXPECT_EQ(4, iob1->computeChainDataLength());
// Pop from the front of the chain
iob2 = iob1->pop();
EXPECT_TRUE(iob1.get() == iob1ptr);
EXPECT_EQ(1, iob1->countChainElements());
EXPECT_EQ(1, iob1->computeChainDataLength());
EXPECT_TRUE(iob2.get() == iob2ptr);
EXPECT_EQ(1, iob2->countChainElements());
EXPECT_EQ(3, iob2->computeChainDataLength());
}
TEST(IOBuf, Reserve) {
uint32_t fillSeed = 0x23456789;
boost::mt19937 gen(fillSeed);
// Reserve does nothing if empty and doesn't have to grow the buffer
{
gen.seed(fillSeed);
unique_ptr<IOBuf> iob(IOBuf::create(2000));
EXPECT_EQ(0, iob->headroom());
const void* p1 = iob->buffer();
iob->reserve(5, 15);
EXPECT_LE(5, iob->headroom());
EXPECT_EQ(p1, iob->buffer());
}
// Reserve doesn't reallocate if we have enough total room
{
gen.seed(fillSeed);
unique_ptr<IOBuf> iob(IOBuf::create(2000));
iob->append(100);
fillBuf(iob.get(), gen);
EXPECT_EQ(0, iob->headroom());
EXPECT_EQ(100, iob->length());
const void* p1 = iob->buffer();
const uint8_t* d1 = iob->data();
iob->reserve(100, 1800);
EXPECT_LE(100, iob->headroom());
EXPECT_EQ(p1, iob->buffer());
EXPECT_EQ(d1 + 100, iob->data());
gen.seed(fillSeed);
checkBuf(iob.get(), gen);
}
// Reserve reallocates if we don't have enough total room.
// NOTE that, with jemalloc, we know that this won't reallocate in place
// as the size is less than jemallocMinInPlaceExpanadable
{
gen.seed(fillSeed);
unique_ptr<IOBuf> iob(IOBuf::create(2000));
iob->append(100);
fillBuf(iob.get(), gen);
EXPECT_EQ(0, iob->headroom());
EXPECT_EQ(100, iob->length());
const void* p1 = iob->buffer();
const uint8_t* d1 = iob->data();
iob->reserve(100, 2512); // allocation sizes are multiples of 256
EXPECT_LE(100, iob->headroom());
if (folly::usingJEMalloc()) {
EXPECT_NE(p1, iob->buffer());
}
gen.seed(fillSeed);
checkBuf(iob.get(), gen);
}
// Test reserve from internal buffer, this used to segfault
{
unique_ptr<IOBuf> iob(IOBuf::create(0));
iob->reserve(0, 2000);
EXPECT_EQ(0, iob->headroom());
EXPECT_LE(2000, iob->tailroom());
}
}
TEST(IOBuf, copyBuffer) {
std::string s("hello");
auto buf = IOBuf::copyBuffer(s.data(), s.size(), 1, 2);
EXPECT_EQ(1, buf->headroom());
EXPECT_EQ(s, std::string(reinterpret_cast<const char*>(buf->data()),
buf->length()));
EXPECT_LE(2, buf->tailroom());
buf = IOBuf::copyBuffer(s, 5, 7);
EXPECT_EQ(5, buf->headroom());
EXPECT_EQ(s, std::string(reinterpret_cast<const char*>(buf->data()),
buf->length()));
EXPECT_LE(7, buf->tailroom());
std::string empty;
buf = IOBuf::copyBuffer(empty, 3, 6);
EXPECT_EQ(3, buf->headroom());
EXPECT_EQ(0, buf->length());
EXPECT_LE(6, buf->tailroom());
}
TEST(IOBuf, maybeCopyBuffer) {
std::string s("this is a test");
auto buf = IOBuf::maybeCopyBuffer(s, 1, 2);
EXPECT_EQ(1, buf->headroom());
EXPECT_EQ(s, std::string(reinterpret_cast<const char*>(buf->data()),
buf->length()));
EXPECT_LE(2, buf->tailroom());
std::string empty;
buf = IOBuf::maybeCopyBuffer("", 5, 7);
EXPECT_EQ(nullptr, buf.get());
buf = IOBuf::maybeCopyBuffer("");
EXPECT_EQ(nullptr, buf.get());
}
namespace {
int customDeleterCount = 0;
int destructorCount = 0;
struct OwnershipTestClass {
explicit OwnershipTestClass(int v = 0) : val(v) { }
~OwnershipTestClass() {
++destructorCount;
}
int val;
};
typedef std::function<void(OwnershipTestClass*)> CustomDeleter;
void customDelete(OwnershipTestClass* p) {
++customDeleterCount;
delete p;
}
void customDeleteArray(OwnershipTestClass* p) {
++customDeleterCount;
delete[] p;
}
} // namespace
TEST(IOBuf, takeOwnershipUniquePtr) {
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass> p(new OwnershipTestClass());
}
EXPECT_EQ(1, destructorCount);
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass[]> p(new OwnershipTestClass[2]);
}
EXPECT_EQ(2, destructorCount);
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass> p(new OwnershipTestClass());
std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p)));
EXPECT_EQ(sizeof(OwnershipTestClass), buf->length());
EXPECT_EQ(0, destructorCount);
}
EXPECT_EQ(1, destructorCount);
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass[]> p(new OwnershipTestClass[2]);
std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p), 2));
EXPECT_EQ(2 * sizeof(OwnershipTestClass), buf->length());
EXPECT_EQ(0, destructorCount);
}
EXPECT_EQ(2, destructorCount);
customDeleterCount = 0;
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass, CustomDeleter>
p(new OwnershipTestClass(), customDelete);
std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p)));
EXPECT_EQ(sizeof(OwnershipTestClass), buf->length());
EXPECT_EQ(0, destructorCount);
}
EXPECT_EQ(1, destructorCount);
EXPECT_EQ(1, customDeleterCount);
customDeleterCount = 0;
destructorCount = 0;
{
std::unique_ptr<OwnershipTestClass[], CustomDeleter>
p(new OwnershipTestClass[2], customDeleteArray);
std::unique_ptr<IOBuf> buf(IOBuf::takeOwnership(std::move(p), 2));
EXPECT_EQ(2 * sizeof(OwnershipTestClass), buf->length());
EXPECT_EQ(0, destructorCount);
}
EXPECT_EQ(2, destructorCount);
EXPECT_EQ(1, customDeleterCount);
}
TEST(IOBuf, Alignment) {
// max_align_t doesn't exist in gcc 4.6.2
struct MaxAlign {
char c;
} __attribute__((aligned));
size_t alignment = alignof(MaxAlign);
std::vector<size_t> sizes {0, 1, 64, 256, 1024, 1 << 10};
for (size_t size : sizes) {
auto buf = IOBuf::create(size);
uintptr_t p = reinterpret_cast<uintptr_t>(buf->data());
EXPECT_EQ(0, p & (alignment - 1)) << "size=" << size;
}
}
TEST(TypedIOBuf, Simple) {
auto buf = IOBuf::create(0);
TypedIOBuf<uint64_t> typed(buf.get());
const uint64_t n = 10000;
typed.reserve(0, n);
EXPECT_LE(n, typed.capacity());
for (uint64_t i = 0; i < n; i++) {
*typed.writableTail() = i;
typed.append(1);
}
EXPECT_EQ(n, typed.length());
for (uint64_t i = 0; i < n; i++) {
EXPECT_EQ(i, typed.data()[i]);
}
}
// chain element size, number of elements in chain, shared
class MoveToFbStringTest
: public ::testing::TestWithParam<std::tr1::tuple<int, int, bool>> {
protected:
void SetUp() {
std::tr1::tie(elementSize_, elementCount_, shared_) = GetParam();
buf_ = makeBuf();
for (int i = 0; i < elementCount_ - 1; ++i) {
buf_->prependChain(makeBuf());
}
EXPECT_EQ(elementCount_, buf_->countChainElements());
EXPECT_EQ(elementCount_ * elementSize_, buf_->computeChainDataLength());
if (shared_) {
buf2_ = buf_->clone();
EXPECT_EQ(elementCount_, buf2_->countChainElements());
EXPECT_EQ(elementCount_ * elementSize_, buf2_->computeChainDataLength());
}
}
std::unique_ptr<IOBuf> makeBuf() {
auto buf = IOBuf::create(elementSize_);
memset(buf->writableTail(), 'x', elementSize_);
buf->append(elementSize_);
return buf;
}
void check(std::unique_ptr<IOBuf>& buf) {
fbstring str = buf->moveToFbString();
EXPECT_EQ(elementCount_ * elementSize_, str.size());
EXPECT_EQ(elementCount_ * elementSize_, strspn(str.c_str(), "x"));
EXPECT_EQ(0, buf->length());
EXPECT_EQ(1, buf->countChainElements());
EXPECT_EQ(0, buf->computeChainDataLength());
EXPECT_FALSE(buf->isChained());
}
int elementSize_;
int elementCount_;
bool shared_;
std::unique_ptr<IOBuf> buf_;
std::unique_ptr<IOBuf> buf2_;
};
TEST_P(MoveToFbStringTest, Simple) {
check(buf_);
if (shared_) {
check(buf2_);
}
}
INSTANTIATE_TEST_CASE_P(
MoveToFbString,
MoveToFbStringTest,
::testing::Combine(
::testing::Values(0, 1, 24, 256, 1 << 10, 1 << 20), // element size
::testing::Values(1, 2, 10), // element count
::testing::Bool())); // shared
int main(int argc, char** argv) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
return RUN_ALL_TESTS();
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/IOBuf.h"
#include <gflags/gflags.h>
#include "folly/Benchmark.h"
#include "folly/experimental/io/Cursor.h"
#include <vector>
using folly::IOBuf;
using std::unique_ptr;
using namespace folly::io;
using namespace std;
size_t buf_size = 0;
size_t num_bufs = 0;
BENCHMARK(reserveBenchmark, iters) {
while (--iters) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(buf_size));
iobuf1->append(buf_size);
for (size_t bufs = num_bufs; bufs > 1; bufs --) {
iobuf1->reserve(0, buf_size);
iobuf1->append(buf_size);
}
}
}
BENCHMARK(chainBenchmark, iters) {
while (--iters) {
unique_ptr<IOBuf> iobuf1(IOBuf::create(buf_size));
iobuf1->append(buf_size);
for (size_t bufs = num_bufs; bufs > 1; bufs --) {
unique_ptr<IOBuf> iobufNext(IOBuf::create(buf_size));
iobuf1->prependChain(std::move(iobufNext));
}
}
}
vector<unique_ptr<IOBuf>> bufPool;
inline unique_ptr<IOBuf> poolGetIOBuf() {
if (bufPool.size() > 0) {
unique_ptr<IOBuf> ret = std::move(bufPool.back());
bufPool.pop_back();
return std::move(ret);
} else {
unique_ptr<IOBuf> iobuf(IOBuf::create(buf_size));
iobuf->append(buf_size);
return std::move(iobuf);
}
}
inline void poolPutIOBuf(unique_ptr<IOBuf>&& buf) {
unique_ptr<IOBuf> head = std::move(buf);
while (head) {
unique_ptr<IOBuf> next = std::move(head->pop());
bufPool.push_back(std::move(head));
head = std::move(next);
}
}
BENCHMARK(poolBenchmark, iters) {
while (--iters) {
unique_ptr<IOBuf> head = std::move(poolGetIOBuf());
for (size_t bufs = num_bufs; bufs > 1; bufs --) {
unique_ptr<IOBuf> iobufNext = std::move(poolGetIOBuf());
head->prependChain(std::move(iobufNext));
}
// cleanup
poolPutIOBuf(std::move(head));
}
}
void setNumbers(size_t size, size_t num) {
buf_size = size;
num_bufs = num;
bufPool.clear();
printf("\nBuffer size: %zu, number of buffers: %zu\n\n", size, num);
}
/*
------------------------------------------------------------------------------
reserveBenchmark 100000 9.186 ms 91.86 ns 10.38 M
chainBenchmark 100000 59.44 ms 594.4 ns 1.604 M
poolBenchmark 100000 15.87 ms 158.7 ns 6.01 M
Buffer size: 100, number of buffers: 10
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 100000 62 ms 620 ns 1.538 M
chainBenchmark 100000 59.48 ms 594.8 ns 1.603 M
poolBenchmark 100000 16.07 ms 160.7 ns 5.933 M
Buffer size: 2048, number of buffers: 10
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 100000 148.4 ms 1.484 us 658.2 k
chainBenchmark 100000 140.9 ms 1.409 us 693 k
poolBenchmark 100000 16.73 ms 167.3 ns 5.7 M
Buffer size: 10000, number of buffers: 10
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 100000 234 ms 2.34 us 417.3 k
chainBenchmark 100000 142.3 ms 1.423 us 686.1 k
poolBenchmark 100000 16.78 ms 167.8 ns 5.684 M
Buffer size: 100000, number of buffers: 10
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 100000 186.5 ms 1.865 us 523.5 k
chainBenchmark 100000 360.5 ms 3.605 us 270.9 k
poolBenchmark 100000 16.52 ms 165.2 ns 5.772 M
Buffer size: 1000000, number of buffers: 10
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 156 2.084 s 13.36 ms 74.84
chainBenchmark 30082 2.001 s 66.5 us 14.68 k
poolBenchmark 100000 18.18 ms 181.8 ns 5.244 M
Buffer size: 10, number of buffers: 20
Benchmark Iters Total t t/iter iter/sec
------------------------------------------------------------------------------
reserveBenchmark 100000 12.54 ms 125.4 ns 7.603 M
chainBenchmark 100000 118.6 ms 1.186 us 823.2 k
poolBenchmark 100000 32.2 ms 322 ns 2.962 M
*/
int main(int argc, char** argv) {
google::ParseCommandLineFlags(&argc, &argv, true);
setNumbers(10, 10);
folly::runBenchmarks();
setNumbers(100, 10);
folly::runBenchmarks();
setNumbers(2048, 10);
folly::runBenchmarks();
setNumbers(10000, 10);
folly::runBenchmarks();
setNumbers(100000, 10);
folly::runBenchmarks();
setNumbers(1000000, 10);
folly::runBenchmarks();
setNumbers(10, 20);
folly::runBenchmarks();
return 0;
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/io/Stream.h"
#include <vector>
#include <string>
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "folly/Benchmark.h"
#include "folly/experimental/TestUtil.h"
using namespace folly;
namespace {
std::vector<std::string> streamSplit(const std::string& str, char delimiter,
size_t maxChunkSize = (size_t)-1) {
size_t pos = 0;
auto cb = [&] (ByteRange& sp) mutable -> bool {
if (pos == str.size()) return false;
size_t n = std::min(str.size() - pos, maxChunkSize);
sp.reset(reinterpret_cast<const unsigned char*>(&(str[pos])), n);
pos += n;
return true;
};
std::vector<std::string> result;
for (auto line : makeInputByteStreamSplitter(delimiter, cb)) {
result.push_back(StringPiece(line).str());
}
return result;
}
} // namespace
TEST(InputByteStreamSplitter, Empty) {
{
auto pieces = streamSplit("", ',');
EXPECT_EQ(0, pieces.size());
}
// The last delimiter is eaten, just like std::getline
{
auto pieces = streamSplit(",", ',');
EXPECT_EQ(1, pieces.size());
EXPECT_EQ("", pieces[0]);
}
{
auto pieces = streamSplit(",,", ',');
EXPECT_EQ(2, pieces.size());
EXPECT_EQ("", pieces[0]);
EXPECT_EQ("", pieces[1]);
}
}
TEST(InputByteStreamSplitter, Simple) {
std::string str = "hello,, world, goodbye, meow";
for (size_t chunkSize = 1; chunkSize <= str.size(); ++chunkSize) {
auto pieces = streamSplit(str, ',', chunkSize);
EXPECT_EQ(5, pieces.size());
EXPECT_EQ("hello", pieces[0]);
EXPECT_EQ("", pieces[1]);
EXPECT_EQ(" world", pieces[2]);
EXPECT_EQ(" goodbye", pieces[3]);
EXPECT_EQ(" meow", pieces[4]);
}
}
TEST(ByLine, Simple) {
test::TemporaryFile file("ByLine");
static const std::string lines(
"Hello world\n"
"This is the second line\n"
"\n"
"\n"
"a few empty lines above\n"
"incomplete last line");
EXPECT_EQ(lines.size(), write(file.fd(), lines.data(), lines.size()));
auto expected = streamSplit(lines, '\n');
std::vector<std::string> found;
for (auto& line : byLine(file.path())) {
found.push_back(StringPiece(line).str());
}
EXPECT_TRUE(expected == found);
}
int main(int argc, char *argv[]) {
testing::InitGoogleTest(&argc, argv);
google::ParseCommandLineFlags(&argc, &argv, true);
auto ret = RUN_ALL_TESTS();
if (!ret) {
folly::runBenchmarksOnFlag();
}
return ret;
}
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/symbolizer/Symbolizer.h"
#include <glog/logging.h>
using namespace folly;
using namespace folly::symbolizer;
int main(int argc, char *argv[]) {
google::InitGoogleLogging(argv[0]);
Symbolizer s;
StringPiece name;
Dwarf::LocationInfo location;
CHECK(s.symbolize(reinterpret_cast<uintptr_t>(main), name, location));
LOG(INFO) << name << " " << location.file << " " << location.line << " ("
<< location.mainFile << ")";
return 0;
}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/experimental/File.h"
#include <glog/logging.h>
#include <gtest/gtest.h>
#include "folly/Benchmark.h"
#include "folly/String.h"
using namespace folly;
namespace {
void expectWouldBlock(ssize_t r) {
int savedErrno = errno;
EXPECT_EQ(-1, r);
EXPECT_EQ(EAGAIN, savedErrno) << errnoStr(errno);
}
void expectOK(ssize_t r) {
int savedErrno = errno;
EXPECT_LE(0, r) << ": errno=" << errnoStr(errno);
}
} // namespace
TEST(File, Simple) {
// Open a file, ensure it's indeed open for reading
char buf = 'x';
{
File f("/etc/hosts");
EXPECT_NE(-1, f.fd());
EXPECT_EQ(1, ::read(f.fd(), &buf, 1));
f.close();
EXPECT_EQ(-1, f.fd());
}
}
TEST(File, OwnsFd) {
// Wrap a file descriptor, make sure that ownsFd works
// We'll test that the file descriptor is closed by closing the writing
// end of a pipe and making sure that a non-blocking read from the reading
// end returns 0.
char buf = 'x';
int p[2];
expectOK(::pipe(p));
int flags = ::fcntl(p[0], F_GETFL);
expectOK(flags);
expectOK(::fcntl(p[0], F_SETFL, flags | O_NONBLOCK));
expectWouldBlock(::read(p[0], &buf, 1));
{
File f(p[1]);
EXPECT_EQ(p[1], f.fd());
}
// Ensure that moving the file doesn't close it
{
File f(p[1]);
EXPECT_EQ(p[1], f.fd());
File f1(std::move(f));
EXPECT_EQ(-1, f.fd());
EXPECT_EQ(p[1], f1.fd());
}
expectWouldBlock(::read(p[0], &buf, 1)); // not closed
{
File f(p[1], true);
EXPECT_EQ(p[1], f.fd());
}
ssize_t r = ::read(p[0], &buf, 1); // eof
expectOK(r);
EXPECT_EQ(0, r);
::close(p[0]);
}
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include <errno.h>
#include <gtest/gtest.h>
#include "folly/eventfd.h"
#include <glog/logging.h>
using namespace folly;
TEST(EventFD, Basic) {
int fd = eventfd(10, EFD_NONBLOCK);
CHECK_ERR(fd);
uint64_t val;
ssize_t r;
// Running this twice -- once with the initial value, and once
// after a write()
for (int attempt = 0; attempt < 2; attempt++) {
val = 0;
r = read(fd, &val, sizeof(val));
CHECK_ERR(r);
EXPECT_EQ(sizeof(val), r);
EXPECT_EQ(10, val);
r = read(fd, &val, sizeof(val));
EXPECT_EQ(-1, r);
EXPECT_EQ(EAGAIN, errno);
val = 10;
r = write(fd, &val, sizeof(val));
CHECK_ERR(r);
EXPECT_EQ(sizeof(val), r);
}
close(fd);
}
TEST(EventFD, Semaphore) {
int fd = eventfd(10, EFD_NONBLOCK | EFD_SEMAPHORE);
CHECK_ERR(fd);
uint64_t val;
ssize_t r;
// Running this twice -- once with the initial value, and once
// after a write()
for (int attempt = 0; attempt < 2; attempt++) {
val = 0;
for (int i = 0; i < 10; i++) {
r = read(fd, &val, sizeof(val));
CHECK_ERR(r);
EXPECT_EQ(sizeof(val), r);
EXPECT_EQ(1, val);
}
r = read(fd, &val, sizeof(val));
EXPECT_EQ(-1, r);
EXPECT_EQ(EAGAIN, errno);
val = 10;
r = write(fd, &val, sizeof(val));
CHECK_ERR(r);
EXPECT_EQ(sizeof(val), r);
}
close(fd);
}
/*
* Copyright 2013 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
#include "folly/FBString.h"
#ifdef _GLIBCXX_STDEXCEPT
#error Cannot include <stdexcept> in FBString.h\
(use std::__throw_* from funcexcept.h instead)
#endif
int main(){}
/*
* Copyright 2012 Facebook, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
// SpookyHash: a 128-bit noncryptographic hash function
// By Bob Jenkins, public domain
#include "folly/SpookyHash.h"
#include <cstdio>
#include <cstddef>
#include <cstring>
#include <cstdlib>
#include <ctime>
using namespace ::folly::hash;
static bool failed = false;
static uint64_t GetTickCount() {
timespec ts;
clock_gettime(CLOCK_REALTIME, &ts);
return ts.tv_sec * 1000 + ts.tv_nsec / 1000000; // milliseconds
}
class Random
{
public:
inline uint64_t Value()
{
uint64_t e = m_a - Rot64(m_b, 23);
m_a = m_b ^ Rot64(m_c, 16);
m_b = m_c + Rot64(m_d, 11);
m_c = m_d + e;
m_d = e + m_a;
return m_d;
}
inline void Init( uint64_t seed)
{
m_a = 0xdeadbeef;
m_b = m_c = m_d = seed;
for (int i=0; i<20; ++i)
(void)Value();
}
private:
static inline uint64_t Rot64(uint64_t x, int k)
{
return (x << k) | (x >> (64-(k)));
}
uint64_t m_a;
uint64_t m_b;
uint64_t m_c;
uint64_t m_d;
};
// fastest conceivable hash function (for comparison)
static void Add(const void *data, size_t length, uint64_t *hash1, uint64_t *hash2)
{
uint64_t *p64 = (uint64_t *)data;
uint64_t *end = p64 + length/8;
uint64_t hash = *hash1 + *hash2;
while (p64 < end)
{
hash += *p64;
++p64;
}
*hash1 = hash;
*hash2 = hash;
}
#define BUFSIZE (512)
void TestResults()
{
printf("\ntesting results ...\n");
static const uint32_t expected[BUFSIZE] = {
0xa24295ec, 0xfe3a05ce, 0x257fd8ef, 0x3acd5217,
0xfdccf85c, 0xc7b5f143, 0x3b0c3ff0, 0x5220f13c,
0xa6426724, 0x4d5426b4, 0x43e76b26, 0x051bc437,
0xd8f28a02, 0x23ccc30e, 0x811d1a2d, 0x039128d4,
0x9cd96a73, 0x216e6a8d, 0x97293fe8, 0xe4fc6d09,
0x1ad34423, 0x9722d7e4, 0x5a6fdeca, 0x3c94a7e1,
0x81a9a876, 0xae3f7c0e, 0x624b50ee, 0x875e5771,
0x0095ab74, 0x1a7333fb, 0x056a4221, 0xa38351fa,
0x73f575f1, 0x8fded05b, 0x9097138f, 0xbd74620c,
0x62d3f5f2, 0x07b78bd0, 0xbafdd81e, 0x0638f2ff,
0x1f6e3aeb, 0xa7786473, 0x71700e1d, 0x6b4625ab,
0xf02867e1, 0xb2b2408f, 0x9ce21ce5, 0xa62baaaf,
0x26720461, 0x434813ee, 0x33bc0f14, 0xaaab098a,
0x750af488, 0xc31bf476, 0x9cecbf26, 0x94793cf3,
0xe1a27584, 0xe80c4880, 0x1299f748, 0x25e55ed2,
0x405e3feb, 0x109e2412, 0x3e55f94f, 0x59575864,
0x365c869d, 0xc9852e6a, 0x12c30c62, 0x47f5b286,
0xb47e488d, 0xa6667571, 0x78220d67, 0xa49e30b9,
0x2005ef88, 0xf6d3816d, 0x6926834b, 0xe6116805,
0x694777aa, 0x464af25b, 0x0e0e2d27, 0x0ea92eae,
0x602c2ca9, 0x1d1d79c5, 0x6364f280, 0x939ee1a4,
0x3b851bd8, 0x5bb6f19f, 0x80b9ed54, 0x3496a9f1,
0xdf815033, 0x91612339, 0x14c516d6, 0xa3f0a804,
0x5e78e975, 0xf408bcd9, 0x63d525ed, 0xa1e459c3,
0xfde303af, 0x049fc17f, 0xe7ed4489, 0xfaeefdb6,
0x2b1b2fa8, 0xc67579a6, 0x5505882e, 0xe3e1c7cb,
0xed53bf30, 0x9e628351, 0x8fa12113, 0x7500c30f,
0xde1bee00, 0xf1fefe06, 0xdc759c00, 0x4c75e5ab,
0xf889b069, 0x695bf8ae, 0x47d6600f, 0xd2a84f87,
0xa0ca82a9, 0x8d2b750c, 0xe03d8cd7, 0x581fea33,
0x969b0460, 0x36c7b7de, 0x74b3fd20, 0x2bb8bde6,
0x13b20dec, 0xa2dcee89, 0xca36229d, 0x06fdb74e,
0x6d9a982d, 0x02503496, 0xbdb4e0d9, 0xbd1f94cf,
0x6d26f82d, 0xcf5e41cd, 0x88b67b65, 0x3e1b3ee4,
0xb20e5e53, 0x1d9be438, 0xcef9c692, 0x299bd1b2,
0xb1279627, 0x210b5f3d, 0x5569bd88, 0x9652ed43,
0x7e8e0f8c, 0xdfa01085, 0xcd6d6343, 0xb8739826,
0xa52ce9a0, 0xd33ef231, 0x1b4d92c2, 0xabfa116d,
0xcdf47800, 0x3a4eefdc, 0xd01f3bcf, 0x30a32f46,
0xfb54d851, 0x06a98f67, 0xbdcd0a71, 0x21a00949,
0xfe7049c9, 0x67ef46d2, 0xa1fabcbc, 0xa4c72db4,
0x4a8a910d, 0x85a890ad, 0xc37e9454, 0xfc3d034a,
0x6f46cc52, 0x742be7a8, 0xe94ecbc5, 0x5f993659,
0x98270309, 0x8d1adae9, 0xea6e035e, 0x293d5fae,
0x669955b3, 0x5afe23b5, 0x4c74efbf, 0x98106505,
0xfbe09627, 0x3c00e8df, 0x5b03975d, 0x78edc83c,
0x117c49c6, 0x66cdfc73, 0xfa55c94f, 0x5bf285fe,
0x2db49b7d, 0xfbfeb8f0, 0xb7631bab, 0x837849f3,
0xf77f3ae5, 0x6e5db9bc, 0xfdd76f15, 0x545abf92,
0x8b538102, 0xdd5c9b65, 0xa5adfd55, 0xecbd7bc5,
0x9f99ebdd, 0x67500dcb, 0xf5246d1f, 0x2b0c061c,
0x927a3747, 0xc77ba267, 0x6da9f855, 0x6240d41a,
0xe9d1701d, 0xc69f0c55, 0x2c2c37cf, 0x12d82191,
0x47be40d3, 0x165b35cd, 0xb7db42e1, 0x358786e4,
0x84b8fc4e, 0x92f57c28, 0xf9c8bbd7, 0xab95a33d,
0x11009238, 0xe9770420, 0xd6967e2a, 0x97c1589f,
0x2ee7e7d3, 0x32cc86da, 0xe47767d1, 0x73e9b61e,
0xd35bac45, 0x835a62bb, 0x5d9217b0, 0x43f3f0ed,
0x8a97911e, 0x4ec7eb55, 0x4b5a988c, 0xb9056683,
0x45456f97, 0x1669fe44, 0xafb861b8, 0x8e83a19c,
0x0bab08d6, 0xe6a145a9, 0xc31e5fc2, 0x27621f4c,
0x795692fa, 0xb5e33ab9, 0x1bc786b6, 0x45d1c106,
0x986531c9, 0x40c9a0ec, 0xff0fdf84, 0xa7359a42,
0xfd1c2091, 0xf73463d4, 0x51b0d635, 0x1d602fb4,
0xc56b69b7, 0x6909d3f7, 0xa04d68f4, 0x8d1001a7,
0x8ecace50, 0x21ec4765, 0x3530f6b0, 0x645f3644,
0x9963ef1e, 0x2b3c70d5, 0xa20c823b, 0x8d26dcae,
0x05214e0c, 0x1993896d, 0x62085a35, 0x7b620b67,
0x1dd85da2, 0x09ce9b1d, 0xd7873326, 0x063ff730,
0xf4ff3c14, 0x09a49d69, 0x532062ba, 0x03ba7729,
0xbd9a86cc, 0xe26d02a7, 0x7ccbe5d3, 0x4f662214,
0x8b999a66, 0x3d0b92b4, 0x70b210f0, 0xf5b8f16f,
0x32146d34, 0x430b92bf, 0x8ab6204c, 0x35e6e1ff,
0xc2f6c2fa, 0xa2df8a1a, 0x887413ec, 0x7cb7a69f,
0x7ac6dbe6, 0x9102d1cb, 0x8892a590, 0xc804fe3a,
0xdfc4920a, 0xfc829840, 0x8910d2eb, 0x38a210fd,
0x9d840cc9, 0x7b9c827f, 0x3444ca0c, 0x071735ab,
0x5e9088e4, 0xc995d60e, 0xbe0bb942, 0x17b089ae,
0x050e1054, 0xcf4324f7, 0x1e3e64dd, 0x436414bb,
0xc48fc2e3, 0x6b6b83d4, 0x9f6558ac, 0x781b22c5,
0x7147cfe2, 0x3c221b4d, 0xa5602765, 0x8f01a4f0,
0x2a9f14ae, 0x12158cb8, 0x28177c50, 0x1091a165,
0x39e4e4be, 0x3e451b7a, 0xd965419c, 0x52053005,
0x0798aa53, 0xe6773e13, 0x1207f671, 0xd2ef998b,
0xab88a38f, 0xc77a8482, 0xa88fb031, 0x5199e0cd,
0x01b30536, 0x46eeb0ef, 0x814259ff, 0x9789a8cf,
0x376ec5ac, 0x7087034a, 0x948b6bdd, 0x4281e628,
0x2c848370, 0xd76ce66a, 0xe9b6959e, 0x24321a8e,
0xdeddd622, 0xb890f960, 0xea26c00a, 0x55e7d8b2,
0xeab67f09, 0x9227fb08, 0xeebbed06, 0xcac1b0d1,
0xb6412083, 0x05d2b0e7, 0x9037624a, 0xc9702198,
0x2c8d1a86, 0x3e7d416e, 0xc3f1a39f, 0xf04bdce4,
0xc88cdb61, 0xbdc89587, 0x4d29b63b, 0x6f24c267,
0x4b529c87, 0x573f5a53, 0xdb3316e9, 0x288eb53b,
0xd2c074bd, 0xef44a99a, 0x2b404d2d, 0xf6706464,
0xfe824f4c, 0xc3debaf8, 0x12f44f98, 0x03135e76,
0xb4888e7f, 0xb6b2325d, 0x3a138259, 0x513c83ec,
0x2386d214, 0x94555500, 0xfbd1522d, 0xda2af018,
0x15b054c0, 0x5ad654e6, 0xb6ed00aa, 0xa2f2180e,
0x5f662825, 0xecd11366, 0x1de5e99d, 0x07afd2ad,
0xcf457b04, 0xe631e10b, 0x83ae8a21, 0x709f0d59,
0x3e278bf9, 0x246816db, 0x9f5e8fd3, 0xc5b5b5a2,
0xd54a9d5c, 0x4b6f2856, 0x2eb5a666, 0xfc68bdd4,
0x1ed1a7f8, 0x98a34b75, 0xc895ada9, 0x2907cc69,
0x87b0b455, 0xddaf96d9, 0xe7da15a6, 0x9298c82a,
0x72bd5cab, 0x2e2a6ad4, 0x7f4b6bb8, 0x525225fe,
0x985abe90, 0xac1fd6e1, 0xb8340f23, 0x92985159,
0x7d29501d, 0xe75dc744, 0x687501b4, 0x92077dc3,
0x58281a67, 0xe7e8e9be, 0xd0e64fd1, 0xb2eb0a30,
0x0e1feccd, 0xc0dc4a9e, 0x5c4aeace, 0x2ca5b93c,
0xee0ec34f, 0xad78467b, 0x0830e76e, 0x0df63f8b,
0x2c2dfd95, 0x9b41ed31, 0x9ff4cddc, 0x1590c412,
0x2366fc82, 0x7a83294f, 0x9336c4de, 0x2343823c,
0x5b681096, 0xf320e4c2, 0xc22b70e2, 0xb5fbfb2a,
0x3ebc2fed, 0x11af07bd, 0x429a08c5, 0x42bee387,
0x58629e33, 0xfb63b486, 0x52135fbe, 0xf1380e60,
0x6355de87, 0x2f0bb19a, 0x167f63ac, 0x507224cf,
0xf7c99d00, 0x71646f50, 0x74feb1ca, 0x5f9abfdd,
0x278f7d68, 0x70120cd7, 0x4281b0f2, 0xdc8ebe5c,
0x36c32163, 0x2da1e884, 0x61877598, 0xbef04402,
0x304db695, 0xfa8e9add, 0x503bac31, 0x0fe04722,
0xf0d59f47, 0xcdc5c595, 0x918c39dd, 0x0cad8d05,
0x6b3ed1eb, 0x4d43e089, 0x7ab051f8, 0xdeec371f,
0x0f4816ae, 0xf8a1a240, 0xd15317f6, 0xb8efbf0b,
0xcdd05df8, 0x4fd5633e, 0x7cf19668, 0x25d8f422,
0x72d156f2, 0x2a778502, 0xda7aefb9, 0x4f4f66e8,
0x19db6bff, 0x74e468da, 0xa754f358, 0x7339ec50,
0x139006f6, 0xefbd0b91, 0x217e9a73, 0x939bd79c
};
uint8_t buf[BUFSIZE];
uint32_t saw[BUFSIZE];
for (int i=0; i<BUFSIZE; ++i)
{
buf[i] = i+128;
saw[i] = SpookyHash::Hash32(buf, i, 0);
if (saw[i] != expected[i])
{
printf("%d: saw 0x%.8x, expected 0x%.8x\n", i, saw[i], expected[i]);
failed = true;
}
}
}
#undef BUFSIZE
#define NUMBUF (1<<10)
#define BUFSIZE (1<<20)
void DoTimingBig(int seed)
{
printf("\ntesting time to hash 2^^30 bytes ...\n");
char *buf[NUMBUF];
for (int i=0; i<NUMBUF; ++i)
{
buf[i] = (char *)malloc(BUFSIZE);
memset(buf[i], (char)seed, BUFSIZE);
}
uint64_t a = GetTickCount();
uint64_t hash1 = seed;
uint64_t hash2 = seed;
for (uint64_t i=0; i<NUMBUF; ++i)
{
SpookyHash::Hash128(buf[i], BUFSIZE, &hash1, &hash2);
}
uint64_t z = GetTickCount();
printf("SpookyHash::Hash128, uncached: time is %4lu milliseconds\n", z-a);
a = GetTickCount();
for (uint64_t i=0; i<NUMBUF; ++i)
{
Add(buf[i], BUFSIZE, &hash1, &hash2);
}
z = GetTickCount();
printf("Addition , uncached: time is %4lu milliseconds\n", z-a);
a = GetTickCount();
for (uint64_t i=0; i<NUMBUF*BUFSIZE/1024; ++i)
{
SpookyHash::Hash128(buf[0], 1024, &hash1, &hash2);
}
z = GetTickCount();
printf("SpookyHash::Hash128, cached: time is %4lu milliseconds\n", z-a);
a = GetTickCount();
for (uint64_t i=0; i<NUMBUF*BUFSIZE/1024; ++i)
{
Add(buf[0], 1024, &hash1, &hash2);
}
z = GetTickCount();
printf("Addition , cached: time is %4lu milliseconds\n", z-a);
for (int i=0; i<NUMBUF; ++i)
{
free(buf[i]);
buf[i] = 0;
}
}
#undef NUMBUF
#undef BUFSIZE
#define BUFSIZE (1<<14)
#define NUMITER 10000000
void DoTimingSmall(int seed)
{
printf("\ntesting timing of hashing up to %d cached aligned bytes %d times ...\n",
BUFSIZE, NUMITER);
uint64_t buf[BUFSIZE/8];
for (int i=0; i<BUFSIZE/8; ++i)
{
buf[i] = i+seed;
}
for (int i=1; i <= BUFSIZE; i <<= 1)
{
uint64_t a = GetTickCount();
uint64_t hash1 = seed;
uint64_t hash2 = seed+i;
for (int j=0; j<NUMITER; ++j)
{
SpookyHash::Hash128((char *)buf, i, &hash1, &hash2);
}
uint64_t z = GetTickCount();
printf("%d bytes: hash is %.16lx %.16lx, time is %lu\n",
i, hash1, hash2, z-a);
}
}
#undef BUFSIZE
#define BUFSIZE 1024
void TestAlignment()
{
printf("\ntesting alignment ...\n");
char buf[BUFSIZE];
uint64_t hash[8];
for (int i=0; i<BUFSIZE-16; ++i)
{
for (int j=0; j<8; ++j)
{
buf[j] = (char)i+j;
for (int k=1; k<=i; ++k)
{
buf[j+k] = k;
}
buf[j+i+1] = (char)i+j;
hash[j] = SpookyHash::Hash64((const void *)(buf+j+1), i, 0);
}
for (int j=1; j<8; ++j)
{
if (hash[0] != hash[j])
{
printf("alignment problems: %d %d\n", i, j);
failed = true;
}
}
}
}
#undef BUFSIZE
// test that all deltas of one or two input bits affect all output bits
#define BUFSIZE 256
#define TRIES 50
#define MEASURES 6
void TestDeltas(int seed)
{
printf("\nall 1 or 2 bit input deltas get %d tries to flip every output bit ...\n", TRIES);
Random random;
random.Init((uint64_t)seed);
// for messages 0..BUFSIZE-1 bytes
for (int h=0; h<BUFSIZE; ++h)
{
int maxk = 0;
// first bit to set
for (int i=0; i<h*8; ++i)
{
// second bit to set, or don't have a second bit
for (int j=0; j<=i; ++j)
{
uint64_t measure[MEASURES][2];
uint64_t counter[MEASURES][2];
for (int l=0; l<2; ++l)
{
for (int m=0; m<MEASURES; ++m)
{
counter[m][l] = 0;
}
}
// try to hit every output bit TRIES times
int k;
for (k=0; k<TRIES; ++k)
{
uint8_t buf1[BUFSIZE];
uint8_t buf2[BUFSIZE];
int done = 1;
for (int l=0; l<h; ++l)
{
buf1[l] = buf2[l] = random.Value();
}
buf1[i/8] ^= (1 << (i%8));
if (j != i)
{
buf1[j/8] ^= (1 << (j%8));
}
SpookyHash::Hash128(buf1, h, &measure[0][0], &measure[0][1]);
SpookyHash::Hash128(buf2, h, &measure[1][0], &measure[1][1]);
for (int l=0; l<2; ++l) {
measure[2][l] = measure[0][l] ^ measure[1][l];
measure[3][l] = ~(measure[0][l] ^ measure[1][l]);
measure[4][l] = measure[0][l] - measure[1][l];
measure[4][l] ^= (measure[4][l]>>1);
measure[5][l] = measure[0][l] + measure[1][l];
measure[5][l] ^= (measure[4][l]>>1);
}
for (int l=0; l<2; ++l)
{
for (int m=0; m<MEASURES; ++m)
{
counter[m][l] |= measure[m][l];
if (~counter[m][l]) done = 0;
}
}
if (done) break;
}
if (k == TRIES)
{
printf("failed %d %d %d\n", h, i, j);
failed = true;
}
else if (k > maxk)
{
maxk = k;
}
}
}
printf("passed for buffer size %d max %d\n", h, maxk);
}
}
#undef BUFSIZE
#undef TRIES
#undef MEASURES
// test that hashing pieces has the same behavior as hashing the whole
#define BUFSIZE 1024
void TestPieces()
{
printf("\ntesting pieces ...\n");
char buf[BUFSIZE];
for (int i=0; i<BUFSIZE; ++i)
{
buf[i] = i;
}
for (int i=0; i<BUFSIZE; ++i)
{
uint64_t a,b,c,d,seed1=1,seed2=2;
SpookyHash state;
// all as one call
a = seed1;
b = seed2;
SpookyHash::Hash128(buf, i, &a, &b);
// all as one piece
c = 0xdeadbeefdeadbeef;
d = 0xbaceba11baceba11;
state.Init(seed1, seed2);
state.Update(buf, i);
state.Final(&c, &d);
if (a != c)
{
printf("wrong a %d: %.16lx %.16lx\n", i, a,c);
failed = true;
}
if (b != d)
{
printf("wrong b %d: %.16lx %.16lx\n", i, b,d);
failed = true;
}
// all possible two consecutive pieces
for (int j=0; j<i; ++j)
{
c = seed1;
d = seed2;
state.Init(c, d);
state.Update(&buf[0], j);
state.Update(&buf[j], i-j);
state.Final(&c, &d);
if (a != c)
{
printf("wrong a %d %d: %.16lx %.16lx\n", j, i, a,c);
failed = true;
}
if (b != d)
{
printf("wrong b %d %d: %.16lx %.16lx\n", j, i, b,d);
failed = true;
}
}
}
}
#undef BUFSIZE
int main(int argc, const char **argv)
{
TestResults();
TestAlignment();
TestPieces();
DoTimingBig(argc);
// tudorb@fb.com: Commented out slow tests
#if 0
DoTimingSmall(argc);
TestDeltas(argc);
#endif
return failed;
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment