Update folly
Esse commit está contido em:
@@ -45,6 +45,13 @@ template <class Alloc>
|
||||
void* Arena<Alloc>::allocateSlow(size_t size) {
|
||||
std::pair<Block*, size_t> p;
|
||||
char* start;
|
||||
|
||||
|
||||
size_t allocSize = std::max(size, minBlockSize()) + sizeof(Block);
|
||||
if(sizeLimit_ && allocSize > sizeLimit_ - totalAllocatedSize_) {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
|
||||
if (size > minBlockSize()) {
|
||||
// Allocate a large block for this chunk only, put it at the back of the
|
||||
// list so it doesn't get used for small allocations; don't change ptr_
|
||||
|
||||
+9
-4
@@ -60,12 +60,14 @@ template <class Alloc>
|
||||
class Arena {
|
||||
public:
|
||||
explicit Arena(const Alloc& alloc,
|
||||
size_t minBlockSize = kDefaultMinBlockSize)
|
||||
size_t minBlockSize = kDefaultMinBlockSize,
|
||||
size_t sizeLimit = 0)
|
||||
: allocAndSize_(alloc, minBlockSize)
|
||||
, ptr_(nullptr)
|
||||
, end_(nullptr)
|
||||
, totalAllocatedSize_(0)
|
||||
, bytesUsed_(0) {
|
||||
, bytesUsed_(0)
|
||||
, sizeLimit_(sizeLimit) {
|
||||
}
|
||||
|
||||
~Arena();
|
||||
@@ -192,6 +194,7 @@ class Arena {
|
||||
char* end_;
|
||||
size_t totalAllocatedSize_;
|
||||
size_t bytesUsed_;
|
||||
size_t sizeLimit_;
|
||||
};
|
||||
|
||||
/**
|
||||
@@ -231,8 +234,10 @@ struct ArenaAllocatorTraits<SysAlloc> {
|
||||
*/
|
||||
class SysArena : public Arena<SysAlloc> {
|
||||
public:
|
||||
explicit SysArena(size_t minBlockSize = kDefaultMinBlockSize)
|
||||
: Arena<SysAlloc>(SysAlloc(), minBlockSize) {
|
||||
explicit SysArena(
|
||||
size_t minBlockSize = kDefaultMinBlockSize,
|
||||
size_t sizeLimit = 0)
|
||||
: Arena<SysAlloc>(SysAlloc(), minBlockSize, sizeLimit) {
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+28
-26
@@ -24,8 +24,8 @@
|
||||
namespace folly {
|
||||
|
||||
// AtomicHashArray private constructor --
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
|
||||
KeyT erasedKey, double maxLoadFactor, size_t cacheSize)
|
||||
: capacity_(capacity), maxEntries_(size_t(maxLoadFactor * capacity_ + 0.5)),
|
||||
@@ -41,9 +41,9 @@ AtomicHashArray(size_t capacity, KeyT emptyKey, KeyT lockedKey,
|
||||
* of key and returns true, or if key does not exist returns false and
|
||||
* ret.index is set to capacity_.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findInternal(const KeyT key_in) {
|
||||
DCHECK_NE(key_in, kEmptyKey_);
|
||||
DCHECK_NE(key_in, kLockedKey_);
|
||||
@@ -52,7 +52,7 @@ findInternal(const KeyT key_in) {
|
||||
;
|
||||
idx = probeNext(idx, numProbes)) {
|
||||
const KeyT key = acquireLoadKey(cells_[idx]);
|
||||
if (LIKELY(key == key_in)) {
|
||||
if (LIKELY(EqualFcn()(key, key_in))) {
|
||||
return SimpleRetT(idx, true);
|
||||
}
|
||||
if (UNLIKELY(key == kEmptyKey_)) {
|
||||
@@ -77,10 +77,10 @@ findInternal(const KeyT key_in) {
|
||||
* this will be the previously inserted value, and if the map is full it is
|
||||
* default.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
template <class T>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insertInternal(KeyT key_in, T&& value) {
|
||||
const short NO_NEW_INSERTS = 1;
|
||||
const short NO_PENDING_INSERTS = 2;
|
||||
@@ -140,6 +140,8 @@ insertInternal(KeyT key_in, T&& value) {
|
||||
--numPendingEntries_;
|
||||
throw;
|
||||
}
|
||||
// Direct comparison rather than EqualFcn ok here
|
||||
// (we just inserted it)
|
||||
DCHECK(relaxedLoadKey(*cell) == key_in);
|
||||
--numPendingEntries_;
|
||||
++numEntries_; // This is a thread cached atomic increment :)
|
||||
@@ -159,7 +161,7 @@ insertInternal(KeyT key_in, T&& value) {
|
||||
}
|
||||
|
||||
const KeyT thisKey = acquireLoadKey(*cell);
|
||||
if (thisKey == key_in) {
|
||||
if (EqualFcn()(thisKey, key_in)) {
|
||||
// Found an existing entry for our key, but we don't overwrite the
|
||||
// previous value.
|
||||
return SimpleRetT(idx, false);
|
||||
@@ -191,8 +193,8 @@ insertInternal(KeyT key_in, T&& value) {
|
||||
* erased key will never be reused. If there's an associated value, we won't
|
||||
* touch it either.
|
||||
*/
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
size_t AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
size_t AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
erase(KeyT key_in) {
|
||||
CHECK_NE(key_in, kEmptyKey_);
|
||||
CHECK_NE(key_in, kLockedKey_);
|
||||
@@ -208,10 +210,10 @@ erase(KeyT key_in) {
|
||||
// is similar to how it's handled in find().
|
||||
return 0;
|
||||
}
|
||||
if (key_in == currentKey) {
|
||||
if (EqualFcn()(currentKey, key_in)) {
|
||||
// Found an existing entry for our key, attempt to mark it erased.
|
||||
// Some other thread may have erased our key, but this is ok.
|
||||
KeyT expect = key_in;
|
||||
KeyT expect = currentKey;
|
||||
if (cellKeyPtr(*cell)->compare_exchange_strong(expect, kErasedKey_)) {
|
||||
numErases_.fetch_add(1, std::memory_order_relaxed);
|
||||
|
||||
@@ -234,13 +236,13 @@ erase(KeyT key_in) {
|
||||
}
|
||||
}
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
const typename AtomicHashArray<KeyT, ValueT, HashFcn>::Config
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn>::defaultConfig;
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
const typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::Config
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::defaultConfig;
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn>::SmartPtr
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
typename AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::SmartPtr
|
||||
AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
create(size_t maxSize, const Config& c) {
|
||||
CHECK_LE(c.maxLoadFactor, 1.0);
|
||||
CHECK_GT(c.maxLoadFactor, 0.0);
|
||||
@@ -272,8 +274,8 @@ create(size_t maxSize, const Config& c) {
|
||||
return map;
|
||||
}
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
destroy(AtomicHashArray* p) {
|
||||
assert(p);
|
||||
FOR_EACH_RANGE(i, 0, p->capacity_) {
|
||||
@@ -286,8 +288,8 @@ destroy(AtomicHashArray* p) {
|
||||
}
|
||||
|
||||
// clear -- clears all keys and values in the map and resets all counters
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn>::
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
void AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
clear() {
|
||||
FOR_EACH_RANGE(i, 0, capacity_) {
|
||||
if (cells_[i].first != kEmptyKey_) {
|
||||
@@ -305,9 +307,9 @@ clear() {
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
template <class ContT, class IterVal>
|
||||
struct AtomicHashArray<KeyT, ValueT, HashFcn>::aha_iterator
|
||||
struct AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn>::aha_iterator
|
||||
: boost::iterator_facade<aha_iterator<ContT,IterVal>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag>
|
||||
|
||||
+12
-7
@@ -42,13 +42,16 @@
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn = std::hash<KeyT>>
|
||||
template <class KeyT, class ValueT,
|
||||
class HashFcn = std::hash<KeyT>, class EqualFcn = std::equal_to<KeyT>>
|
||||
class AtomicHashMap;
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn = std::hash<KeyT>>
|
||||
template <class KeyT, class ValueT,
|
||||
class HashFcn = std::hash<KeyT>, class EqualFcn = std::equal_to<KeyT>>
|
||||
class AtomicHashArray : boost::noncopyable {
|
||||
static_assert((std::is_convertible<KeyT,int32_t>::value ||
|
||||
std::is_convertible<KeyT,int64_t>::value),
|
||||
std::is_convertible<KeyT,int64_t>::value ||
|
||||
std::is_convertible<KeyT,const void*>::value),
|
||||
"You are trying to use AtomicHashArray with disallowed key "
|
||||
"types. You must use atomically compare-and-swappable integer "
|
||||
"keys, or a different container class.");
|
||||
@@ -117,13 +120,15 @@ class AtomicHashArray : boost::noncopyable {
|
||||
KeyT lockedKey;
|
||||
KeyT erasedKey;
|
||||
double maxLoadFactor;
|
||||
double growthFactor;
|
||||
int entryCountThreadCacheSize;
|
||||
size_t capacity; // if positive, overrides maxLoadFactor
|
||||
|
||||
constexpr Config() : emptyKey(static_cast<KeyT>(-1ul)),
|
||||
lockedKey(static_cast<KeyT>(-2ul)),
|
||||
erasedKey(static_cast<KeyT>(-3ul)),
|
||||
constexpr Config() : emptyKey((KeyT)-1),
|
||||
lockedKey((KeyT)-2),
|
||||
erasedKey((KeyT)-3),
|
||||
maxLoadFactor(0.8),
|
||||
growthFactor(-1),
|
||||
entryCountThreadCacheSize(1000),
|
||||
capacity(0) {}
|
||||
};
|
||||
@@ -210,7 +215,7 @@ class AtomicHashArray : boost::noncopyable {
|
||||
/* Private data and helper functions... */
|
||||
|
||||
private:
|
||||
friend class AtomicHashMap<KeyT,ValueT,HashFcn>;
|
||||
friend class AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>;
|
||||
|
||||
struct SimpleRetT { size_t idx; bool success;
|
||||
SimpleRetT(size_t i, bool s) : idx(i), success(s) {}
|
||||
|
||||
+43
-43
@@ -22,16 +22,17 @@
|
||||
|
||||
namespace folly {
|
||||
|
||||
template <class KeyT, class ValueT, class HashFcn>
|
||||
const typename AtomicHashMap<KeyT, ValueT, HashFcn>::Config
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::defaultConfig;
|
||||
template <class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
const typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::Config
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::defaultConfig;
|
||||
|
||||
// AtomicHashMap constructor -- Atomic wrapper that allows growth
|
||||
// This class has a lot of overhead (184 Bytes) so only use for big maps
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
AtomicHashMap(size_t size, const Config& config)
|
||||
: kGrowthFrac_(1.0 - config.maxLoadFactor) {
|
||||
: kGrowthFrac_(config.growthFactor < 0 ?
|
||||
1.0 - config.maxLoadFactor : config.growthFactor) {
|
||||
CHECK(config.maxLoadFactor > 0.0 && config.maxLoadFactor < 1.0);
|
||||
subMaps_[0].store(SubMap::create(size, config).release(),
|
||||
std::memory_order_relaxed);
|
||||
@@ -43,9 +44,9 @@ AtomicHashMap(size_t size, const Config& config)
|
||||
}
|
||||
|
||||
// insert --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insert(key_type k, const mapped_type& v) {
|
||||
SimpleRetT ret = insertInternal(k,v);
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
@@ -53,9 +54,9 @@ insert(key_type k, const mapped_type& v) {
|
||||
ret.success);
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
std::pair<typename AtomicHashMap<KeyT,ValueT,HashFcn,EqualFcn>::iterator,bool>
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insert(key_type k, mapped_type&& v) {
|
||||
SimpleRetT ret = insertInternal(k, std::move(v));
|
||||
SubMap* subMap = subMaps_[ret.i].load(std::memory_order_relaxed);
|
||||
@@ -64,15 +65,14 @@ insert(key_type k, mapped_type&& v) {
|
||||
}
|
||||
|
||||
// insertInternal -- Allocates new sub maps as existing ones fill up.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
template <class T>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
insertInternal(key_type key, T&& value) {
|
||||
beginInsertInternal:
|
||||
int nextMapIdx = // this maintains our state
|
||||
numMapsAllocated_.load(std::memory_order_acquire);
|
||||
uint32_t idx = 0;
|
||||
typename SubMap::SimpleRetT ret;
|
||||
FOR_EACH_RANGE(i, 0, nextMapIdx) {
|
||||
// insert in each map successively. If one succeeds, we're done!
|
||||
@@ -142,9 +142,9 @@ insertInternal(key_type key, T&& value) {
|
||||
}
|
||||
|
||||
// find --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
find(KeyT k) {
|
||||
SimpleRetT ret = findInternal(k);
|
||||
if (ret.i >= numMapsAllocated_.load(std::memory_order_acquire)) {
|
||||
@@ -154,17 +154,17 @@ find(KeyT k) {
|
||||
return iterator(this, ret.i, subMap->makeIter(ret.j));
|
||||
}
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::const_iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::const_iterator
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
find(KeyT k) const {
|
||||
return const_cast<AtomicHashMap*>(this)->find(k);
|
||||
}
|
||||
|
||||
// findInternal --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findInternal(const KeyT k) const {
|
||||
SubMap* const primaryMap = subMaps_[0].load(std::memory_order_relaxed);
|
||||
typename SubMap::SimpleRetT ret = primaryMap->findInternal(k);
|
||||
@@ -185,9 +185,9 @@ findInternal(const KeyT k) const {
|
||||
}
|
||||
|
||||
// findAtInternal -- see encodeIndex() for details.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::SimpleRetT
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
findAtInternal(uint32_t idx) const {
|
||||
uint32_t subMapIdx, subMapOffset;
|
||||
if (idx & kSecondaryMapBit_) {
|
||||
@@ -205,9 +205,9 @@ findAtInternal(uint32_t idx) const {
|
||||
}
|
||||
|
||||
// erase --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn>::size_type
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
typename AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::size_type
|
||||
AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
erase(const KeyT k) {
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
FOR_EACH_RANGE(i, 0, numMaps) {
|
||||
@@ -221,8 +221,8 @@ erase(const KeyT k) {
|
||||
}
|
||||
|
||||
// capacity -- summation of capacities of all submaps
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
capacity() const {
|
||||
size_t totalCap(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
@@ -234,8 +234,8 @@ capacity() const {
|
||||
|
||||
// spaceRemaining --
|
||||
// number of new insertions until current submaps are all at max load
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
spaceRemaining() const {
|
||||
size_t spaceRem(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
@@ -251,8 +251,8 @@ spaceRemaining() const {
|
||||
|
||||
// clear -- Wipes all keys and values from primary map and destroys
|
||||
// all secondary maps. Not thread safe.
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
void AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
void AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
clear() {
|
||||
subMaps_[0].load(std::memory_order_relaxed)->clear();
|
||||
int const numMaps = numMapsAllocated_
|
||||
@@ -267,8 +267,8 @@ clear() {
|
||||
}
|
||||
|
||||
// size --
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
size_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
size() const {
|
||||
size_t totalSize(0);
|
||||
int const numMaps = numMapsAllocated_.load(std::memory_order_acquire);
|
||||
@@ -296,8 +296,8 @@ size() const {
|
||||
// 31 1
|
||||
// 27-30 which subMap
|
||||
// 0-26 subMap offset (index_ret input)
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
inline uint32_t AtomicHashMap<KeyT, ValueT, HashFcn>::
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
inline uint32_t AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::
|
||||
encodeIndex(uint32_t subMap, uint32_t offset) {
|
||||
DCHECK_EQ(offset & kSecondaryMapBit_, 0); // offset can't be too big
|
||||
if (subMap == 0) return offset;
|
||||
@@ -313,9 +313,9 @@ encodeIndex(uint32_t subMap, uint32_t offset) {
|
||||
|
||||
// Iterator implementation
|
||||
|
||||
template <typename KeyT, typename ValueT, typename HashFcn>
|
||||
template <typename KeyT, typename ValueT, typename HashFcn, typename EqualFcn>
|
||||
template<class ContT, class IterVal, class SubIt>
|
||||
struct AtomicHashMap<KeyT, ValueT, HashFcn>::ahm_iterator
|
||||
struct AtomicHashMap<KeyT, ValueT, HashFcn, EqualFcn>::ahm_iterator
|
||||
: boost::iterator_facade<ahm_iterator<ContT,IterVal,SubIt>,
|
||||
IterVal,
|
||||
boost::forward_traversal_tag>
|
||||
|
||||
+4
-8
@@ -143,10 +143,6 @@ namespace folly {
|
||||
* - We don't take the Hash function object as an instance in the
|
||||
* constructor.
|
||||
*
|
||||
* - We don't take a Compare template parameter (since our keys must
|
||||
* be integers, and the underlying hash array here uses atomic
|
||||
* compare-and-swap instructions, we only allow normal integer
|
||||
* comparisons).
|
||||
*/
|
||||
|
||||
// Thrown when insertion fails due to running out of space for
|
||||
@@ -157,16 +153,16 @@ struct AtomicHashMapFullError : std::runtime_error {
|
||||
{}
|
||||
};
|
||||
|
||||
template<class KeyT, class ValueT, class HashFcn>
|
||||
template<class KeyT, class ValueT, class HashFcn, class EqualFcn>
|
||||
class AtomicHashMap : boost::noncopyable {
|
||||
typedef AtomicHashArray<KeyT, ValueT, HashFcn> SubMap;
|
||||
typedef AtomicHashArray<KeyT, ValueT, HashFcn, EqualFcn> SubMap;
|
||||
|
||||
public:
|
||||
typedef KeyT key_type;
|
||||
typedef ValueT mapped_type;
|
||||
typedef std::pair<const KeyT, ValueT> value_type;
|
||||
typedef HashFcn hasher;
|
||||
typedef std::equal_to<KeyT> key_equal;
|
||||
typedef EqualFcn key_equal;
|
||||
typedef value_type* pointer;
|
||||
typedef value_type& reference;
|
||||
typedef const value_type& const_reference;
|
||||
@@ -204,7 +200,7 @@ class AtomicHashMap : boost::noncopyable {
|
||||
}
|
||||
}
|
||||
|
||||
key_equal key_eq() const { return key_eq(); }
|
||||
key_equal key_eq() const { return key_equal(); }
|
||||
hasher hash_function() const { return hasher(); }
|
||||
|
||||
// TODO: emplace() support would be nice.
|
||||
|
||||
+36
-15
@@ -65,14 +65,17 @@
|
||||
#error GCC required
|
||||
#endif
|
||||
|
||||
#include "folly/folly-config.h"
|
||||
#include "folly/detail/BitsDetail.h"
|
||||
#include "folly/detail/BitIteratorDetail.h"
|
||||
#include "folly/Likely.h"
|
||||
|
||||
#include <byteswap.h>
|
||||
#if FOLLY_HAVE_BYTESWAP_H
|
||||
# include <byteswap.h>
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cinttypes>
|
||||
#include <endian.h>
|
||||
#include <iterator>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
@@ -230,23 +233,41 @@ struct EndianIntBase {
|
||||
static T swap(T x);
|
||||
};
|
||||
|
||||
/**
|
||||
* If we have the bswap_16 macro from byteswap.h, use it; otherwise, provide our
|
||||
* own definition.
|
||||
*/
|
||||
#ifdef bswap_16
|
||||
# define our_bswap16 bswap_16
|
||||
#else
|
||||
|
||||
template<class Int16>
|
||||
inline constexpr typename std::enable_if<
|
||||
sizeof(Int16) == 2,
|
||||
Int16>::type
|
||||
our_bswap16(Int16 x) {
|
||||
return ((x >> 8) & 0xff) | ((x & 0xff) << 8);
|
||||
}
|
||||
#endif
|
||||
|
||||
#define FB_GEN(t, fn) \
|
||||
template<> inline t EndianIntBase<t>::swap(t x) { return fn(x); }
|
||||
|
||||
// fn(x) expands to (x) if the second argument is empty, which is exactly
|
||||
// what we want for [u]int8_t
|
||||
// what we want for [u]int8_t. Also, gcc 4.7 on Intel doesn't have
|
||||
// __builtin_bswap16 for some reason, so we have to provide our own.
|
||||
FB_GEN( int8_t,)
|
||||
FB_GEN(uint8_t,)
|
||||
FB_GEN( int64_t, bswap_64)
|
||||
FB_GEN(uint64_t, bswap_64)
|
||||
FB_GEN( int32_t, bswap_32)
|
||||
FB_GEN(uint32_t, bswap_32)
|
||||
FB_GEN( int16_t, bswap_16)
|
||||
FB_GEN(uint16_t, bswap_16)
|
||||
FB_GEN( int64_t, __builtin_bswap64)
|
||||
FB_GEN(uint64_t, __builtin_bswap64)
|
||||
FB_GEN( int32_t, __builtin_bswap32)
|
||||
FB_GEN(uint32_t, __builtin_bswap32)
|
||||
FB_GEN( int16_t, our_bswap16)
|
||||
FB_GEN(uint16_t, our_bswap16)
|
||||
|
||||
#undef FB_GEN
|
||||
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
|
||||
template <class T>
|
||||
struct EndianInt : public detail::EndianIntBase<T> {
|
||||
@@ -255,7 +276,7 @@ struct EndianInt : public detail::EndianIntBase<T> {
|
||||
static T little(T x) { return x; }
|
||||
};
|
||||
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
|
||||
template <class T>
|
||||
struct EndianInt : public detail::EndianIntBase<T> {
|
||||
@@ -266,7 +287,7 @@ struct EndianInt : public detail::EndianIntBase<T> {
|
||||
|
||||
#else
|
||||
# error Your machine uses a weird endianness!
|
||||
#endif /* __BYTE_ORDER */
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
|
||||
} // namespace detail
|
||||
|
||||
@@ -296,13 +317,13 @@ class Endian {
|
||||
};
|
||||
|
||||
static constexpr Order order =
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
Order::LITTLE;
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
Order::BIG;
|
||||
#else
|
||||
# error Your machine uses a weird endianness!
|
||||
#endif /* __BYTE_ORDER */
|
||||
#endif /* __BYTE_ORDER__ */
|
||||
|
||||
template <class T> static T swap(T x) {
|
||||
return detail::EndianInt<T>::swap(x);
|
||||
|
||||
+52
-4
@@ -64,13 +64,17 @@ typename std::enable_if<
|
||||
to(const Src & value) {
|
||||
/* static */ if (std::numeric_limits<Tgt>::max()
|
||||
< std::numeric_limits<Src>::max()) {
|
||||
FOLLY_RANGE_CHECK(value <= std::numeric_limits<Tgt>::max(),
|
||||
"Overflow");
|
||||
FOLLY_RANGE_CHECK(
|
||||
(!greater_than<Tgt, std::numeric_limits<Tgt>::max()>(value)),
|
||||
"Overflow"
|
||||
);
|
||||
}
|
||||
/* static */ if (std::is_signed<Src>::value &&
|
||||
(!std::is_signed<Tgt>::value || sizeof(Src) > sizeof(Tgt))) {
|
||||
FOLLY_RANGE_CHECK(value >= std::numeric_limits<Tgt>::min(),
|
||||
"Negative overflow");
|
||||
FOLLY_RANGE_CHECK(
|
||||
(!less_than<Tgt, std::numeric_limits<Tgt>::min()>(value)),
|
||||
"Negative overflow"
|
||||
);
|
||||
}
|
||||
return static_cast<Tgt>(value);
|
||||
}
|
||||
@@ -472,6 +476,38 @@ typename std::enable_if<detail::IsSomeString<Tgt>::value>::type
|
||||
toAppend(Tgt* result) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Variadic base case: do nothing.
|
||||
*/
|
||||
template <class Delimiter, class Tgt>
|
||||
typename std::enable_if<detail::IsSomeString<Tgt>::value>::type
|
||||
toAppendDelim(const Delimiter& delim, Tgt* result) {
|
||||
}
|
||||
|
||||
/**
|
||||
* 1 element: same as toAppend.
|
||||
*/
|
||||
template <class Delimiter, class T, class Tgt>
|
||||
typename std::enable_if<detail::IsSomeString<Tgt>::value>::type
|
||||
toAppendDelim(const Delimiter& delim, const T& v, Tgt* tgt) {
|
||||
toAppend(v, tgt);
|
||||
}
|
||||
|
||||
/**
|
||||
* Append to string with a delimiter in between elements.
|
||||
*/
|
||||
template <class Delimiter, class T, class... Ts>
|
||||
typename std::enable_if<sizeof...(Ts) >= 2
|
||||
&& detail::IsSomeString<
|
||||
typename std::remove_pointer<
|
||||
typename std::tuple_element<
|
||||
sizeof...(Ts) - 1, std::tuple<Ts...>
|
||||
>::type>::type>::value>::type
|
||||
toAppendDelim(const Delimiter& delim, const T& v, const Ts&... vs) {
|
||||
toAppend(v, delim, detail::getLastElement(vs...));
|
||||
toAppendDelim(delim, vs...);
|
||||
}
|
||||
|
||||
/**
|
||||
* to<SomeString>(v1, v2, ...) uses toAppend() (see below) as back-end
|
||||
* for all types.
|
||||
@@ -484,6 +520,18 @@ to(const Ts&... vs) {
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* toDelim<SomeString>(delim, v1, v2, ...) uses toAppendDelim() as
|
||||
* back-end for all types.
|
||||
*/
|
||||
template <class Tgt, class Delim, class... Ts>
|
||||
typename std::enable_if<detail::IsSomeString<Tgt>::value, Tgt>::type
|
||||
toDelim(const Delim& delim, const Ts&... vs) {
|
||||
Tgt result;
|
||||
toAppendDelim(delim, vs..., &result);
|
||||
return result;
|
||||
}
|
||||
|
||||
/*******************************************************************************
|
||||
* Conversions from string types to integral types.
|
||||
******************************************************************************/
|
||||
|
||||
+50
-17
@@ -19,60 +19,93 @@
|
||||
|
||||
#include <errno.h>
|
||||
|
||||
#include <cstdio>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/FBString.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Portability.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
// Various helpers to throw appropriate std::system_error exceptions from C
|
||||
// library errors (returned in errno, as positive return values (many POSIX
|
||||
// functions), or as negative return values (Linux syscalls))
|
||||
//
|
||||
// The *Explicit functions take an explicit value for errno.
|
||||
|
||||
// Helper to throw std::system_error
|
||||
void throwSystemError(int err, const char* msg) __attribute__((noreturn));
|
||||
inline void throwSystemError(int err, const char* msg) {
|
||||
void throwSystemErrorExplicit(int err, const char*) FOLLY_NORETURN;
|
||||
inline void throwSystemErrorExplicit(int err, const char* msg) {
|
||||
throw std::system_error(err, std::system_category(), msg);
|
||||
}
|
||||
|
||||
// Helper to throw std::system_error from errno
|
||||
void throwSystemError(const char* msg) __attribute__((noreturn));
|
||||
inline void throwSystemError(const char* msg) {
|
||||
throwSystemError(errno, msg);
|
||||
template <class... Args>
|
||||
void throwSystemErrorExplicit(int, Args&&... args) FOLLY_NORETURN;
|
||||
template <class... Args>
|
||||
void throwSystemErrorExplicit(int err, Args&&... args) {
|
||||
throwSystemErrorExplicit(
|
||||
err, to<fbstring>(std::forward<Args>(args)...).c_str());
|
||||
}
|
||||
|
||||
// Helper to throw std::system_error from errno and components of a string
|
||||
template <class... Args>
|
||||
void throwSystemError(Args... args) __attribute__((noreturn));
|
||||
void throwSystemError(Args&&... args) FOLLY_NORETURN;
|
||||
template <class... Args>
|
||||
inline void throwSystemError(Args... args) {
|
||||
throwSystemError(errno, folly::to<std::string>(args...));
|
||||
void throwSystemError(Args&&... args) {
|
||||
throwSystemErrorExplicit(errno, std::forward<Args>(args)...);
|
||||
}
|
||||
|
||||
// Check a Posix return code (0 on success, error number on error), throw
|
||||
// on error.
|
||||
inline void checkPosixError(int err, const char* msg) {
|
||||
template <class... Args>
|
||||
void checkPosixError(int err, Args&&... args) {
|
||||
if (UNLIKELY(err != 0)) {
|
||||
throwSystemError(err, msg);
|
||||
throwSystemErrorExplicit(err, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check a Linux kernel-style return code (>= 0 on success, negative error
|
||||
// number on error), throw on error.
|
||||
inline void checkKernelError(ssize_t ret, const char* msg) {
|
||||
template <class... Args>
|
||||
void checkKernelError(ssize_t ret, Args&&... args) {
|
||||
if (UNLIKELY(ret < 0)) {
|
||||
throwSystemError(-ret, msg);
|
||||
throwSystemErrorExplicit(-ret, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check a traditional Unix return code (-1 and sets errno on error), throw
|
||||
// on error.
|
||||
inline void checkUnixError(ssize_t ret, const char* msg) {
|
||||
template <class... Args>
|
||||
void checkUnixError(ssize_t ret, Args&&... args) {
|
||||
if (UNLIKELY(ret == -1)) {
|
||||
throwSystemError(msg);
|
||||
throwSystemError(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
inline void checkUnixError(ssize_t ret, int savedErrno, const char* msg) {
|
||||
|
||||
template <class... Args>
|
||||
void checkUnixErrorExplicit(ssize_t ret, int savedErrno, Args&&... args) {
|
||||
if (UNLIKELY(ret == -1)) {
|
||||
throwSystemError(savedErrno, msg);
|
||||
throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
// Check the return code from a fopen-style function (returns a non-nullptr
|
||||
// FILE* on success, nullptr on error, sets errno). Works with fopen, fdopen,
|
||||
// freopen, tmpfile, etc.
|
||||
template <class... Args>
|
||||
void checkFopenError(FILE* fp, Args&&... args) {
|
||||
if (UNLIKELY(!fp)) {
|
||||
throwSystemError(std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
template <class... Args>
|
||||
void checkFopenErrorExplicit(FILE* fp, int savedErrno, Args&&... args) {
|
||||
if (UNLIKELY(!fp)) {
|
||||
throwSystemErrorExplicit(savedErrno, std::forward<Args>(args)...);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+5
-2
@@ -1070,8 +1070,11 @@ public:
|
||||
|
||||
// Move assignment
|
||||
basic_fbstring& operator=(basic_fbstring&& goner) {
|
||||
// Self move assignment is illegal, see 17.6.4.9 for the explanation
|
||||
assert(&goner != this);
|
||||
if (FBSTRING_UNLIKELY(&goner == this)) {
|
||||
// Compatibility with std::basic_string<>,
|
||||
// 21.4.2 [string.cons] / 23 requires self-move-assignment support.
|
||||
return *this;
|
||||
}
|
||||
// No need of this anymore
|
||||
this->~basic_fbstring();
|
||||
// Move the goner into this
|
||||
|
||||
+2
-2
@@ -1095,7 +1095,7 @@ public:
|
||||
if (impl_.b_)
|
||||
M_deallocate(impl_.b_, impl_.z_ - impl_.b_);
|
||||
impl_.z_ = newB + newCap;
|
||||
impl_.e_ += newB - impl_.b_; // speed hax
|
||||
impl_.e_ = newB + (impl_.e_ - impl_.b_);
|
||||
impl_.b_ = newB;
|
||||
}
|
||||
|
||||
@@ -1128,7 +1128,7 @@ public:
|
||||
if (impl_.b_)
|
||||
M_deallocate(impl_.b_, impl_.z_ - impl_.b_);
|
||||
impl_.z_ = newB + newCap;
|
||||
impl_.e_ += newB - impl_.b_; // speed hax
|
||||
impl_.e_ = newB + (impl_.e_ - impl_.b_);
|
||||
impl_.b_ = newB;
|
||||
}
|
||||
}
|
||||
|
||||
+34
-12
@@ -15,7 +15,13 @@
|
||||
*/
|
||||
|
||||
#include "folly/File.h"
|
||||
|
||||
#include <sys/file.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
#include "folly/Format.h"
|
||||
#include "folly/Exception.h"
|
||||
#include "folly/ScopeGuard.h"
|
||||
|
||||
#include <system_error>
|
||||
@@ -37,11 +43,9 @@ File::File(int fd, bool ownsFd)
|
||||
File::File(const char* name, int flags, mode_t mode)
|
||||
: fd_(::open(name, flags, mode))
|
||||
, ownsFd_(false) {
|
||||
|
||||
if (fd_ < 0) {
|
||||
throw std::system_error(errno, std::system_category(),
|
||||
folly::format("open(\"{}\", {:#o}, 0{:#o}) failed",
|
||||
name, flags, mode).str());
|
||||
if (fd_ == -1) {
|
||||
throwSystemError(folly::format("open(\"{}\", {:#o}, 0{:#o}) failed",
|
||||
name, flags, mode).fbstr());
|
||||
}
|
||||
ownsFd_ = true;
|
||||
}
|
||||
@@ -66,15 +70,11 @@ File::~File() {
|
||||
/* static */ File File::temporary() {
|
||||
// make a temp file with tmpfile(), dup the fd, then return it in a File.
|
||||
FILE* tmpFile = tmpfile();
|
||||
if (!tmpFile) {
|
||||
throw std::system_error(errno, std::system_category(), "tmpfile() failed");
|
||||
}
|
||||
checkFopenError(tmpFile, "tmpfile() failed");
|
||||
SCOPE_EXIT { fclose(tmpFile); };
|
||||
|
||||
int fd = dup(fileno(tmpFile));
|
||||
if (fd < 0) {
|
||||
throw std::system_error(errno, std::system_category(), "dup() failed");
|
||||
}
|
||||
checkUnixError(fd, "dup() failed");
|
||||
|
||||
return File(fd, true);
|
||||
}
|
||||
@@ -96,7 +96,7 @@ void swap(File& a, File& b) {
|
||||
|
||||
void File::close() {
|
||||
if (!closeNoThrow()) {
|
||||
throw std::system_error(errno, std::system_category(), "close() failed");
|
||||
throwSystemError("close() failed");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -106,4 +106,26 @@ bool File::closeNoThrow() {
|
||||
return r == 0;
|
||||
}
|
||||
|
||||
void File::lock() { doLock(LOCK_EX); }
|
||||
bool File::try_lock() { return doTryLock(LOCK_EX); }
|
||||
void File::lock_shared() { doLock(LOCK_SH); }
|
||||
bool File::try_lock_shared() { return doTryLock(LOCK_SH); }
|
||||
|
||||
void File::doLock(int op) {
|
||||
checkUnixError(flock(fd_, op), "flock() failed (lock)");
|
||||
}
|
||||
|
||||
bool File::doTryLock(int op) {
|
||||
int r = flock(fd_, op | LOCK_NB);
|
||||
// flock returns EWOULDBLOCK if already locked
|
||||
if (r == -1 && errno == EWOULDBLOCK) return false;
|
||||
checkUnixError(r, "flock() failed (try_lock)");
|
||||
return true;
|
||||
}
|
||||
|
||||
void File::unlock() {
|
||||
checkUnixError(flock(fd_, LOCK_UN), "flock() failed (unlock)");
|
||||
}
|
||||
void File::unlock_shared() { unlock(); }
|
||||
|
||||
} // namespace folly
|
||||
|
||||
+21
@@ -20,6 +20,7 @@
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
#include <fcntl.h>
|
||||
#include <unistd.h>
|
||||
|
||||
namespace folly {
|
||||
|
||||
@@ -92,7 +93,26 @@ class File {
|
||||
File(File&&);
|
||||
File& operator=(File&&);
|
||||
|
||||
// FLOCK (INTERPROCESS) LOCKS
|
||||
//
|
||||
// NOTE THAT THESE LOCKS ARE flock() LOCKS. That is, they may only be used
|
||||
// for inter-process synchronization -- an attempt to acquire a second lock
|
||||
// on the same file descriptor from the same process may succeed. Attempting
|
||||
// to acquire a second lock on a different file descriptor for the same file
|
||||
// should fail, but some systems might implement flock() using fcntl() locks,
|
||||
// in which case it will succeed.
|
||||
void lock();
|
||||
bool try_lock();
|
||||
void unlock();
|
||||
|
||||
void lock_shared();
|
||||
bool try_lock_shared();
|
||||
void unlock_shared();
|
||||
|
||||
private:
|
||||
void doLock(int op);
|
||||
bool doTryLock(int op);
|
||||
|
||||
// unique
|
||||
File(const File&) = delete;
|
||||
File& operator=(const File&) = delete;
|
||||
@@ -103,6 +123,7 @@ class File {
|
||||
|
||||
void swap(File& a, File& b);
|
||||
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_FILE_H_ */
|
||||
|
||||
+4
-4
@@ -18,6 +18,8 @@
|
||||
#error This file may only be included from Format.h.
|
||||
#endif
|
||||
|
||||
#include "folly/Traits.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail {
|
||||
@@ -45,7 +47,7 @@ size_t uintToHex(char* buffer, size_t bufLen, Uint v,
|
||||
const char (&repr)[256][2]) {
|
||||
// 'v >>= 7, v >>= 1' is no more than a work around to get rid of shift size
|
||||
// warning when Uint = uint8_t (it's false as v >= 256 implies sizeof(v) > 1).
|
||||
for (; v >= 256; v >>= 7, v >>= 1) {
|
||||
for (; !less_than<unsigned, 256>(v); v >>= 7, v >>= 1) {
|
||||
auto b = v & 0xff;
|
||||
bufLen -= 2;
|
||||
buffer[bufLen] = repr[b][0];
|
||||
@@ -89,7 +91,7 @@ size_t uintToOctal(char* buffer, size_t bufLen, Uint v) {
|
||||
auto& repr = formatOctal;
|
||||
// 'v >>= 7, v >>= 2' is no more than a work around to get rid of shift size
|
||||
// warning when Uint = uint8_t (it's false as v >= 512 implies sizeof(v) > 1).
|
||||
for (; v >= 512; v >>= 7, v >>= 2) {
|
||||
for (; !less_than<unsigned, 512>(v); v >>= 7, v >>= 2) {
|
||||
auto b = v & 0x1ff;
|
||||
bufLen -= 3;
|
||||
buffer[bufLen] = repr[b][0];
|
||||
@@ -562,8 +564,6 @@ class FormatValue<double> {
|
||||
arg.precision = 6;
|
||||
}
|
||||
|
||||
bool done = false;
|
||||
|
||||
// 2+: for null terminator and optional sign shenanigans.
|
||||
char buf[2 + std::max({
|
||||
(2 + DoubleToStringConverter::kMaxFixedDigitsBeforePoint +
|
||||
|
||||
+4
-3
@@ -18,9 +18,10 @@
|
||||
#define FOLLY_FORMATARG_H_
|
||||
|
||||
#include <stdexcept>
|
||||
#include "folly/Range.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/Range.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
@@ -71,7 +72,7 @@ struct FormatArg {
|
||||
}
|
||||
|
||||
template <typename... Args>
|
||||
void error(Args&&... args) const __attribute__((noreturn));
|
||||
void error(Args&&... args) const FOLLY_NORETURN;
|
||||
/**
|
||||
* Full argument string, as passed in to the constructor.
|
||||
*/
|
||||
|
||||
+1
-1
@@ -43,7 +43,7 @@ namespace folly {
|
||||
#pragma GCC system_header
|
||||
#define FOLLY_HAVE_MALLOC_H 1
|
||||
#else
|
||||
#include "folly-config.h"
|
||||
#include "folly/Portability.h"
|
||||
#endif
|
||||
|
||||
// for malloc_usable_size
|
||||
|
||||
+57
@@ -78,9 +78,28 @@ template <class Alloc> class StlAllocator<Alloc, void> {
|
||||
typedef void value_type;
|
||||
typedef void* pointer;
|
||||
typedef const void* const_pointer;
|
||||
|
||||
StlAllocator() : alloc_(nullptr) { }
|
||||
explicit StlAllocator(Alloc* alloc) : alloc_(alloc) { }
|
||||
|
||||
Alloc* alloc() const {
|
||||
return alloc_;
|
||||
}
|
||||
|
||||
template <class U> struct rebind {
|
||||
typedef StlAllocator<Alloc, U> other;
|
||||
};
|
||||
|
||||
bool operator!=(const StlAllocator<Alloc, void>& other) const {
|
||||
return alloc_ != other.alloc_;
|
||||
}
|
||||
|
||||
bool operator==(const StlAllocator<Alloc, void>& other) const {
|
||||
return alloc_ == other.alloc_;
|
||||
}
|
||||
|
||||
private:
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
template <class Alloc, class T>
|
||||
@@ -150,6 +169,18 @@ class StlAllocator {
|
||||
Alloc* alloc_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Helper function to obtain rebound allocators
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
template <typename T, typename Allocator>
|
||||
typename Allocator::template rebind<T>::other rebind_allocator(
|
||||
Allocator const &allocator
|
||||
) {
|
||||
return typename Allocator::template rebind<T>::other(allocator);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper classes/functions for creating a unique_ptr using a custom allocator
|
||||
*
|
||||
@@ -210,6 +241,18 @@ public:
|
||||
&& !has_destroy<allocator, void(void*)>::value;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
struct as_stl_allocator {
|
||||
typedef typename std::conditional<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
folly::StlAllocator<
|
||||
typename std::remove_reference<Allocator>::type,
|
||||
typename std::remove_reference<T>::type
|
||||
>,
|
||||
typename std::remove_reference<Allocator>::type
|
||||
>::type type;
|
||||
};
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
typename std::enable_if<
|
||||
is_simple_allocator<T, Allocator>::value,
|
||||
@@ -232,6 +275,13 @@ typename std::enable_if<
|
||||
return std::move(allocator);
|
||||
}
|
||||
|
||||
/**
|
||||
* AllocatorUniquePtr: a unique_ptr that supports both STL-style
|
||||
* allocators and SimpleAllocator
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
template <typename T, typename Allocator>
|
||||
struct AllocatorUniquePtr {
|
||||
typedef std::unique_ptr<T,
|
||||
@@ -245,6 +295,13 @@ struct AllocatorUniquePtr {
|
||||
> type;
|
||||
};
|
||||
|
||||
/**
|
||||
* Functions to allocate a unique_ptr / shared_ptr, supporting both
|
||||
* STL-style allocators and SimpleAllocator, analog to std::allocate_shared
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
template <typename T, typename Allocator, typename ...Args>
|
||||
typename AllocatorUniquePtr<T, Allocator>::type allocate_unique(
|
||||
Allocator&& allocator, Args&&... args
|
||||
|
||||
+1
-1
@@ -22,7 +22,7 @@
|
||||
#include <sys/types.h>
|
||||
#include <system_error>
|
||||
|
||||
#define FLAGS_mlock_chunk_size (1<<20)
|
||||
#define FLAGS_mlock_chunk_size (1 << 20)
|
||||
|
||||
namespace folly {
|
||||
|
||||
|
||||
+53
-34
@@ -61,6 +61,7 @@
|
||||
|
||||
#include <boost/operators.hpp>
|
||||
|
||||
|
||||
namespace folly {
|
||||
|
||||
namespace detail { struct NoneHelper {}; }
|
||||
@@ -81,10 +82,7 @@ const None none = nullptr;
|
||||
#endif // __GNUC__
|
||||
|
||||
template<class Value>
|
||||
class Optional : boost::totally_ordered<Optional<Value>,
|
||||
boost::totally_ordered<Optional<Value>, Value>> {
|
||||
typedef void (Optional::*bool_type)() const;
|
||||
void truthy() const {};
|
||||
class Optional {
|
||||
public:
|
||||
static_assert(!std::is_reference<Value>::value,
|
||||
"Optional may not be used with reference types");
|
||||
@@ -110,7 +108,7 @@ class Optional : boost::totally_ordered<Optional<Value>,
|
||||
}
|
||||
}
|
||||
|
||||
/* implicit */ Optional(const None& empty)
|
||||
/* implicit */ Optional(const None&)
|
||||
: hasValue_(false) {
|
||||
}
|
||||
|
||||
@@ -179,32 +177,6 @@ class Optional : boost::totally_ordered<Optional<Value>,
|
||||
return *this;
|
||||
}
|
||||
|
||||
bool operator<(const Optional& other) const {
|
||||
if (hasValue() != other.hasValue()) {
|
||||
return hasValue() < other.hasValue();
|
||||
}
|
||||
if (hasValue()) {
|
||||
return value() < other.value();
|
||||
}
|
||||
return false; // both empty
|
||||
}
|
||||
|
||||
bool operator<(const Value& other) const {
|
||||
return !hasValue() || value() < other;
|
||||
}
|
||||
|
||||
bool operator==(const Optional& other) const {
|
||||
if (hasValue()) {
|
||||
return other.hasValue() && value() == other.value();
|
||||
} else {
|
||||
return !other.hasValue();
|
||||
}
|
||||
}
|
||||
|
||||
bool operator==(const Value& other) const {
|
||||
return hasValue() && value() == other;
|
||||
}
|
||||
|
||||
template<class... Args>
|
||||
void emplace(Args&&... args) {
|
||||
clear();
|
||||
@@ -230,9 +202,8 @@ class Optional : boost::totally_ordered<Optional<Value>,
|
||||
|
||||
bool hasValue() const { return hasValue_; }
|
||||
|
||||
/* safe bool idiom */
|
||||
operator bool_type() const {
|
||||
return hasValue() ? &Optional::truthy : nullptr;
|
||||
explicit operator bool() const {
|
||||
return hasValue();
|
||||
}
|
||||
|
||||
const Value& operator*() const { return value(); }
|
||||
@@ -286,6 +257,54 @@ Opt make_optional(T&& v) {
|
||||
return Opt(std::forward<T>(v));
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator< (const Optional<V>& a, const Optional<V>& b) {
|
||||
if (a.hasValue() != b.hasValue()) { return a.hasValue() < b.hasValue(); }
|
||||
if (a.hasValue()) { return a.value() < b.value(); }
|
||||
return false;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator==(const Optional<V>& a, const Optional<V>& b) {
|
||||
if (a.hasValue() != b.hasValue()) { return false; }
|
||||
if (a.hasValue()) { return a.value() == b.value(); }
|
||||
return true;
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator<=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(b < a);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator!=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(b == a);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator>=(const Optional<V>& a, const Optional<V>& b) {
|
||||
return !(a < b);
|
||||
}
|
||||
|
||||
template<class V>
|
||||
bool operator> (const Optional<V>& a, const Optional<V>& b) {
|
||||
return b < a;
|
||||
}
|
||||
|
||||
// To supress comparability of Optional<T> with T, despite implicit conversion.
|
||||
template<class V> bool operator< (const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator<=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator==(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator!=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator>=(const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator> (const Optional<V>&, const V& other) = delete;
|
||||
template<class V> bool operator< (const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator<=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator==(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator!=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator>=(const V& other, const Optional<V>&) = delete;
|
||||
template<class V> bool operator> (const V& other, const Optional<V>&) = delete;
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif//FOLLY_OPTIONAL_H_
|
||||
|
||||
+37
-26
@@ -19,6 +19,11 @@
|
||||
|
||||
#include "folly-config.h"
|
||||
|
||||
#ifdef FOLLY_HAVE_FEATURES_H
|
||||
#include <features.h>
|
||||
#endif
|
||||
|
||||
|
||||
#ifdef FOLLY_HAVE_SCHED_H
|
||||
#include <sched.h>
|
||||
#ifndef FOLLY_HAVE_PTHREAD_YIELD
|
||||
@@ -26,32 +31,6 @@
|
||||
#endif
|
||||
#endif
|
||||
|
||||
// Define macro wrappers for C++11's "final" and "override" keywords, which
|
||||
// are supported in gcc 4.7 but not gcc 4.6.
|
||||
//
|
||||
// TODO(tudorb/agallagher): Autotoolize this.
|
||||
#undef FOLLY_FINAL
|
||||
#undef FOLLY_OVERRIDE
|
||||
|
||||
#if defined(__clang__)
|
||||
# define FOLLY_FINAL final
|
||||
# define FOLLY_OVERRIDE override
|
||||
#elif defined(__GNUC__)
|
||||
# include <features.h>
|
||||
# if __GNUC_PREREQ(4,7)
|
||||
# define FOLLY_FINAL final
|
||||
# define FOLLY_OVERRIDE override
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FOLLY_FINAL
|
||||
# define FOLLY_FINAL
|
||||
#endif
|
||||
|
||||
#ifndef FOLLY_OVERRIDE
|
||||
# define FOLLY_OVERRIDE
|
||||
#endif
|
||||
|
||||
|
||||
// MaxAlign: max_align_t isn't supported by gcc
|
||||
#ifdef __GNUC__
|
||||
@@ -60,4 +39,36 @@ struct MaxAlign { char c; } __attribute__((aligned));
|
||||
# error Cannot define MaxAlign on this platform
|
||||
#endif
|
||||
|
||||
|
||||
// noreturn
|
||||
#if defined(__clang__) || defined(__GNUC__)
|
||||
# define FOLLY_NORETURN __attribute__((noreturn))
|
||||
#else
|
||||
# define FOLLY_NORETURN
|
||||
#endif
|
||||
|
||||
|
||||
/* Define macro wrappers for C++11's "final" and "override" keywords, which
|
||||
* are supported in gcc 4.7 but not gcc 4.6. */
|
||||
#if !defined(FOLLY_FINAL) && !defined(FOLLY_OVERRIDE)
|
||||
# if defined(__clang__) || \
|
||||
(defined(__GNUC__) && defined(__GNUC_MINOR__) && \
|
||||
((__GNUC__ << 16) + __GNUC_MINOR__) >= ((4 << 16) + 7))
|
||||
# define FOLLY_FINAL final
|
||||
# define FOLLY_OVERRIDE override
|
||||
# else
|
||||
# define FOLLY_FINAL /**/
|
||||
# define FOLLY_OVERRIDE /**/
|
||||
# endif
|
||||
#endif
|
||||
|
||||
|
||||
// Define to 1 if you have the `preadv' and `pwritev' functions, respectively
|
||||
#if !defined(FOLLY_HAVE_PREADV) && !defined(FOLLY_HAVE_PWRITEV)
|
||||
# if defined(__GLIBC_PREREQ) && __GLIBC_PREREQ(2, 10)
|
||||
# define FOLLY_HAVE_PREADV 1
|
||||
# define FOLLY_HAVE_PWRITEV 1
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#endif // FOLLY_PORTABILITY_H_
|
||||
|
||||
+1
-1
@@ -20,7 +20,7 @@
|
||||
#include "folly/Range.h"
|
||||
|
||||
#include <emmintrin.h> // __v16qi
|
||||
#include "folly/Likely.h"
|
||||
#include <iostream>
|
||||
|
||||
namespace folly {
|
||||
|
||||
|
||||
+58
-31
@@ -20,21 +20,20 @@
|
||||
#ifndef FOLLY_RANGE_H_
|
||||
#define FOLLY_RANGE_H_
|
||||
|
||||
#include "folly/folly-config.h"
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/FBString.h"
|
||||
#include <glog/logging.h>
|
||||
#include <algorithm>
|
||||
#include <cstring>
|
||||
#include <iostream>
|
||||
#include <iosfwd>
|
||||
#include <string>
|
||||
#include <stdexcept>
|
||||
#include <type_traits>
|
||||
#include <boost/operators.hpp>
|
||||
#include <boost/utility/enable_if.hpp>
|
||||
#include <boost/type_traits.hpp>
|
||||
#include <bits/c++config.h>
|
||||
#include "folly/CpuId.h"
|
||||
#include "folly/Traits.h"
|
||||
#include "folly/Likely.h"
|
||||
|
||||
namespace folly {
|
||||
|
||||
@@ -77,9 +76,9 @@ namespace detail {
|
||||
* For random-access iterators, the value before is simply i[-1].
|
||||
*/
|
||||
template <class Iter>
|
||||
typename boost::enable_if_c<
|
||||
boost::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::enable_if<
|
||||
std::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_before(Iter i) {
|
||||
return i[-1];
|
||||
@@ -89,9 +88,9 @@ value_before(Iter i) {
|
||||
* For all other iterators, we need to use the decrement operator.
|
||||
*/
|
||||
template <class Iter>
|
||||
typename boost::enable_if_c<
|
||||
!boost::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::enable_if<
|
||||
!std::is_same<typename std::iterator_traits<Iter>::iterator_category,
|
||||
std::random_access_iterator_tag>::value,
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_before(Iter i) {
|
||||
return *--i;
|
||||
@@ -116,7 +115,7 @@ public:
|
||||
typedef std::size_t size_type;
|
||||
typedef Iter iterator;
|
||||
typedef Iter const_iterator;
|
||||
typedef typename boost::remove_reference<
|
||||
typedef typename std::remove_reference<
|
||||
typename std::iterator_traits<Iter>::reference>::type
|
||||
value_type;
|
||||
typedef typename std::iterator_traits<Iter>::reference reference;
|
||||
@@ -145,7 +144,9 @@ public:
|
||||
: b_(str.data()), e_(b_ + str.size()) {}
|
||||
// Works only for Range<const char*>
|
||||
Range(const std::string& str, std::string::size_type startFrom) {
|
||||
CHECK_LE(startFrom, str.size());
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = str.data() + str.size();
|
||||
}
|
||||
@@ -153,32 +154,52 @@ public:
|
||||
Range(const std::string& str,
|
||||
std::string::size_type startFrom,
|
||||
std::string::size_type size) {
|
||||
CHECK_LE(startFrom + size, str.size());
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = b_ + size;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.data() + str.size();
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
Range(const Range<Iter>& str,
|
||||
size_t startFrom,
|
||||
size_t size) {
|
||||
CHECK_LE(startFrom + size, str.size());
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.b_ + startFrom;
|
||||
e_ = b_ + size;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.e_;
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
/* implicit */ Range(const fbstring& str)
|
||||
: b_(str.data()), e_(b_ + str.size()) { }
|
||||
// Works only for Range<const char*>
|
||||
Range(const fbstring& str, fbstring::size_type startFrom) {
|
||||
CHECK_LE(startFrom, str.size());
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = str.data() + str.size();
|
||||
}
|
||||
// Works only for Range<const char*>
|
||||
Range(const fbstring& str, fbstring::size_type startFrom,
|
||||
fbstring::size_type size) {
|
||||
CHECK_LE(startFrom + size, str.size());
|
||||
if (UNLIKELY(startFrom > str.size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ = str.data() + startFrom;
|
||||
e_ = b_ + size;
|
||||
if (str.size() - startFrom < size) {
|
||||
e_ = str.data() + str.size();
|
||||
} else {
|
||||
e_ = b_ + size;
|
||||
}
|
||||
}
|
||||
|
||||
// Allow implicit conversion from Range<const char*> (aka StringPiece) to
|
||||
@@ -301,12 +322,16 @@ public:
|
||||
}
|
||||
|
||||
void advance(size_type n) {
|
||||
CHECK_LE(n, size());
|
||||
if (UNLIKELY(n > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
b_ += n;
|
||||
}
|
||||
|
||||
void subtract(size_type n) {
|
||||
CHECK_LE(n, size());
|
||||
if (UNLIKELY(n > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
e_ -= n;
|
||||
}
|
||||
|
||||
@@ -322,7 +347,9 @@ public:
|
||||
|
||||
Range subpiece(size_type first,
|
||||
size_type length = std::string::npos) const {
|
||||
CHECK_LE(first, size());
|
||||
if (UNLIKELY(first > size())) {
|
||||
throw std::out_of_range("index out of range");
|
||||
}
|
||||
return Range(b_ + first,
|
||||
std::min<std::string::size_type>(length, size() - first));
|
||||
}
|
||||
@@ -452,11 +479,11 @@ template <class A, class B>
|
||||
struct ComparableAsStringPiece {
|
||||
enum {
|
||||
value =
|
||||
(boost::is_convertible<A, StringPiece>::value
|
||||
&& boost::is_same<B, StringPiece>::value)
|
||||
(std::is_convertible<A, StringPiece>::value
|
||||
&& std::is_same<B, StringPiece>::value)
|
||||
||
|
||||
(boost::is_convertible<B, StringPiece>::value
|
||||
&& boost::is_same<A, StringPiece>::value)
|
||||
(std::is_convertible<B, StringPiece>::value
|
||||
&& std::is_same<A, StringPiece>::value)
|
||||
};
|
||||
};
|
||||
|
||||
@@ -467,7 +494,7 @@ struct ComparableAsStringPiece {
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
boost::enable_if_c<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator==(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) == StringPiece(rhs);
|
||||
}
|
||||
@@ -477,7 +504,7 @@ operator==(const T& lhs, const U& rhs) {
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
boost::enable_if_c<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator<(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) < StringPiece(rhs);
|
||||
}
|
||||
@@ -487,7 +514,7 @@ operator<(const T& lhs, const U& rhs) {
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
boost::enable_if_c<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator>(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) > StringPiece(rhs);
|
||||
}
|
||||
@@ -497,7 +524,7 @@ operator>(const T& lhs, const U& rhs) {
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
boost::enable_if_c<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator<=(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) <= StringPiece(rhs);
|
||||
}
|
||||
@@ -507,7 +534,7 @@ operator<=(const T& lhs, const U& rhs) {
|
||||
*/
|
||||
template <class T, class U>
|
||||
typename
|
||||
boost::enable_if_c<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
std::enable_if<detail::ComparableAsStringPiece<T, U>::value, bool>::type
|
||||
operator>=(const T& lhs, const U& rhs) {
|
||||
return StringPiece(lhs) >= StringPiece(rhs);
|
||||
}
|
||||
|
||||
+1
-1
@@ -104,7 +104,7 @@ class ScopeGuardImpl : public ScopeGuardImplBase {
|
||||
}
|
||||
}
|
||||
|
||||
private:
|
||||
private:
|
||||
void* operator new(size_t) = delete;
|
||||
|
||||
void execute() noexcept { function_(); }
|
||||
|
||||
+136
@@ -149,6 +149,95 @@ void cUnescape(StringPiece str, String& out, bool strict) {
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
// Map from character code to escape mode:
|
||||
// 0 = pass through
|
||||
// 1 = unused
|
||||
// 2 = pass through in PATH mode
|
||||
// 3 = space, replace with '+' in QUERY mode
|
||||
// 4 = percent-encode
|
||||
extern const unsigned char uriEscapeTable[];
|
||||
} // namespace detail
|
||||
|
||||
template <class String>
|
||||
void uriEscape(StringPiece str, String& out, UriEscapeMode mode) {
|
||||
static const char hexValues[] = "0123456789abcdef";
|
||||
char esc[3];
|
||||
esc[0] = '%';
|
||||
// Preallocate assuming that 25% of the input string will be escaped
|
||||
out.reserve(out.size() + str.size() + 3 * (str.size() / 4));
|
||||
auto p = str.begin();
|
||||
auto last = p; // last regular character
|
||||
// We advance over runs of passthrough characters and copy them in one go;
|
||||
// this is faster than calling push_back repeatedly.
|
||||
unsigned char minEncode = static_cast<unsigned char>(mode);
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
unsigned char v = static_cast<unsigned char>(c);
|
||||
unsigned char discriminator = detail::uriEscapeTable[v];
|
||||
if (LIKELY(discriminator <= minEncode)) {
|
||||
++p;
|
||||
} else if (mode == UriEscapeMode::QUERY && discriminator == 3) {
|
||||
out.append(&*last, p - last);
|
||||
out.push_back('+');
|
||||
++p;
|
||||
last = p;
|
||||
} else {
|
||||
out.append(&*last, p - last);
|
||||
esc[1] = hexValues[v >> 4];
|
||||
esc[2] = hexValues[v & 0x0f];
|
||||
out.append(esc, 3);
|
||||
++p;
|
||||
last = p;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
template <class String>
|
||||
void uriUnescape(StringPiece str, String& out, UriEscapeMode mode) {
|
||||
out.reserve(out.size() + str.size());
|
||||
auto p = str.begin();
|
||||
auto last = p;
|
||||
// We advance over runs of passthrough characters and copy them in one go;
|
||||
// this is faster than calling push_back repeatedly.
|
||||
while (p != str.end()) {
|
||||
char c = *p;
|
||||
unsigned char v = static_cast<unsigned char>(v);
|
||||
switch (c) {
|
||||
case '%':
|
||||
{
|
||||
if (UNLIKELY(std::distance(p, str.end()) < 3)) {
|
||||
throw std::invalid_argument("incomplete percent encode sequence");
|
||||
}
|
||||
auto h1 = detail::hexTable[static_cast<unsigned char>(p[1])];
|
||||
auto h2 = detail::hexTable[static_cast<unsigned char>(p[2])];
|
||||
if (UNLIKELY(h1 == 16 || h2 == 16)) {
|
||||
throw std::invalid_argument("invalid percent encode sequence");
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
out.push_back((h1 << 4) | h2);
|
||||
p += 3;
|
||||
last = p;
|
||||
break;
|
||||
}
|
||||
case '+':
|
||||
if (mode == UriEscapeMode::QUERY) {
|
||||
out.append(&*last, p - last);
|
||||
out.push_back(' ');
|
||||
++p;
|
||||
last = p;
|
||||
break;
|
||||
}
|
||||
// else fallthrough
|
||||
default:
|
||||
++p;
|
||||
break;
|
||||
}
|
||||
}
|
||||
out.append(&*last, p - last);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
/*
|
||||
@@ -258,6 +347,39 @@ template<class String> StringPiece prepareDelim(const String& s) {
|
||||
}
|
||||
inline char prepareDelim(char c) { return c; }
|
||||
|
||||
template<bool exact,
|
||||
class Delim>
|
||||
bool splitFixed(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& out) {
|
||||
if (exact && UNLIKELY(std::string::npos != input.find(delimiter))) {
|
||||
return false;
|
||||
}
|
||||
out = input;
|
||||
return true;
|
||||
}
|
||||
|
||||
template<bool exact,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool splitFixed(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail) {
|
||||
size_t cut = input.find(delimiter);
|
||||
if (UNLIKELY(cut == std::string::npos)) {
|
||||
return false;
|
||||
}
|
||||
StringPiece head(input.begin(), input.begin() + cut);
|
||||
StringPiece tail(input.begin() + cut + detail::delimSize(delimiter),
|
||||
input.end());
|
||||
if (LIKELY(splitFixed<exact>(delimiter, tail, outTail...))) {
|
||||
outHead = head;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
@@ -299,6 +421,20 @@ void splitTo(const Delim& delimiter,
|
||||
ignoreEmpty);
|
||||
}
|
||||
|
||||
template<bool exact,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool split(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail) {
|
||||
return detail::splitFixed<exact>(
|
||||
detail::prepareDelim(delimiter),
|
||||
input,
|
||||
outHead,
|
||||
outTail...);
|
||||
}
|
||||
|
||||
namespace detail {
|
||||
|
||||
template <class Iterator>
|
||||
|
||||
+1
-2
@@ -242,8 +242,7 @@ fbstring errnoStr(int err) {
|
||||
fbstring result;
|
||||
|
||||
// http://www.kernel.org/doc/man-pages/online/pages/man3/strerror.3.html
|
||||
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600 || \
|
||||
!FOLLY_HAVE_FEATURES_H) && !_GNU_SOURCE
|
||||
#if (_POSIX_C_SOURCE >= 200112L || _XOPEN_SOURCE >= 600) && !_GNU_SOURCE
|
||||
// Using XSI-compatible strerror_r
|
||||
int r = strerror_r(err, buf, sizeof(buf));
|
||||
|
||||
|
||||
+79
@@ -112,6 +112,56 @@ String cUnescape(StringPiece str, bool strict = true) {
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* URI-escape a string. Appends the result to the output string.
|
||||
*
|
||||
* Alphanumeric characters and other characters marked as "unreserved" in RFC
|
||||
* 3986 ( -_.~ ) are left unchanged. In PATH mode, the forward slash (/) is
|
||||
* also left unchanged. In QUERY mode, spaces are replaced by '+'. All other
|
||||
* characters are percent-encoded.
|
||||
*/
|
||||
enum class UriEscapeMode : unsigned char {
|
||||
// The values are meaningful, see generate_escape_tables.py
|
||||
ALL = 0,
|
||||
QUERY = 1,
|
||||
PATH = 2
|
||||
};
|
||||
template <class String>
|
||||
void uriEscape(StringPiece str,
|
||||
String& out,
|
||||
UriEscapeMode mode = UriEscapeMode::ALL);
|
||||
|
||||
/**
|
||||
* Similar to uriEscape above, but returns the escaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String uriEscape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
|
||||
String out;
|
||||
uriEscape(str, out, mode);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* URI-unescape a string. Appends the result to the output string.
|
||||
*
|
||||
* In QUERY mode, '+' are replaced by space. %XX sequences are decoded if
|
||||
* XX is a valid hex sequence, otherwise we throw invalid_argument.
|
||||
*/
|
||||
template <class String>
|
||||
void uriUnescape(StringPiece str,
|
||||
String& out,
|
||||
UriEscapeMode mode = UriEscapeMode::ALL);
|
||||
|
||||
/**
|
||||
* Similar to uriUnescape above, but returns the unescaped string.
|
||||
*/
|
||||
template <class String>
|
||||
String uriUnescape(StringPiece str, UriEscapeMode mode = UriEscapeMode::ALL) {
|
||||
String out;
|
||||
uriUnescape(str, out, mode);
|
||||
return out;
|
||||
}
|
||||
|
||||
/**
|
||||
* stringPrintf is much like printf but deposits its result into a
|
||||
* string. Two signatures are supported: the first simply returns the
|
||||
@@ -344,6 +394,35 @@ void splitTo(const Delim& delimiter,
|
||||
OutputIterator out,
|
||||
bool ignoreEmpty = false);
|
||||
|
||||
/*
|
||||
* Split a string into a fixed number of pieces by delimiter. Returns 'true' if
|
||||
* the fields were all successfully populated.
|
||||
*
|
||||
* Example:
|
||||
*
|
||||
* folly::StringPiece name, key, value;
|
||||
* if (folly::split('\t', line, name, key, value))
|
||||
* ...
|
||||
*
|
||||
* The 'exact' template paremeter specifies how the function behaves when too
|
||||
* many fields are present in the input string. When 'exact' is set to its
|
||||
* default value of 'true', a call to split will fail if the number of fields in
|
||||
* the input string does not exactly match the number of output parameters
|
||||
* passed. If 'exact' is overridden to 'false', all remaining fields will be
|
||||
* stored, unsplit, in the last field, as shown below:
|
||||
*
|
||||
* folly::StringPiece x, y.
|
||||
* if (folly::split<false>(':', "a:b:c", x, y))
|
||||
* assert(x == "a" && y == "b:c");
|
||||
*/
|
||||
template<bool exact = true,
|
||||
class Delim,
|
||||
class... StringPieces>
|
||||
bool split(const Delim& delimiter,
|
||||
StringPiece input,
|
||||
StringPiece& outHead,
|
||||
StringPieces&... outTail);
|
||||
|
||||
/*
|
||||
* Join list of tokens.
|
||||
*
|
||||
|
||||
+185
-52
@@ -16,6 +16,7 @@
|
||||
|
||||
#include "folly/Subprocess.h"
|
||||
|
||||
#include <sys/prctl.h>
|
||||
#include <fcntl.h>
|
||||
#include <poll.h>
|
||||
#include <unistd.h>
|
||||
@@ -32,12 +33,16 @@
|
||||
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Exception.h"
|
||||
#include "folly/FileUtil.h"
|
||||
#include "folly/ScopeGuard.h"
|
||||
#include "folly/String.h"
|
||||
#include "folly/io/Cursor.h"
|
||||
|
||||
extern char** environ;
|
||||
|
||||
constexpr int kExecFailure = 127;
|
||||
constexpr int kChildFailure = 126;
|
||||
|
||||
namespace folly {
|
||||
|
||||
ProcessReturnCode::State ProcessReturnCode::state() const {
|
||||
@@ -90,6 +95,16 @@ CalledProcessError::CalledProcessError(ProcessReturnCode rc)
|
||||
what_(returnCode_.str()) {
|
||||
}
|
||||
|
||||
SubprocessSpawnError::SubprocessSpawnError(const char* executable,
|
||||
int errCode,
|
||||
int errnoValue)
|
||||
: errnoValue_(errnoValue),
|
||||
what_(to<std::string>(errCode == kExecFailure ?
|
||||
"failed to execute " :
|
||||
"error preparing to execute ",
|
||||
executable, ": ", errnoStr(errnoValue))) {
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Copy pointers to the given strings in a format suitable for posix_spawn
|
||||
@@ -166,12 +181,29 @@ Subprocess::Subprocess(
|
||||
Subprocess::~Subprocess() {
|
||||
CHECK_NE(returnCode_.state(), ProcessReturnCode::RUNNING)
|
||||
<< "Subprocess destroyed without reaping child";
|
||||
closeAll();
|
||||
}
|
||||
|
||||
namespace {
|
||||
void closeChecked(int fd) {
|
||||
checkUnixError(::close(fd), "close");
|
||||
}
|
||||
|
||||
struct ChildErrorInfo {
|
||||
int errCode;
|
||||
int errnoValue;
|
||||
};
|
||||
|
||||
void childError(int errFd, int errCode, int errnoValue) FOLLY_NORETURN;
|
||||
void childError(int errFd, int errCode, int errnoValue) {
|
||||
ChildErrorInfo info = {errCode, errnoValue};
|
||||
// Write the error information over the pipe to our parent process.
|
||||
// We can't really do anything else if this write call fails.
|
||||
writeNoInt(errFd, &info, sizeof(info));
|
||||
// exit
|
||||
_exit(errCode);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Subprocess::closeAll() {
|
||||
@@ -204,12 +236,79 @@ void Subprocess::spawn(
|
||||
// Make a copy, we'll mutate options
|
||||
Options options(optionsIn);
|
||||
|
||||
// On error, close all of the pipes_
|
||||
auto pipesGuard = makeGuard([&] {
|
||||
for (auto& p : this->pipes_) {
|
||||
CHECK_ERR(::close(p.parentFd));
|
||||
}
|
||||
});
|
||||
|
||||
// Create a pipe to use to receive error information from the child,
|
||||
// in case it fails before calling exec()
|
||||
int errFds[2];
|
||||
int r = ::pipe(errFds);
|
||||
checkUnixError(r, "pipe");
|
||||
SCOPE_EXIT {
|
||||
CHECK_ERR(::close(errFds[0]));
|
||||
if (errFds[1] >= 0) {
|
||||
CHECK_ERR(::close(errFds[1]));
|
||||
}
|
||||
};
|
||||
// Ask the child to close the read end of the error pipe.
|
||||
options.fdActions_[errFds[0]] = CLOSE;
|
||||
// Set the close-on-exec flag on the write side of the pipe.
|
||||
// This way the pipe will be closed automatically in the child if execve()
|
||||
// succeeds. If the exec fails the child can write error information to the
|
||||
// pipe.
|
||||
r = fcntl(errFds[1], F_SETFD, FD_CLOEXEC);
|
||||
checkUnixError(r, "set FD_CLOEXEC");
|
||||
|
||||
// Perform the actual work of setting up pipes then forking and
|
||||
// executing the child.
|
||||
spawnInternal(std::move(argv), executable, options, env, errFds[1]);
|
||||
|
||||
// After spawnInternal() returns the child is alive. We have to be very
|
||||
// careful about throwing after this point. We are inside the constructor,
|
||||
// so if we throw the Subprocess object will have never existed, and the
|
||||
// destructor will never be called.
|
||||
//
|
||||
// We should only throw if we got an error via the errFd, and we know the
|
||||
// child has exited and can be immediately waited for. In all other cases,
|
||||
// we have no way of cleaning up the child.
|
||||
|
||||
// Close writable side of the errFd pipe in the parent process
|
||||
CHECK_ERR(::close(errFds[1]));
|
||||
errFds[1] = -1;
|
||||
|
||||
// Read from the errFd pipe, to tell if the child ran into any errors before
|
||||
// calling exec()
|
||||
readChildErrorPipe(errFds[0], executable);
|
||||
|
||||
// We have fully succeeded now, so release the guard on pipes_
|
||||
pipesGuard.dismiss();
|
||||
}
|
||||
|
||||
void Subprocess::spawnInternal(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
Options& options,
|
||||
const std::vector<std::string>* env,
|
||||
int errFd) {
|
||||
// Parent work, pre-fork: create pipes
|
||||
std::vector<int> childFds;
|
||||
// Close all of the childFds as we leave this scope
|
||||
SCOPE_EXIT {
|
||||
// These are only pipes, closing them shouldn't fail
|
||||
for (int cfd : childFds) {
|
||||
CHECK_ERR(::close(cfd));
|
||||
}
|
||||
};
|
||||
|
||||
int r;
|
||||
for (auto& p : options.fdActions_) {
|
||||
if (p.second == PIPE_IN || p.second == PIPE_OUT) {
|
||||
int fds[2];
|
||||
int r = ::pipe(fds);
|
||||
r = ::pipe(fds);
|
||||
checkUnixError(r, "pipe");
|
||||
PipeInfo pinfo;
|
||||
pinfo.direction = p.second;
|
||||
@@ -261,72 +360,62 @@ void Subprocess::spawn(
|
||||
//
|
||||
// The parent also unblocks all signals as soon as vfork() returns.
|
||||
sigset_t allBlocked;
|
||||
int r = ::sigfillset(&allBlocked);
|
||||
r = ::sigfillset(&allBlocked);
|
||||
checkUnixError(r, "sigfillset");
|
||||
sigset_t oldSignals;
|
||||
|
||||
r = pthread_sigmask(SIG_SETMASK, &allBlocked, &oldSignals);
|
||||
checkPosixError(r, "pthread_sigmask");
|
||||
SCOPE_EXIT {
|
||||
// Restore signal mask
|
||||
r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
|
||||
CHECK_EQ(r, 0) << "pthread_sigmask: " << errnoStr(r); // shouldn't fail
|
||||
};
|
||||
|
||||
pid_t pid = vfork();
|
||||
if (pid == 0) {
|
||||
// While all signals are blocked, we must reset their
|
||||
// dispositions to default.
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
::signal(sig, SIG_DFL);
|
||||
int errnoValue = prepareChild(options, &oldSignals);
|
||||
if (errnoValue != 0) {
|
||||
childError(errFd, kChildFailure, errnoValue);
|
||||
}
|
||||
// Unblock signals; restore signal mask.
|
||||
int r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
|
||||
if (r != 0) abort();
|
||||
|
||||
runChild(executable, argVec, envVec, options);
|
||||
// This should never return, but there's nothing else we can do here.
|
||||
abort();
|
||||
errnoValue = runChild(executable, argVec, envVec, options);
|
||||
// If we get here, exec() failed.
|
||||
childError(errFd, kExecFailure, errnoValue);
|
||||
}
|
||||
// In parent. We want to restore the signal mask even if vfork fails,
|
||||
// so we'll save errno here, restore the signal mask, and only then
|
||||
// throw.
|
||||
int savedErrno = errno;
|
||||
// In parent. Make sure vfork() succeeded.
|
||||
checkUnixError(pid, errno, "vfork");
|
||||
|
||||
// Restore signal mask; do this even if vfork fails!
|
||||
// We only check for errors from pthread_sigmask after we recorded state
|
||||
// that the child is alive, so we know to reap it.
|
||||
r = pthread_sigmask(SIG_SETMASK, &oldSignals, nullptr);
|
||||
checkUnixError(pid, savedErrno, "vfork");
|
||||
|
||||
// Child is alive
|
||||
// Child is alive. We have to be very careful about throwing after this
|
||||
// point. We are inside the constructor, so if we throw the Subprocess
|
||||
// object will have never existed, and the destructor will never be called.
|
||||
//
|
||||
// We should only throw if we got an error via the errFd, and we know the
|
||||
// child has exited and can be immediately waited for. In all other cases,
|
||||
// we have no way of cleaning up the child.
|
||||
pid_ = pid;
|
||||
returnCode_ = ProcessReturnCode(RV_RUNNING);
|
||||
}
|
||||
|
||||
// Parent work, post-fork: close child's ends of pipes
|
||||
for (int f : childFds) {
|
||||
closeChecked(f);
|
||||
int Subprocess::prepareChild(const Options& options,
|
||||
const sigset_t* sigmask) const {
|
||||
// While all signals are blocked, we must reset their
|
||||
// dispositions to default.
|
||||
for (int sig = 1; sig < NSIG; ++sig) {
|
||||
::signal(sig, SIG_DFL);
|
||||
}
|
||||
// Unblock signals; restore signal mask.
|
||||
int r = pthread_sigmask(SIG_SETMASK, sigmask, nullptr);
|
||||
if (r != 0) {
|
||||
return r; // pthread_sigmask() returns an errno value
|
||||
}
|
||||
|
||||
checkPosixError(r, "pthread_sigmask");
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
// Checked version of close() to use in the child: abort() on error
|
||||
void childClose(int fd) {
|
||||
int r = ::close(fd);
|
||||
if (r == -1) abort();
|
||||
}
|
||||
|
||||
// Checked version of dup2() to use in the child: abort() on error
|
||||
void childDup2(int oldfd, int newfd) {
|
||||
int r = ::dup2(oldfd, newfd);
|
||||
if (r == -1) abort();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
|
||||
void Subprocess::runChild(const char* executable,
|
||||
char** argv, char** env,
|
||||
const Options& options) const {
|
||||
// Close parent's ends of all pipes
|
||||
for (auto& p : pipes_) {
|
||||
childClose(p.parentFd);
|
||||
r = ::close(p.parentFd);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
// Close all fds that we're supposed to close.
|
||||
@@ -336,7 +425,10 @@ void Subprocess::runChild(const char* executable,
|
||||
if (p.second == CLOSE) {
|
||||
::close(p.first);
|
||||
} else {
|
||||
childDup2(p.second, p.first);
|
||||
r = ::dup2(p.second, p.first);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -351,6 +443,20 @@ void Subprocess::runChild(const char* executable,
|
||||
}
|
||||
}
|
||||
|
||||
// Opt to receive signal on parent death, if requested
|
||||
if (options.parentDeathSignal_ != 0) {
|
||||
r = prctl(PR_SET_PDEATHSIG, options.parentDeathSignal_, 0, 0, 0);
|
||||
if (r == -1) {
|
||||
return errno;
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
int Subprocess::runChild(const char* executable,
|
||||
char** argv, char** env,
|
||||
const Options& options) const {
|
||||
// Now, finally, exec.
|
||||
int r;
|
||||
if (options.usePath_) {
|
||||
@@ -358,9 +464,36 @@ void Subprocess::runChild(const char* executable,
|
||||
} else {
|
||||
::execve(executable, argv, env);
|
||||
}
|
||||
return errno;
|
||||
}
|
||||
|
||||
// If we're here, something's wrong.
|
||||
abort();
|
||||
void Subprocess::readChildErrorPipe(int pfd, const char* executable) {
|
||||
ChildErrorInfo info;
|
||||
auto rc = readNoInt(pfd, &info, sizeof(info));
|
||||
if (rc == 0) {
|
||||
// No data means the child executed successfully, and the pipe
|
||||
// was closed due to the close-on-exec flag being set.
|
||||
return;
|
||||
} else if (rc != sizeof(ChildErrorInfo)) {
|
||||
// An error occurred trying to read from the pipe, or we got a partial read.
|
||||
// Neither of these cases should really occur in practice.
|
||||
//
|
||||
// We can't get any error data from the child in this case, and we don't
|
||||
// know if it is successfully running or not. All we can do is to return
|
||||
// normally, as if the child executed successfully. If something bad
|
||||
// happened the caller should at least get a non-normal exit status from
|
||||
// the child.
|
||||
LOG(ERROR) << "unexpected error trying to read from child error pipe " <<
|
||||
"rc=" << rc << ", errno=" << errno;
|
||||
return;
|
||||
}
|
||||
|
||||
// We got error data from the child. The child should exit immediately in
|
||||
// this case, so wait on it to clean up.
|
||||
wait();
|
||||
|
||||
// Throw to signal the error
|
||||
throw SubprocessSpawnError(executable, info.errCode, info.errnoValue);
|
||||
}
|
||||
|
||||
ProcessReturnCode Subprocess::poll() {
|
||||
|
||||
+56
-5
@@ -144,10 +144,15 @@ class ProcessReturnCode {
|
||||
int rawStatus_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Base exception thrown by the Subprocess methods.
|
||||
*/
|
||||
class SubprocessError : public std::exception {};
|
||||
|
||||
/**
|
||||
* Exception thrown by *Checked methods of Subprocess.
|
||||
*/
|
||||
class CalledProcessError : public std::exception {
|
||||
class CalledProcessError : public SubprocessError {
|
||||
public:
|
||||
explicit CalledProcessError(ProcessReturnCode rc);
|
||||
~CalledProcessError() throw() { }
|
||||
@@ -158,6 +163,21 @@ class CalledProcessError : public std::exception {
|
||||
std::string what_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Exception thrown if the subprocess cannot be started.
|
||||
*/
|
||||
class SubprocessSpawnError : public SubprocessError {
|
||||
public:
|
||||
SubprocessSpawnError(const char* executable, int errCode, int errnoValue);
|
||||
~SubprocessSpawnError() throw() {}
|
||||
const char* what() const throw() FOLLY_OVERRIDE { return what_.c_str(); }
|
||||
int errnoValue() const { return errnoValue_; }
|
||||
|
||||
private:
|
||||
int errnoValue_;
|
||||
std::string what_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Subprocess.
|
||||
*/
|
||||
@@ -179,7 +199,11 @@ class Subprocess : private boost::noncopyable {
|
||||
class Options : private boost::orable<Options> {
|
||||
friend class Subprocess;
|
||||
public:
|
||||
Options() : closeOtherFds_(false), usePath_(false) { }
|
||||
Options()
|
||||
: closeOtherFds_(false),
|
||||
usePath_(false),
|
||||
parentDeathSignal_(0) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Change action for file descriptor fd.
|
||||
@@ -233,6 +257,14 @@ class Subprocess : private boost::noncopyable {
|
||||
*/
|
||||
Options& usePath() { usePath_ = true; return *this; }
|
||||
|
||||
/**
|
||||
* Child will receive a signal when the parent exits.
|
||||
*/
|
||||
Options& parentDeathSignal(int sig) {
|
||||
parentDeathSignal_ = sig;
|
||||
return *this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helpful way to combine Options.
|
||||
*/
|
||||
@@ -243,6 +275,7 @@ class Subprocess : private boost::noncopyable {
|
||||
FdMap fdActions_;
|
||||
bool closeOtherFds_;
|
||||
bool usePath_;
|
||||
int parentDeathSignal_;
|
||||
};
|
||||
|
||||
static Options pipeStdin() { return Options().stdin(PIPE); }
|
||||
@@ -444,16 +477,34 @@ class Subprocess : private boost::noncopyable {
|
||||
static const int RV_RUNNING = ProcessReturnCode::RV_RUNNING;
|
||||
static const int RV_NOT_STARTED = ProcessReturnCode::RV_NOT_STARTED;
|
||||
|
||||
// spawn() sets up a pipe to read errors from the child,
|
||||
// then calls spawnInternal() to do the bulk of the work. Once
|
||||
// spawnInternal() returns it reads the error pipe to see if the child
|
||||
// encountered any errors.
|
||||
void spawn(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
const Options& options,
|
||||
const std::vector<std::string>* env);
|
||||
void spawnInternal(
|
||||
std::unique_ptr<const char*[]> argv,
|
||||
const char* executable,
|
||||
Options& options,
|
||||
const std::vector<std::string>* env,
|
||||
int errFd);
|
||||
|
||||
// Action to run in child.
|
||||
// Actions to run in child.
|
||||
// Note that this runs after vfork(), so tread lightly.
|
||||
void runChild(const char* executable, char** argv, char** env,
|
||||
const Options& options) const;
|
||||
// Returns 0 on success, or an errno value on failure.
|
||||
int prepareChild(const Options& options, const sigset_t* sigmask) const;
|
||||
int runChild(const char* executable, char** argv, char** env,
|
||||
const Options& options) const;
|
||||
|
||||
/**
|
||||
* Read from the error pipe, and throw SubprocessSpawnError if the child
|
||||
* failed before calling exec().
|
||||
*/
|
||||
void readChildErrorPipe(int pfd, const char* executable);
|
||||
|
||||
/**
|
||||
* Close all file descriptors.
|
||||
|
||||
+15
-26
@@ -42,22 +42,6 @@
|
||||
#include "folly/Likely.h"
|
||||
#include <type_traits>
|
||||
|
||||
// Use noexcept on gcc 4.6 or higher
|
||||
#undef FOLLY_NOEXCEPT
|
||||
#ifdef __GNUC__
|
||||
# ifdef HAVE_FEATURES_H
|
||||
# include <features.h>
|
||||
# if __GNUC_PREREQ(4,6)
|
||||
# define FOLLY_NOEXCEPT noexcept
|
||||
# define FOLLY_ASSERT(x) x
|
||||
# endif
|
||||
# endif
|
||||
#endif
|
||||
|
||||
#ifndef FOLLY_NOEXCEPT
|
||||
# define FOLLY_NOEXCEPT
|
||||
# define FOLLY_ASSERT(x) /**/
|
||||
#endif
|
||||
|
||||
namespace folly {
|
||||
enum class TLPDestructionMode {
|
||||
@@ -79,11 +63,12 @@ class ThreadLocal {
|
||||
|
||||
T* get() const {
|
||||
T* ptr = tlp_.get();
|
||||
if (UNLIKELY(ptr == nullptr)) {
|
||||
ptr = new T();
|
||||
tlp_.reset(ptr);
|
||||
if (LIKELY(ptr != nullptr)) {
|
||||
return ptr;
|
||||
}
|
||||
return ptr;
|
||||
|
||||
// separated new item creation out to speed up the fast path.
|
||||
return makeTlp();
|
||||
}
|
||||
|
||||
T* operator->() const {
|
||||
@@ -112,6 +97,12 @@ class ThreadLocal {
|
||||
ThreadLocal(const ThreadLocal&) = delete;
|
||||
ThreadLocal& operator=(const ThreadLocal&) = delete;
|
||||
|
||||
T* makeTlp() const {
|
||||
T* ptr = new T();
|
||||
tlp_.reset(ptr);
|
||||
return ptr;
|
||||
}
|
||||
|
||||
mutable ThreadLocalPtr<T,Tag> tlp_;
|
||||
};
|
||||
|
||||
@@ -276,7 +267,7 @@ class ThreadLocalPtr {
|
||||
Accessor(const Accessor&) = delete;
|
||||
Accessor& operator=(const Accessor&) = delete;
|
||||
|
||||
Accessor(Accessor&& other) FOLLY_NOEXCEPT
|
||||
Accessor(Accessor&& other) noexcept
|
||||
: meta_(other.meta_),
|
||||
lock_(other.lock_),
|
||||
id_(other.id_) {
|
||||
@@ -284,7 +275,7 @@ class ThreadLocalPtr {
|
||||
other.lock_ = nullptr;
|
||||
}
|
||||
|
||||
Accessor& operator=(Accessor&& other) FOLLY_NOEXCEPT {
|
||||
Accessor& operator=(Accessor&& other) noexcept {
|
||||
// Each Tag has its own unique meta, and accessors with different Tags
|
||||
// have different types. So either *this is empty, or this and other
|
||||
// have the same tag. But if they have the same tag, they have the same
|
||||
@@ -324,8 +315,8 @@ class ThreadLocalPtr {
|
||||
// accessor allows a client to iterate through all thread local child
|
||||
// elements of this ThreadLocal instance. Holds a global lock for each <Tag>
|
||||
Accessor accessAllThreads() const {
|
||||
FOLLY_ASSERT(static_assert(!std::is_same<Tag, void>::value,
|
||||
"Must use a unique Tag to use the accessAllThreads feature"));
|
||||
static_assert(!std::is_same<Tag, void>::value,
|
||||
"Must use a unique Tag to use the accessAllThreads feature");
|
||||
return Accessor(id_);
|
||||
}
|
||||
|
||||
@@ -343,8 +334,6 @@ class ThreadLocalPtr {
|
||||
int id_; // every instantiation has a unique id
|
||||
};
|
||||
|
||||
#undef FOLLY_NOEXCEPT
|
||||
|
||||
} // namespace folly
|
||||
|
||||
#endif /* FOLLY_THREADLOCAL_H_ */
|
||||
|
||||
+114
-3
@@ -20,6 +20,7 @@
|
||||
#define FOLLY_BASE_TRAITS_H_
|
||||
|
||||
#include <memory>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
|
||||
#include <bits/c++config.h>
|
||||
@@ -277,10 +278,42 @@ struct IsOneOf<T, T1, Ts...> {
|
||||
enum { value = std::is_same<T, T1>::value || IsOneOf<T, Ts...>::value };
|
||||
};
|
||||
|
||||
/*
|
||||
* Complementary type traits to check for a negative/non-positive value.
|
||||
/**
|
||||
* A traits class to check for incomplete types.
|
||||
*
|
||||
* `if(x < 0)` yields an error in clang for unsigned types when -Werror is used
|
||||
* Example:
|
||||
*
|
||||
* struct FullyDeclared {}; // complete type
|
||||
* struct ForwardDeclared; // incomplete type
|
||||
*
|
||||
* is_complete<int>::value // evaluates to true
|
||||
* is_complete<FullyDeclared>::value // evaluates to true
|
||||
* is_complete<ForwardDeclared>::value // evaluates to false
|
||||
*
|
||||
* struct ForwardDeclared {}; // declared, at last
|
||||
*
|
||||
* is_complete<ForwardDeclared>::value // now it evaluates to true
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
template <typename T>
|
||||
class is_complete {
|
||||
template <unsigned long long> struct sfinae {};
|
||||
template <typename U>
|
||||
constexpr static bool test(sfinae<sizeof(U)>*) { return true; }
|
||||
template <typename> constexpr static bool test(...) { return false; }
|
||||
public:
|
||||
constexpr static bool value = test<T>(nullptr);
|
||||
};
|
||||
|
||||
/*
|
||||
* Complementary type traits for integral comparisons.
|
||||
*
|
||||
* For instance, `if(x < 0)` yields an error in clang for unsigned types
|
||||
* when -Werror is used due to -Wtautological-compare
|
||||
*
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
|
||||
namespace detail {
|
||||
@@ -295,6 +328,68 @@ struct is_negative_impl<T, false> {
|
||||
constexpr static bool check(T x) { return false; }
|
||||
};
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs <= std::numeric_limits<LHS>::max()
|
||||
&& rhs >= std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const lhs
|
||||
) {
|
||||
return lhs < rhs;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs > std::numeric_limits<LHS>::max()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than_impl(
|
||||
typename std::enable_if<
|
||||
(rhs < std::numeric_limits<LHS>::min()),
|
||||
LHS
|
||||
>::type const
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LHS, LHS lhs, typename RHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(lhs <= std::numeric_limits<RHS>::max()
|
||||
&& lhs >= std::numeric_limits<RHS>::min()),
|
||||
RHS
|
||||
>::type const rhs
|
||||
) {
|
||||
return lhs < rhs;
|
||||
}
|
||||
|
||||
template <typename LHS, LHS lhs, typename RHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(lhs > std::numeric_limits<RHS>::max()),
|
||||
RHS
|
||||
>::type const
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
|
||||
template <typename LHS, LHS lhs, typename RHS>
|
||||
bool greater_than_impl(
|
||||
typename std::enable_if<
|
||||
(lhs < std::numeric_limits<RHS>::min()),
|
||||
RHS
|
||||
>::type const
|
||||
) {
|
||||
return true;
|
||||
}
|
||||
|
||||
} // namespace detail {
|
||||
|
||||
// same as `x < 0`
|
||||
@@ -307,6 +402,20 @@ constexpr bool is_negative(T x) {
|
||||
template <typename T>
|
||||
constexpr bool is_non_positive(T x) { return !x || folly::is_negative(x); }
|
||||
|
||||
template <typename RHS, RHS rhs, typename LHS>
|
||||
bool less_than(LHS const lhs) {
|
||||
return detail::less_than_impl<
|
||||
RHS, rhs, typename std::remove_reference<LHS>::type
|
||||
>(lhs);
|
||||
}
|
||||
|
||||
template <typename LHS, LHS lhs, typename RHS>
|
||||
bool greater_than(RHS const rhs) {
|
||||
return detail::greater_than_impl<
|
||||
LHS, lhs, typename std::remove_reference<RHS>::type
|
||||
>(rhs);
|
||||
}
|
||||
|
||||
} // namespace folly
|
||||
|
||||
FOLLY_ASSUME_FBVECTOR_COMPATIBLE_3(std::basic_string);
|
||||
@@ -382,6 +491,8 @@ FOLLY_ASSUME_FBVECTOR_COMPATIBLE_1(boost::shared_ptr);
|
||||
* cout << "Does class Bar have a member double test(const string&, long)? "
|
||||
* << boolalpha << has_test_traits<Bar, double(const string&, long)>::value;
|
||||
* }
|
||||
*
|
||||
* @author: Marcelo Juchem <marcelo@fb.com>
|
||||
*/
|
||||
#define FOLLY_CREATE_HAS_MEMBER_FN_TRAITS(classname, func_name) \
|
||||
template <typename, typename> class classname; \
|
||||
|
||||
@@ -17,6 +17,7 @@
|
||||
#ifndef FOLLY_DETAIL_STATS_H_
|
||||
#define FOLLY_DETAIL_STATS_H_
|
||||
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <type_traits>
|
||||
|
||||
@@ -49,6 +50,36 @@ avgHelper(ValueType sum, uint64_t count) {
|
||||
return static_cast<ReturnType>(sumf / countf);
|
||||
}
|
||||
|
||||
/*
|
||||
* Helper function to compute the rate per Interval,
|
||||
* given the specified count recorded over the elapsed time period.
|
||||
*/
|
||||
template <typename ReturnType=double,
|
||||
typename TimeType=std::chrono::seconds,
|
||||
typename Interval=TimeType>
|
||||
ReturnType rateHelper(ReturnType count, TimeType elapsed) {
|
||||
if (elapsed == TimeType(0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use std::chrono::duration_cast to convert between the native
|
||||
// duration and the desired interval. However, convert the rates,
|
||||
// rather than just converting the elapsed duration. Converting the
|
||||
// elapsed time first may collapse it down to 0 if the elapsed interval
|
||||
// is less than the desired interval, which will incorrectly result in
|
||||
// an infinite rate.
|
||||
typedef std::chrono::duration<
|
||||
ReturnType, std::ratio<TimeType::period::den,
|
||||
TimeType::period::num>> NativeRate;
|
||||
typedef std::chrono::duration<
|
||||
ReturnType, std::ratio<Interval::period::den,
|
||||
Interval::period::num>> DesiredRate;
|
||||
|
||||
NativeRate native(count / elapsed.count());
|
||||
DesiredRate desired = std::chrono::duration_cast<DesiredRate>(native);
|
||||
return desired.count();
|
||||
}
|
||||
|
||||
|
||||
template<typename T>
|
||||
struct Bucket {
|
||||
|
||||
+56
-48
@@ -145,6 +145,7 @@ struct StaticMeta {
|
||||
// Leak it on exit, there's only one per process and we don't have to
|
||||
// worry about synchronization with exiting threads.
|
||||
static bool constructed = (inst = new StaticMeta<Tag>());
|
||||
(void)constructed; // suppress unused warning
|
||||
return *inst;
|
||||
}
|
||||
|
||||
@@ -259,59 +260,66 @@ struct StaticMeta {
|
||||
}
|
||||
}
|
||||
|
||||
static ElementWrapper& get(int id) {
|
||||
/**
|
||||
* Reserve enough space in the threadEntry_.elements for the item
|
||||
* @id to fit in.
|
||||
*/
|
||||
static void reserve(int id) {
|
||||
size_t prevSize = threadEntry_.elementsCapacity;
|
||||
if (prevSize <= id) {
|
||||
size_t newSize = static_cast<size_t>((id + 5) * 1.7);
|
||||
auto & meta = instance();
|
||||
ElementWrapper* ptr = NULL;
|
||||
// Rely on jemalloc to zero the memory if possible -- maybe it knows
|
||||
// it's already zeroed and saves us some work.
|
||||
if (!usingJEMalloc() ||
|
||||
prevSize < jemallocMinInPlaceExpandable ||
|
||||
(rallocm(
|
||||
static_cast<void**>(static_cast<void*>(&threadEntry_.elements)),
|
||||
NULL, newSize * sizeof(ElementWrapper), 0,
|
||||
ALLOCM_NO_MOVE | ALLOCM_ZERO) != ALLOCM_SUCCESS)) {
|
||||
// Sigh, must realloc, but we can't call realloc here, as elements is
|
||||
// still linked in meta, so another thread might access invalid memory
|
||||
// after realloc succeeds. We'll copy by hand and update threadEntry_
|
||||
// under the lock.
|
||||
//
|
||||
// Note that we're using calloc instead of malloc in order to zero
|
||||
// the entire region. rallocm (ALLOCM_ZERO) will only zero newly
|
||||
// allocated memory, so if a previous allocation allocated more than
|
||||
// we requested, it's our responsibility to guarantee that the tail
|
||||
// is zeroed. calloc() is simpler than malloc() followed by memset(),
|
||||
// and potentially faster when dealing with a lot of memory, as
|
||||
// it can get already-zeroed pages from the kernel.
|
||||
if ((ptr = static_cast<ElementWrapper*>(
|
||||
calloc(newSize, sizeof(ElementWrapper)))) != NULL) {
|
||||
memcpy(ptr, threadEntry_.elements,
|
||||
sizeof(ElementWrapper) * prevSize);
|
||||
} else {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
size_t newSize = static_cast<size_t>((id + 5) * 1.7);
|
||||
auto& meta = instance();
|
||||
ElementWrapper* ptr = nullptr;
|
||||
// Rely on jemalloc to zero the memory if possible -- maybe it knows
|
||||
// it's already zeroed and saves us some work.
|
||||
if (!usingJEMalloc() ||
|
||||
prevSize < jemallocMinInPlaceExpandable ||
|
||||
(rallocm(
|
||||
static_cast<void**>(static_cast<void*>(&threadEntry_.elements)),
|
||||
NULL, newSize * sizeof(ElementWrapper), 0,
|
||||
ALLOCM_NO_MOVE | ALLOCM_ZERO) != ALLOCM_SUCCESS)) {
|
||||
// Sigh, must realloc, but we can't call realloc here, as elements is
|
||||
// still linked in meta, so another thread might access invalid memory
|
||||
// after realloc succeeds. We'll copy by hand and update threadEntry_
|
||||
// under the lock.
|
||||
//
|
||||
// Note that we're using calloc instead of malloc in order to zero
|
||||
// the entire region. rallocm (ALLOCM_ZERO) will only zero newly
|
||||
// allocated memory, so if a previous allocation allocated more than
|
||||
// we requested, it's our responsibility to guarantee that the tail
|
||||
// is zeroed. calloc() is simpler than malloc() followed by memset(),
|
||||
// and potentially faster when dealing with a lot of memory, as
|
||||
// it can get already-zeroed pages from the kernel.
|
||||
if ((ptr = static_cast<ElementWrapper*>(
|
||||
calloc(newSize, sizeof(ElementWrapper)))) != nullptr) {
|
||||
memcpy(ptr, threadEntry_.elements, sizeof(ElementWrapper) * prevSize);
|
||||
} else {
|
||||
throw std::bad_alloc();
|
||||
}
|
||||
}
|
||||
|
||||
// Success, update the entry
|
||||
{
|
||||
boost::lock_guard<boost::mutex> g(meta.lock_);
|
||||
if (prevSize == 0) {
|
||||
meta.push_back(&threadEntry_);
|
||||
}
|
||||
if (ptr) {
|
||||
using std::swap;
|
||||
swap(ptr, threadEntry_.elements);
|
||||
}
|
||||
threadEntry_.elementsCapacity = newSize;
|
||||
}
|
||||
|
||||
free(ptr);
|
||||
|
||||
// Success, update the entry
|
||||
{
|
||||
boost::lock_guard<boost::mutex> g(meta.lock_);
|
||||
if (prevSize == 0) {
|
||||
pthread_setspecific(meta.pthreadKey_, &meta);
|
||||
meta.push_back(&threadEntry_);
|
||||
}
|
||||
if (ptr) {
|
||||
using std::swap;
|
||||
swap(ptr, threadEntry_.elements);
|
||||
}
|
||||
threadEntry_.elementsCapacity = newSize;
|
||||
}
|
||||
|
||||
free(ptr);
|
||||
|
||||
if (prevSize == 0) {
|
||||
pthread_setspecific(meta.pthreadKey_, &meta);
|
||||
}
|
||||
}
|
||||
|
||||
static ElementWrapper& get(int id) {
|
||||
if (UNLIKELY(threadEntry_.elementsCapacity <= id)) {
|
||||
reserve(id);
|
||||
}
|
||||
return threadEntry_.elements[id];
|
||||
}
|
||||
|
||||
@@ -325,6 +325,7 @@ inline dynamic::const_iterator dynamic::end() const {
|
||||
template <class It>
|
||||
struct dynamic::IterableProxy {
|
||||
typedef It const_iterator;
|
||||
typedef typename It::value_type value_type;
|
||||
|
||||
/* implicit */ IterableProxy(const dynamic::ObjectImpl* o) : o_(o) { }
|
||||
|
||||
|
||||
@@ -34,6 +34,10 @@ DEF_TYPE(dynamic::ObjectImpl, "object", dynamic::OBJECT);
|
||||
|
||||
#undef DEF_TYPE
|
||||
|
||||
const char* dynamic::typeName() const {
|
||||
return typeName(type_);
|
||||
}
|
||||
|
||||
//////////////////////////////////////////////////////////////////////
|
||||
|
||||
}
|
||||
|
||||
@@ -253,6 +253,11 @@ public:
|
||||
*/
|
||||
Type type() const;
|
||||
|
||||
/*
|
||||
* Returns the type of this dynamic as a printable string.
|
||||
*/
|
||||
const char* typeName() const;
|
||||
|
||||
/*
|
||||
* Extract a value while trying to convert to the specified type.
|
||||
* Throws exceptions if we cannot convert from the real type to the
|
||||
|
||||
@@ -33,7 +33,6 @@
|
||||
#endif
|
||||
|
||||
#include <cstdlib>
|
||||
#include <endian.h>
|
||||
#include <algorithm>
|
||||
#include <limits>
|
||||
#include <type_traits>
|
||||
@@ -44,7 +43,7 @@
|
||||
#include "folly/Likely.h"
|
||||
#include "folly/Range.h"
|
||||
|
||||
#if __BYTE_ORDER != __LITTLE_ENDIAN
|
||||
#if __BYTE_ORDER__ != __ORDER_LITTLE_ENDIAN__
|
||||
#error EliasFanoCoding.h requires little endianness
|
||||
#endif
|
||||
|
||||
|
||||
+173
-6
@@ -156,8 +156,10 @@ class GenImpl : public FBounded<Self> {
|
||||
|
||||
/**
|
||||
* apply() - Send all values produced by this generator to given
|
||||
* handler until it returns false. Returns true if the false iff the handler
|
||||
* returned false.
|
||||
* handler until the handler returns false. Returns true until the handler
|
||||
* returns false. GOTCHA: It should return true even if it completes (without
|
||||
* the handler returning false), as 'Chain' uses the return value of apply
|
||||
* to determine if it should process the second object in its chain.
|
||||
*/
|
||||
template<class Handler>
|
||||
bool apply(Handler&& handler) const;
|
||||
@@ -773,6 +775,84 @@ class Take : public Operator<Take> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Sample - For taking a random sample of N elements from a sequence
|
||||
* (without replacement).
|
||||
*/
|
||||
template<class Random>
|
||||
class Sample : public Operator<Sample<Random>> {
|
||||
size_t count_;
|
||||
Random rng_;
|
||||
public:
|
||||
explicit Sample(size_t count, Random rng)
|
||||
: count_(count), rng_(std::move(rng)) {}
|
||||
|
||||
template<class Value,
|
||||
class Source,
|
||||
class Rand,
|
||||
class StorageType = typename std::decay<Value>::type>
|
||||
class Generator :
|
||||
public GenImpl<StorageType&&,
|
||||
Generator<Value, Source, Rand, StorageType>> {
|
||||
static_assert(!Source::infinite, "Cannot sample infinite source!");
|
||||
// It's too easy to bite ourselves if random generator is only 16-bit
|
||||
static_assert(Random::max() >= std::numeric_limits<int32_t>::max() - 1,
|
||||
"Random number generator must support big values");
|
||||
Source source_;
|
||||
size_t count_;
|
||||
mutable Rand rng_;
|
||||
public:
|
||||
explicit Generator(Source source, size_t count, Random rng)
|
||||
: source_(std::move(source)) , count_(count), rng_(std::move(rng)) {}
|
||||
|
||||
template<class Handler>
|
||||
bool apply(Handler&& handler) const {
|
||||
if (count_ == 0) { return false; }
|
||||
std::vector<StorageType> v;
|
||||
v.reserve(count_);
|
||||
// use reservoir sampling to give each source value an equal chance
|
||||
// of appearing in our output.
|
||||
size_t n = 1;
|
||||
source_.foreach([&](Value value) -> void {
|
||||
if (v.size() < count_) {
|
||||
v.push_back(std::forward<Value>(value));
|
||||
} else {
|
||||
// alternatively, we could create a std::uniform_int_distribution
|
||||
// instead of using modulus, but benchmarks show this has
|
||||
// substantial overhead.
|
||||
size_t index = rng_() % n;
|
||||
if (index < v.size()) {
|
||||
v[index] = std::forward<Value>(value);
|
||||
}
|
||||
}
|
||||
++n;
|
||||
});
|
||||
|
||||
// output is unsorted!
|
||||
for (auto& val: v) {
|
||||
if (!handler(std::move(val))) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
};
|
||||
|
||||
template<class Source,
|
||||
class Value,
|
||||
class Gen = Generator<Value, Source, Random>>
|
||||
Gen compose(GenImpl<Value, Source>&& source) const {
|
||||
return Gen(std::move(source.self()), count_, rng_);
|
||||
}
|
||||
|
||||
template<class Source,
|
||||
class Value,
|
||||
class Gen = Generator<Value, Source, Random>>
|
||||
Gen compose(const GenImpl<Value, Source>& source) const {
|
||||
return Gen(source.self(), count_, rng_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Skip - For skipping N items from the beginning of a source generator.
|
||||
*
|
||||
@@ -901,11 +981,11 @@ class Order : public Operator<Order<Selector, Comparer>> {
|
||||
}
|
||||
public:
|
||||
Generator(Source source,
|
||||
const Selector& selector,
|
||||
const Comparer& comparer)
|
||||
Selector selector,
|
||||
Comparer comparer)
|
||||
: source_(std::move(source)),
|
||||
selector_(selector),
|
||||
comparer_(comparer) {}
|
||||
selector_(std::move(selector)),
|
||||
comparer_(std::move(comparer)) {}
|
||||
|
||||
VectorType operator|(const Collect<VectorType>&) const {
|
||||
return asVector();
|
||||
@@ -956,6 +1036,86 @@ class Order : public Operator<Order<Selector, Comparer>> {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Distinct - For filtering duplicates out of a sequence. A selector may be
|
||||
* provided to generate a key to uniquify for each value.
|
||||
*
|
||||
* This type is usually used through the 'distinct' helper function, like:
|
||||
*
|
||||
* auto closest = from(results)
|
||||
* | distinctBy([](Item& i) {
|
||||
* return i.target;
|
||||
* })
|
||||
* | take(10);
|
||||
*/
|
||||
template<class Selector>
|
||||
class Distinct : public Operator<Distinct<Selector>> {
|
||||
Selector selector_;
|
||||
public:
|
||||
Distinct() {}
|
||||
|
||||
explicit Distinct(Selector selector)
|
||||
: selector_(std::move(selector))
|
||||
{}
|
||||
|
||||
template<class Value,
|
||||
class Source>
|
||||
class Generator : public GenImpl<Value, Generator<Value, Source>> {
|
||||
Source source_;
|
||||
Selector selector_;
|
||||
|
||||
typedef typename std::decay<Value>::type StorageType;
|
||||
|
||||
// selector_ cannot be passed an rvalue or it would end up passing the husk
|
||||
// of a value to the downstream operators.
|
||||
typedef const StorageType& ParamType;
|
||||
|
||||
typedef typename std::result_of<Selector(ParamType)>::type KeyType;
|
||||
typedef typename std::decay<KeyType>::type KeyStorageType;
|
||||
|
||||
public:
|
||||
Generator(Source source,
|
||||
Selector selector)
|
||||
: source_(std::move(source)),
|
||||
selector_(std::move(selector)) {}
|
||||
|
||||
template<class Body>
|
||||
void foreach(Body&& body) const {
|
||||
std::unordered_set<KeyStorageType> keysSeen;
|
||||
source_.foreach([&](Value value) {
|
||||
if (keysSeen.insert(selector_(ParamType(value))).second) {
|
||||
body(std::forward<Value>(value));
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
template<class Handler>
|
||||
bool apply(Handler&& handler) const {
|
||||
std::unordered_set<KeyStorageType> keysSeen;
|
||||
return source_.apply([&](Value value) -> bool {
|
||||
if (keysSeen.insert(selector_(ParamType(value))).second) {
|
||||
return handler(std::forward<Value>(value));
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
template<class Source,
|
||||
class Value,
|
||||
class Gen = Generator<Value, Source>>
|
||||
Gen compose(GenImpl<Value, Source>&& source) const {
|
||||
return Gen(std::move(source.self()), selector_);
|
||||
}
|
||||
|
||||
template<class Source,
|
||||
class Value,
|
||||
class Gen = Generator<Value, Source>>
|
||||
Gen compose(const GenImpl<Value, Source>& source) const {
|
||||
return Gen(source.self(), selector_);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Composed - For building up a pipeline of operations to perform, absent any
|
||||
* particular source generator. Useful for building up custom pipelines.
|
||||
@@ -1614,6 +1774,8 @@ static const detail::Min<Identity, Greater> max;
|
||||
|
||||
static const detail::Order<Identity> order;
|
||||
|
||||
static const detail::Distinct<Identity> distinct;
|
||||
|
||||
static const detail::Map<Move> move;
|
||||
|
||||
static const detail::Concat concat;
|
||||
@@ -1624,6 +1786,11 @@ inline detail::Take take(size_t count) {
|
||||
return detail::Take(count);
|
||||
}
|
||||
|
||||
template<class Random = std::default_random_engine>
|
||||
inline detail::Sample<Random> sample(size_t count, Random rng = Random()) {
|
||||
return detail::Sample<Random>(count, std::move(rng));
|
||||
}
|
||||
|
||||
inline detail::Skip skip(size_t count) {
|
||||
return detail::Skip(count);
|
||||
}
|
||||
|
||||
+16
-1
@@ -21,6 +21,9 @@
|
||||
#include <type_traits>
|
||||
#include <utility>
|
||||
#include <algorithm>
|
||||
#include <random>
|
||||
#include <vector>
|
||||
#include <unordered_set>
|
||||
|
||||
#include "folly/Range.h"
|
||||
#include "folly/Optional.h"
|
||||
@@ -208,11 +211,17 @@ class Until;
|
||||
|
||||
class Take;
|
||||
|
||||
template<class Rand>
|
||||
class Sample;
|
||||
|
||||
class Skip;
|
||||
|
||||
template<class Selector, class Comparer = Less>
|
||||
class Order;
|
||||
|
||||
template<class Selector>
|
||||
class Distinct;
|
||||
|
||||
template<class First, class Second>
|
||||
class Composed;
|
||||
|
||||
@@ -294,7 +303,7 @@ From from(std::initializer_list<Value> source) {
|
||||
|
||||
template<class Container,
|
||||
class From = detail::CopiedSource<typename Container::value_type,
|
||||
Container>>
|
||||
Container>>
|
||||
From from(Container&& source) {
|
||||
return From(std::move(source));
|
||||
}
|
||||
@@ -380,6 +389,12 @@ Order orderByDescending(Selector selector = Identity()) {
|
||||
return Order(std::move(selector));
|
||||
}
|
||||
|
||||
template<class Selector,
|
||||
class Distinct = detail::Distinct<Selector>>
|
||||
Distinct distinctBy(Selector selector = Identity()) {
|
||||
return Distinct(std::move(selector));
|
||||
}
|
||||
|
||||
template<int n,
|
||||
class Get = detail::Map<Get<n>>>
|
||||
Get get() {
|
||||
|
||||
+59
-45
@@ -16,62 +16,56 @@
|
||||
|
||||
#include "folly/experimental/TestUtil.h"
|
||||
|
||||
#include <stdlib.h>
|
||||
#include <errno.h>
|
||||
#include <stdexcept>
|
||||
#include <system_error>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include "folly/Format.h"
|
||||
#include "folly/Conv.h"
|
||||
#include "folly/Exception.h"
|
||||
|
||||
namespace folly {
|
||||
namespace test {
|
||||
|
||||
TemporaryFile::TemporaryFile(const char* prefix, Scope scope,
|
||||
namespace {
|
||||
|
||||
fs::path generateUniquePath(fs::path path, StringPiece namePrefix) {
|
||||
if (path.empty()) {
|
||||
path = fs::temp_directory_path();
|
||||
}
|
||||
if (namePrefix.empty()) {
|
||||
path /= fs::unique_path();
|
||||
} else {
|
||||
path /= fs::unique_path(
|
||||
to<std::string>(namePrefix, ".%%%%-%%%%-%%%%-%%%%"));
|
||||
}
|
||||
return path;
|
||||
}
|
||||
|
||||
} // namespce
|
||||
|
||||
TemporaryFile::TemporaryFile(StringPiece namePrefix,
|
||||
fs::path dir,
|
||||
Scope scope,
|
||||
bool closeOnDestruction)
|
||||
: scope_(scope),
|
||||
closeOnDestruction_(closeOnDestruction) {
|
||||
static const char* suffix = ".XXXXXX"; // per mkstemp(3)
|
||||
if (!prefix || prefix[0] == '\0') {
|
||||
prefix = "temp";
|
||||
}
|
||||
const char* dir = nullptr;
|
||||
if (!strchr(prefix, '/')) {
|
||||
// Not a full path, try getenv("TMPDIR") or "/tmp"
|
||||
dir = getenv("TMPDIR");
|
||||
if (!dir) {
|
||||
dir = "/tmp";
|
||||
}
|
||||
// The "!" is a placeholder to ensure that &(path[0]) is null-terminated.
|
||||
// This is the only standard-compliant way to get at a null-terminated
|
||||
// non-const char string inside a std::string: put the null-terminator
|
||||
// yourself.
|
||||
path_ = format("{}/{}{}!", dir, prefix, suffix).str();
|
||||
} else {
|
||||
path_ = format("{}{}!", prefix, suffix).str();
|
||||
}
|
||||
|
||||
// Replace the '!' with a null terminator, we'll get rid of it later
|
||||
path_[path_.size() - 1] = '\0';
|
||||
|
||||
fd_ = mkstemp(&(path_[0]));
|
||||
if (fd_ == -1) {
|
||||
throw std::system_error(errno, std::system_category(),
|
||||
format("mkstemp failed: {}", path_).str().c_str());
|
||||
}
|
||||
|
||||
DCHECK_EQ(path_[path_.size() - 1], '\0');
|
||||
path_.erase(path_.size() - 1);
|
||||
closeOnDestruction_(closeOnDestruction),
|
||||
fd_(-1),
|
||||
path_(generateUniquePath(std::move(dir), namePrefix)) {
|
||||
fd_ = open(path_.c_str(), O_RDWR | O_CREAT | O_EXCL, 0644);
|
||||
checkUnixError(fd_, "open failed");
|
||||
|
||||
if (scope_ == Scope::UNLINK_IMMEDIATELY) {
|
||||
if (unlink(path_.c_str()) == -1) {
|
||||
throw std::system_error(errno, std::system_category(),
|
||||
format("unlink failed: {}", path_).str().c_str());
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path_, ec);
|
||||
if (ec) {
|
||||
LOG(WARNING) << "unlink on construction failed: " << ec;
|
||||
} else {
|
||||
path_.clear();
|
||||
}
|
||||
path_.clear(); // path no longer available or meaningful
|
||||
}
|
||||
}
|
||||
|
||||
const std::string& TemporaryFile::path() const {
|
||||
const fs::path& TemporaryFile::path() const {
|
||||
CHECK(scope_ != Scope::UNLINK_IMMEDIATELY);
|
||||
DCHECK(!path_.empty());
|
||||
return path_;
|
||||
@@ -87,8 +81,28 @@ TemporaryFile::~TemporaryFile() {
|
||||
// If we previously failed to unlink() (UNLINK_IMMEDIATELY), we'll
|
||||
// try again here.
|
||||
if (scope_ != Scope::PERMANENT && !path_.empty()) {
|
||||
if (unlink(path_.c_str()) == -1) {
|
||||
PLOG(ERROR) << "unlink(" << path_ << ") failed";
|
||||
boost::system::error_code ec;
|
||||
fs::remove(path_, ec);
|
||||
if (ec) {
|
||||
LOG(WARNING) << "unlink on destruction failed: " << ec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TemporaryDirectory::TemporaryDirectory(StringPiece namePrefix,
|
||||
fs::path dir,
|
||||
Scope scope)
|
||||
: scope_(scope),
|
||||
path_(generateUniquePath(std::move(dir), namePrefix)) {
|
||||
fs::create_directory(path_);
|
||||
}
|
||||
|
||||
TemporaryDirectory::~TemporaryDirectory() {
|
||||
if (scope_ == Scope::DELETE_ON_DESTRUCTION) {
|
||||
boost::system::error_code ec;
|
||||
fs::remove_all(path_, ec);
|
||||
if (ec) {
|
||||
LOG(WARNING) << "recursive delete on destruction failed: " << ec;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
+39
-7
@@ -18,6 +18,8 @@
|
||||
#define FOLLY_TESTUTIL_H_
|
||||
|
||||
#include <string>
|
||||
#include "folly/Range.h"
|
||||
#include "folly/experimental/io/FsUtil.h"
|
||||
|
||||
namespace folly {
|
||||
namespace test {
|
||||
@@ -27,8 +29,7 @@ namespace test {
|
||||
*
|
||||
* By default, the file is created in a system-specific location (the value
|
||||
* of the TMPDIR environment variable, or /tmp), but you can override that
|
||||
* by making "prefix" be a path (containing a '/'; use a prefix starting with
|
||||
* './' to create a file in the current directory).
|
||||
* with a different (non-empty) directory passed to the constructor.
|
||||
*
|
||||
* By default, the file is closed and deleted when the TemporaryFile object
|
||||
* is destroyed, but both these behaviors can be overridden with arguments
|
||||
@@ -41,19 +42,50 @@ class TemporaryFile {
|
||||
UNLINK_IMMEDIATELY,
|
||||
UNLINK_ON_DESTRUCTION
|
||||
};
|
||||
explicit TemporaryFile(const char* prefix=nullptr,
|
||||
Scope scope=Scope::UNLINK_ON_DESTRUCTION,
|
||||
bool closeOnDestruction=true);
|
||||
explicit TemporaryFile(StringPiece namePrefix = StringPiece(),
|
||||
fs::path dir = fs::path(),
|
||||
Scope scope = Scope::UNLINK_ON_DESTRUCTION,
|
||||
bool closeOnDestruction = true);
|
||||
~TemporaryFile();
|
||||
|
||||
int fd() const { return fd_; }
|
||||
const std::string& path() const;
|
||||
const fs::path& path() const;
|
||||
|
||||
private:
|
||||
Scope scope_;
|
||||
bool closeOnDestruction_;
|
||||
int fd_;
|
||||
std::string path_;
|
||||
fs::path path_;
|
||||
};
|
||||
|
||||
/**
|
||||
* Temporary directory.
|
||||
*
|
||||
* By default, the temporary directory is created in a system-specific
|
||||
* location (the value of the TMPDIR environment variable, or /tmp), but you
|
||||
* can override that with a non-empty directory passed to the constructor.
|
||||
*
|
||||
* By default, the directory is recursively deleted when the TemporaryDirectory
|
||||
* object is destroyed, but that can be overridden with an argument
|
||||
* to the constructor.
|
||||
*/
|
||||
|
||||
class TemporaryDirectory {
|
||||
public:
|
||||
enum class Scope {
|
||||
PERMANENT,
|
||||
DELETE_ON_DESTRUCTION
|
||||
};
|
||||
explicit TemporaryDirectory(StringPiece namePrefix = StringPiece(),
|
||||
fs::path dir = fs::path(),
|
||||
Scope scope = Scope::DELETE_ON_DESTRUCTION);
|
||||
~TemporaryDirectory();
|
||||
|
||||
const fs::path& path() const { return path_; }
|
||||
|
||||
private:
|
||||
Scope scope_;
|
||||
fs::path path_;
|
||||
};
|
||||
|
||||
} // namespace test
|
||||
|
||||
+1
@@ -19,6 +19,7 @@
|
||||
|
||||
#include <dlfcn.h>
|
||||
#include <exception>
|
||||
#include <iostream>
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "folly/experimental/exception_tracer/ExceptionAbi.h"
|
||||
|
||||
+2
-1
@@ -20,7 +20,8 @@
|
||||
#ifndef FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACER_H_
|
||||
#define FOLLY_EXPERIMENTAL_EXCEPTION_TRACER_EXCEPTIONTRACER_H_
|
||||
|
||||
#include <iostream>
|
||||
#include <cstdint>
|
||||
#include <iosfwd>
|
||||
#include <typeinfo>
|
||||
#include <vector>
|
||||
|
||||
|
||||
+6
-5
@@ -20,6 +20,7 @@
|
||||
|
||||
#include <glog/logging.h>
|
||||
|
||||
#include "folly/Portability.h"
|
||||
#include "folly/experimental/exception_tracer/StackTrace.h"
|
||||
#include "folly/experimental/exception_tracer/ExceptionAbi.h"
|
||||
#include "folly/experimental/exception_tracer/ExceptionTracer.h"
|
||||
@@ -28,9 +29,9 @@ namespace __cxxabiv1 {
|
||||
|
||||
extern "C" {
|
||||
void __cxa_throw(void* thrownException, std::type_info* type,
|
||||
void (*destructor)(void)) __attribute__((noreturn));
|
||||
void (*destructor)(void)) FOLLY_NORETURN;
|
||||
void* __cxa_begin_catch(void* excObj);
|
||||
void __cxa_rethrow(void) __attribute__((noreturn));
|
||||
void __cxa_rethrow(void) FOLLY_NORETURN;
|
||||
void __cxa_end_catch(void);
|
||||
}
|
||||
|
||||
@@ -45,10 +46,10 @@ pthread_once_t initialized = PTHREAD_ONCE_INIT;
|
||||
|
||||
extern "C" {
|
||||
typedef void (*CxaThrowType)(void*, std::type_info*, void (*)(void))
|
||||
__attribute__((noreturn));
|
||||
FOLLY_NORETURN;
|
||||
typedef void* (*CxaBeginCatchType)(void*);
|
||||
typedef void (*CxaRethrowType)(void)
|
||||
__attribute__((noreturn));
|
||||
FOLLY_NORETURN;
|
||||
typedef void (*CxaEndCatchType)(void);
|
||||
|
||||
CxaThrowType orig_cxa_throw;
|
||||
@@ -58,7 +59,7 @@ CxaEndCatchType orig_cxa_end_catch;
|
||||
} // extern "C"
|
||||
|
||||
typedef void (*RethrowExceptionType)(std::exception_ptr)
|
||||
__attribute__((noreturn));
|
||||
FOLLY_NORETURN;
|
||||
RethrowExceptionType orig_rethrow_exception;
|
||||
|
||||
void initialize() {
|
||||
|
||||
+1
@@ -15,6 +15,7 @@
|
||||
*/
|
||||
|
||||
|
||||
#include <iostream>
|
||||
#include <stdexcept>
|
||||
|
||||
#include "folly/experimental/exception_tracer/ExceptionTracer.h"
|
||||
|
||||
@@ -20,7 +20,6 @@
|
||||
#include <sys/mman.h>
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
#include <endian.h>
|
||||
#include <fcntl.h>
|
||||
|
||||
#include <string>
|
||||
@@ -130,9 +129,9 @@ void ElfFile::init() {
|
||||
#undef EXPECTED_CLASS
|
||||
|
||||
// Validate ELF data encoding (LSB/MSB)
|
||||
#if __BYTE_ORDER == __LITTLE_ENDIAN
|
||||
#if __BYTE_ORDER__ == __ORDER_LITTLE_ENDIAN__
|
||||
# define EXPECTED_ENCODING ELFDATA2LSB
|
||||
#elif __BYTE_ORDER == __BIG_ENDIAN
|
||||
#elif __BYTE_ORDER__ == __ORDER_BIG_ENDIAN__
|
||||
# define EXPECTED_ENCODING ELFDATA2MSB
|
||||
#else
|
||||
# error Unsupported byte order
|
||||
|
||||
+70
@@ -84,6 +84,76 @@ class CursorBase {
|
||||
return Endian::little(read<T>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a fixed-length string.
|
||||
*
|
||||
* The std::string-based APIs should probably be avoided unless you
|
||||
* ultimately want the data to live in an std::string. You're better off
|
||||
* using the pull() APIs to copy into a raw buffer otherwise.
|
||||
*/
|
||||
std::string readFixedString(size_t len) {
|
||||
std::string str;
|
||||
|
||||
str.reserve(len);
|
||||
for (;;) {
|
||||
// Fast path: it all fits in one buffer.
|
||||
size_t available = length();
|
||||
if (LIKELY(available >= len)) {
|
||||
str.append(reinterpret_cast<const char*>(data()), len);
|
||||
offset_ += len;
|
||||
return str;
|
||||
}
|
||||
|
||||
str.append(reinterpret_cast<const char*>(data()), available);
|
||||
if (UNLIKELY(!tryAdvanceBuffer())) {
|
||||
throw std::out_of_range("string underflow");
|
||||
}
|
||||
len -= available;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a string consisting of bytes until the given terminator character is
|
||||
* seen. Raises an std::length_error if maxLength bytes have been processed
|
||||
* before the terminator is seen.
|
||||
*
|
||||
* See comments in readFixedString() about when it's appropriate to use this
|
||||
* vs. using pull().
|
||||
*/
|
||||
std::string readTerminatedString(
|
||||
char termChar = '\0',
|
||||
size_t maxLength = std::numeric_limits<size_t>::max()) {
|
||||
std::string str;
|
||||
|
||||
for (;;) {
|
||||
const uint8_t* buf = data();
|
||||
size_t buflen = length();
|
||||
|
||||
size_t i = 0;
|
||||
while (i < buflen && buf[i] != termChar) {
|
||||
++i;
|
||||
|
||||
// Do this check after incrementing 'i', as even though we start at the
|
||||
// 0 byte, it still represents a single character
|
||||
if (str.length() + i >= maxLength) {
|
||||
throw std::length_error("string overflow");
|
||||
}
|
||||
}
|
||||
|
||||
str.append(reinterpret_cast<const char*>(buf), i);
|
||||
if (i < buflen) {
|
||||
skip(i + 1);
|
||||
return str;
|
||||
}
|
||||
|
||||
skip(i);
|
||||
|
||||
if (UNLIKELY(!tryAdvanceBuffer())) {
|
||||
throw std::out_of_range("string underflow");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
explicit CursorBase(BufType* buf)
|
||||
: crtBuf_(buf)
|
||||
, offset_(0)
|
||||
|
||||
@@ -161,13 +161,12 @@ void BucketedTimeSeries<VT, TT>::clear() {
|
||||
|
||||
|
||||
template <typename VT, typename TT>
|
||||
TT BucketedTimeSeries<VT, TT>::elapsed() const {
|
||||
TT BucketedTimeSeries<VT, TT>::getEarliestTime() const {
|
||||
if (empty()) {
|
||||
return TimeType(0);
|
||||
}
|
||||
|
||||
if (isAllTime()) {
|
||||
return latestTime_ - firstTime_ + TimeType(1);
|
||||
return firstTime_;
|
||||
}
|
||||
|
||||
size_t currentBucket;
|
||||
@@ -183,7 +182,28 @@ TT BucketedTimeSeries<VT, TT>::elapsed() const {
|
||||
// We're never tracking data before firstTime_
|
||||
earliestTime = std::max(earliestTime, firstTime_);
|
||||
|
||||
return latestTime_ - earliestTime + TimeType(1);
|
||||
return earliestTime;
|
||||
}
|
||||
|
||||
template <typename VT, typename TT>
|
||||
TT BucketedTimeSeries<VT, TT>::elapsed() const {
|
||||
if (empty()) {
|
||||
return TimeType(0);
|
||||
}
|
||||
|
||||
// Add 1 since [latestTime_, earliestTime] is an inclusive interval.
|
||||
return latestTime_ - getEarliestTime() + TimeType(1);
|
||||
}
|
||||
|
||||
template <typename VT, typename TT>
|
||||
TT BucketedTimeSeries<VT, TT>::elapsed(TimeType start, TimeType end) const {
|
||||
if (empty()) {
|
||||
return TimeType(0);
|
||||
}
|
||||
start = std::max(start, getEarliestTime());
|
||||
end = std::min(end, latestTime_ + TimeType(1));
|
||||
end = std::max(start, end);
|
||||
return end - start;
|
||||
}
|
||||
|
||||
template <typename VT, typename TT>
|
||||
|
||||
+36
-24
@@ -104,11 +104,29 @@ class BucketedTimeSeries {
|
||||
|
||||
/*
|
||||
* Get the latest time that has ever been passed to update() or addValue().
|
||||
*
|
||||
* If no data has ever been added to this timeseries, 0 will be returned.
|
||||
*/
|
||||
TimeType getLatestTime() const {
|
||||
return latestTime_;
|
||||
}
|
||||
|
||||
/*
|
||||
* Get the time of the earliest data point stored in this timeseries.
|
||||
*
|
||||
* If no data has ever been added to this timeseries, 0 will be returned.
|
||||
*
|
||||
* If isAllTime() is true, this is simply the time when the first data point
|
||||
* was recorded.
|
||||
*
|
||||
* For non-all-time data, the timestamp reflects the first data point still
|
||||
* remembered. As new data points are added, old data will be expired.
|
||||
* getEarliestTime() returns the timestamp of the oldest bucket still present
|
||||
* in the timeseries. This will never be older than (getLatestTime() -
|
||||
* duration()).
|
||||
*/
|
||||
TimeType getEarliestTime() const;
|
||||
|
||||
/*
|
||||
* Return the number of buckets.
|
||||
*/
|
||||
@@ -170,6 +188,16 @@ class BucketedTimeSeries {
|
||||
*/
|
||||
TimeType elapsed() const;
|
||||
|
||||
/*
|
||||
* Get the amount of time tracked by this timeseries, between the specified
|
||||
* start and end times.
|
||||
*
|
||||
* If the timeseries contains data for the entire time range specified, this
|
||||
* simply returns (end - start). However, if start is earlier than
|
||||
* getEarliestTime(), this returns (end - getEarliestTime()).
|
||||
*/
|
||||
TimeType elapsed(TimeType start, TimeType end) const;
|
||||
|
||||
/*
|
||||
* Return the sum of all the data points currently tracked by this
|
||||
* BucketedTimeSeries.
|
||||
@@ -243,8 +271,8 @@ class BucketedTimeSeries {
|
||||
*
|
||||
* Note that data outside of the timeseries duration will no longer be
|
||||
* available for use in the estimation. Specifying a start time earlier than
|
||||
* (getLatestTime() - elapsed()) will not have much effect, since only data
|
||||
* points after that point in time will be counted.
|
||||
* getEarliestTime() will not have much effect, since only data points after
|
||||
* that point in time will be counted.
|
||||
*
|
||||
* Note that the value returned is an estimate, and may not be precise.
|
||||
*/
|
||||
@@ -277,7 +305,8 @@ class BucketedTimeSeries {
|
||||
template <typename ReturnType=double, typename Interval=TimeType>
|
||||
ReturnType rate(TimeType start, TimeType end) const {
|
||||
ValueType intervalSum = sum(start, end);
|
||||
return rateHelper<ReturnType, Interval>(intervalSum, end - start);
|
||||
TimeType interval = elapsed(start, end);
|
||||
return rateHelper<ReturnType, Interval>(intervalSum, interval);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -290,7 +319,8 @@ class BucketedTimeSeries {
|
||||
template <typename ReturnType=double, typename Interval=TimeType>
|
||||
ReturnType countRate(TimeType start, TimeType end) const {
|
||||
uint64_t intervalCount = count(start, end);
|
||||
return rateHelper<ReturnType, Interval>(intervalCount, end - start);
|
||||
TimeType interval = elapsed(start, end);
|
||||
return rateHelper<ReturnType, Interval>(intervalCount, interval);
|
||||
}
|
||||
|
||||
/*
|
||||
@@ -342,26 +372,8 @@ class BucketedTimeSeries {
|
||||
private:
|
||||
template <typename ReturnType=double, typename Interval=TimeType>
|
||||
ReturnType rateHelper(ReturnType numerator, TimeType elapsed) const {
|
||||
if (elapsed == TimeType(0)) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Use std::chrono::duration_cast to convert between the native
|
||||
// duration and the desired interval. However, convert the rates,
|
||||
// rather than just converting the elapsed duration. Converting the
|
||||
// elapsed time first may collapse it down to 0 if the elapsed interval
|
||||
// is less than the desired interval, which will incorrectly result in
|
||||
// an infinite rate.
|
||||
typedef std::chrono::duration<
|
||||
ReturnType, std::ratio<TimeType::period::den,
|
||||
TimeType::period::num>> NativeRate;
|
||||
typedef std::chrono::duration<
|
||||
ReturnType, std::ratio<Interval::period::den,
|
||||
Interval::period::num>> DesiredRate;
|
||||
|
||||
NativeRate native(numerator / elapsed.count());
|
||||
DesiredRate desired = std::chrono::duration_cast<DesiredRate>(native);
|
||||
return desired.count();
|
||||
return detail::rateHelper<ReturnType, TimeType, Interval>(numerator,
|
||||
elapsed);
|
||||
}
|
||||
|
||||
ValueType rangeAdjust(TimeType bucketStart, TimeType nextBucketStart,
|
||||
|
||||
Referência em uma Nova Issue
Bloquear um usuário