This is an automated email from the ASF dual-hosted git repository.
wwbmmm pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/brpc.git
The following commit(s) were added to refs/heads/master by this push:
new 4f85335d Fix invalid headers of multiple cookie and set-cookie (#2577)
4f85335d is described below
commit 4f85335dab82eb142383f52a81db3cc7376bb1b4
Author: Bright Chen <[email protected]>
AuthorDate: Mon Jun 3 16:47:44 2024 +0800
Fix invalid headers of multiple cookie and set-cookie (#2577)
* Fix invalid headers of multiple cookie and set-cookie
* Support MultiFlatMap
* Remove unused header
---
src/brpc/details/http_message.cpp | 13 +-
src/brpc/details/http_message.h | 4 +-
src/brpc/http_header.cpp | 113 +++++++++++----
src/brpc/http_header.h | 69 +++++++--
src/butil/containers/case_ignored_flat_map.h | 4 +
src/butil/containers/flat_map.h | 175 +++++++++++++----------
src/butil/containers/flat_map_inl.h | 206 +++++++++++++++++++++------
test/brpc_http_message_unittest.cpp | 116 +++++++++++++++
test/brpc_http_rpc_protocol_unittest.cpp | 1 +
test/flat_map_unittest.cpp | 184 ++++++++++++++++++++++--
10 files changed, 713 insertions(+), 172 deletions(-)
diff --git a/src/brpc/details/http_message.cpp
b/src/brpc/details/http_message.cpp
index 1dbef801..708f9415 100644
--- a/src/brpc/details/http_message.cpp
+++ b/src/brpc/details/http_message.cpp
@@ -99,10 +99,17 @@ int HttpMessage::on_header_value(http_parser *parser,
LOG(ERROR) << "Header name is empty";
return -1;
}
- http_message->_cur_value =
- &http_message->header().GetOrAddHeader(http_message->_cur_header);
+ HttpHeader& header = http_message->header();
+ if (header.CanFoldedInLine(http_message->_cur_header)) {
+ http_message->_cur_value =
+ &header.GetOrAddHeader(http_message->_cur_header);
+ } else {
+ http_message->_cur_value =
+ &header.AddHeader(http_message->_cur_header);
+ }
if (http_message->_cur_value && !http_message->_cur_value->empty()) {
- http_message->_cur_value->push_back(',');
+ http_message->_cur_value->append(
+ header.HeaderValueDelimiter(http_message->_cur_header));
}
}
if (http_message->_cur_value) {
diff --git a/src/brpc/details/http_message.h b/src/brpc/details/http_message.h
index 1b2ae26c..b86ffdd9 100644
--- a/src/brpc/details/http_message.h
+++ b/src/brpc/details/http_message.h
@@ -68,8 +68,8 @@ public:
HttpMethod request_method() const { return _request_method; }
- HttpHeader &header() { return _header; }
- const HttpHeader &header() const { return _header; }
+ HttpHeader& header() { return _header; }
+ const HttpHeader& header() const { return _header; }
size_t parsed_length() const { return _parsed_length; }
// Http parser callback functions
diff --git a/src/brpc/http_header.cpp b/src/brpc/http_header.cpp
index 5dbbd7ab..aef40b17 100644
--- a/src/brpc/http_header.cpp
+++ b/src/brpc/http_header.cpp
@@ -22,33 +22,19 @@
namespace brpc {
+const char* HttpHeader::SET_COOKIE = "set-cookie";
+const char* HttpHeader::COOKIE = "cookie";
+const char* HttpHeader::CONTENT_TYPE = "content-type";
+
HttpHeader::HttpHeader()
: _status_code(HTTP_STATUS_OK)
, _method(HTTP_METHOD_GET)
- , _version(1, 1) {
+ , _version(1, 1)
+ , _first_set_cookie(NULL) {
+ CHECK_EQ(0, _headers.init(29));
// NOTE: don't forget to clear the field in Clear() as well.
}
-void HttpHeader::RemoveHeader(const char* key) {
- if (IsContentType(key)) {
- _content_type.clear();
- } else {
- _headers.erase(key);
- }
-}
-
-void HttpHeader::AppendHeader(const std::string& key,
- const butil::StringPiece& value) {
- std::string& slot = GetOrAddHeader(key);
- if (slot.empty()) {
- slot.assign(value.data(), value.size());
- } else {
- slot.reserve(slot.size() + 1 + value.size());
- slot.push_back(',');
- slot.append(value.data(), value.size());
- }
-}
-
void HttpHeader::Swap(HttpHeader &rhs) {
_headers.swap(rhs._headers);
_uri.Swap(rhs._uri);
@@ -69,6 +55,67 @@ void HttpHeader::Clear() {
_version = std::make_pair(1, 1);
}
+const std::string* HttpHeader::GetHeader(const char* key) const {
+ return GetHeader(std::string(key));
+}
+
+const std::string* HttpHeader::GetHeader(const std::string& key) const {
+ if (IsSetCookie(key)) {
+ return _first_set_cookie;
+ }
+ std::string* val = _headers.seek(key);
+ return val;
+}
+
+std::vector<const std::string*> HttpHeader::GetAllSetCookieHeader() const {
+ return GetMultiLineHeaders(SET_COOKIE);
+}
+
+std::vector<const std::string*>
+HttpHeader::GetMultiLineHeaders(const std::string& key) const {
+ std::vector<const std::string*> headers;
+ for (const auto& iter : _headers) {
+ if (_header_key_equal(iter.first, key)) {
+ headers.push_back(&iter.second);
+ }
+ }
+ return headers;
+}
+
+void HttpHeader::SetHeader(const std::string& key,
+ const std::string& value) {
+ GetOrAddHeader(key) = value;
+}
+
+void HttpHeader::RemoveHeader(const char* key) {
+ if (IsContentType(key)) {
+ _content_type.clear();
+ } else {
+ _headers.erase(key);
+ if (IsSetCookie(key)) {
+ _first_set_cookie = NULL;
+ }
+ }
+}
+
+void HttpHeader::AppendHeader(const std::string& key,
+ const butil::StringPiece& value) {
+ if (!CanFoldedInLine(key)) {
+ // Add a new Set-Cookie header field.
+ std::string& slot = AddHeader(key);
+ slot.assign(value.data(), value.size());
+ } else {
+ std::string& slot = GetOrAddHeader(key);
+ if (slot.empty()) {
+ slot.assign(value.data(), value.size());
+ } else {
+ slot.reserve(slot.size() + 1 + value.size());
+ slot.append(HeaderValueDelimiter(key));
+ slot.append(value.data(), value.size());
+ }
+ }
+}
+
const char* HttpHeader::reason_phrase() const {
return HttpReasonPhrase(_status_code);
}
@@ -82,10 +129,28 @@ std::string& HttpHeader::GetOrAddHeader(const std::string&
key) {
return _content_type;
}
- if (!_headers.initialized()) {
- _headers.init(29);
+ bool is_set_cookie = IsSetCookie(key);
+ // Only returns the first Set-Cookie header field for compatibility.
+ if (is_set_cookie && NULL != _first_set_cookie) {
+ return *_first_set_cookie;
+ }
+
+ std::string* val = _headers.seek(key);
+ if (NULL == val) {
+ val = _headers.insert({ key, "" });
+ if (is_set_cookie) {
+ _first_set_cookie = val;
+ }
+ }
+ return *val;
+}
+
+std::string& HttpHeader::AddHeader(const std::string& key) {
+ std::string* val = _headers.insert({ key, "" });
+ if (IsSetCookie(key) && NULL == _first_set_cookie) {
+ _first_set_cookie = val;
}
- return _headers[key];
+ return *val;
}
const HttpHeader& DefaultHttpHeader() {
diff --git a/src/brpc/http_header.h b/src/brpc/http_header.h
index c5af6448..39b2fa9b 100644
--- a/src/brpc/http_header.h
+++ b/src/brpc/http_header.h
@@ -19,6 +19,7 @@
#ifndef BRPC_HTTP_HEADER_H
#define BRPC_HTTP_HEADER_H
+#include <vector>
#include "butil/strings/string_piece.h" // StringPiece
#include "butil/containers/case_ignored_flat_map.h"
#include "brpc/uri.h" // URI
@@ -39,7 +40,7 @@ class H2StreamContext;
// Non-body part of a HTTP message.
class HttpHeader {
public:
- typedef butil::CaseIgnoredFlatMap<std::string> HeaderMap;
+ typedef butil::CaseIgnoredMultiFlatMap<std::string> HeaderMap;
typedef HeaderMap::const_iterator HeaderIterator;
typedef HeaderMap::key_equal HeaderKeyEqual;
@@ -80,24 +81,39 @@ public:
// Return pointer to the value, NULL on not found.
// NOTE: If the key is "Content-Type", `GetHeader("Content-Type")'
// (case-insensitive) is equal to `content_type()'.
- const std::string* GetHeader(const char* key) const
- { return _headers.seek(key); }
- const std::string* GetHeader(const std::string& key) const
- { return _headers.seek(key); }
+ const std::string* GetHeader(const char* key) const;
+ const std::string* GetHeader(const std::string& key) const;
+
+ std::vector<const std::string*> GetAllSetCookieHeader() const;
// Set value of a header.
// NOTE: If the key is "Content-Type", `SetHeader("Content-Type", ...)'
// (case-insensitive) is equal to `set_content_type(...)'.
- void SetHeader(const std::string& key, const std::string& value)
- { GetOrAddHeader(key) = value; }
+ void SetHeader(const std::string& key, const std::string& value);
- // Remove a header.
+ // Remove all headers of key.
void RemoveHeader(const char* key);
void RemoveHeader(const std::string& key) { RemoveHeader(key.c_str()); }
// Append value to a header. If the header already exists, separate
- // old value and new value with comma(,) according to:
- // https://www.w3.org/Protocols/rfc2616/rfc2616-sec4.html#sec4.2
+ // old value and new value with comma(,), two-byte delimiter of "; "
+ // or into a new header field, according to:
+ //
+ // https://datatracker.ietf.org/doc/html/rfc2616#section-4.2
+ // Multiple message-header fields with the same field-name MAY be
+ // present in a message if and only if the entire field-value for that
+ // header field is defined as a comma-separated list [i.e., #(values)].
+ //
+ // https://datatracker.ietf.org/doc/html/rfc9114#section-4.2.1
+ // If a decompressed field section contains multiple cookie field lines,
+ // these MUST be concatenated into a single byte string using the two-byte
+ // delimiter of "; " (ASCII 0x3b, 0x20) before being passed into a context
+ // other than HTTP/2 or HTTP/3, such as an HTTP/1.1 connection, or a
generic
+ // HTTP server application.
+ //
+ // https://datatracker.ietf.org/doc/html/rfc6265#section-3
+ // Origin servers SHOULD NOT fold multiple Set-Cookie header
+ // fields into a single header field.
void AppendHeader(const std::string& key, const butil::StringPiece& value);
// Get header iterators which are invalidated after calling AppendHeader()
@@ -145,12 +161,40 @@ friend class HttpMessageSerializer;
friend class policy::H2StreamContext;
friend void policy::ProcessHttpRequest(InputMessageBase *msg);
+ static const char* SET_COOKIE;
+ static const char* COOKIE;
+ static const char* CONTENT_TYPE;
+
+ std::vector<const std::string*> GetMultiLineHeaders(const std::string&
key) const;
+
std::string& GetOrAddHeader(const std::string& key);
- static bool IsContentType(const std::string& key) {
- return HeaderKeyEqual()(key, "content-type");
+ std::string& AddHeader(const std::string& key);
+
+ bool IsSetCookie(const std::string& key) const {
+ return _header_key_equal(key, SET_COOKIE);
+ }
+
+ bool IsCookie(const std::string& key) const {
+ return _header_key_equal(key, COOKIE);
+ }
+
+ bool IsContentType(const std::string& key) const {
+ return _header_key_equal(key, CONTENT_TYPE);
+ }
+
+ // Return true if the header can be folded in line,
+ // otherwise, returns false, i.e., Set-Cookie header.
+ // See comments of `AppendHeader'.
+ bool CanFoldedInLine(const std::string& key) {
+ return !IsSetCookie(key);
+ }
+
+ const char* HeaderValueDelimiter(const std::string& key) {
+ return IsCookie(key) ? "; " : ",";
}
+ HeaderKeyEqual _header_key_equal;
HeaderMap _headers;
URI _uri;
int _status_code;
@@ -158,6 +202,7 @@ friend void policy::ProcessHttpRequest(InputMessageBase
*msg);
std::string _content_type;
std::string _unresolved_path;
std::pair<int, int> _version;
+ std::string* _first_set_cookie;
};
const HttpHeader& DefaultHttpHeader();
diff --git a/src/butil/containers/case_ignored_flat_map.h
b/src/butil/containers/case_ignored_flat_map.h
index 30cfdda6..909e7317 100644
--- a/src/butil/containers/case_ignored_flat_map.h
+++ b/src/butil/containers/case_ignored_flat_map.h
@@ -68,6 +68,10 @@ class CaseIgnoredFlatMap : public butil::FlatMap<
class CaseIgnoredFlatSet : public butil::FlatSet<
std::string, CaseIgnoredHasher, CaseIgnoredEqual> {};
+template <typename T>
+class CaseIgnoredMultiFlatMap : public butil::FlatMap<
+ std::string, T, CaseIgnoredHasher, CaseIgnoredEqual, false, PtAllocator,
true> {};
+
} // namespace butil
#endif // BUTIL_CASE_IGNORED_FLAT_MAP_H
diff --git a/src/butil/containers/flat_map.h b/src/butil/containers/flat_map.h
index 50f44540..f0d1d1ca 100644
--- a/src/butil/containers/flat_map.h
+++ b/src/butil/containers/flat_map.h
@@ -23,72 +23,72 @@
// fastest hashmap for small-sized key/value ever.
//
// Performance comparisons between several maps:
-// [ value = 8 bytes ]
-// Sequentially inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 11/108/55/58
-// Sequentially erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/123/55/37
-// Sequentially inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/92/55/54
-// Sequentially erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/67/51/35
-// Sequentially inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/100/66/54
-// Sequentially erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/72/55/35
-// [ value = 32 bytes ]
-// Sequentially inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 14/108/56/56
-// Sequentially erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/77/53/38
-// Sequentially inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 14/94/54/53
-// Sequentially erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/66/49/36
-// Sequentially inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 13/106/62/54
-// Sequentially erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 4/69/53/36
-// [ value = 128 bytes ]
-// Sequentially inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 31/182/96/96
-// Sequentially erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 8/117/51/44
-// Sequentially inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 29/191/100/97
-// Sequentially erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/100/49/44
-// Sequentially inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 30/184/113/114
-// Sequentially erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/99/52/43
-// [ value = 8 bytes ]
-// Randomly inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 11/171/108/60
-// Randomly erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 8/158/126/37
-// Randomly inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 10/159/117/54
-// Randomly erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/153/135/36
-// Randomly inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 12/223/180/55
-// Randomly erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/237/210/48
-// [ value = 32 bytes ]
-// Randomly inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 16/179/108/57
-// Randomly erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/157/120/38
-// Randomly inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 15/168/127/54
-// Randomly erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/164/135/39
-// Randomly inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 19/241/201/56
-// Randomly erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 5/235/218/54
-// [ value = 128 bytes ]
-// Randomly inserting 100 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 35/242/154/97
-// Randomly erasing 100 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 7/185/119/56
-// Randomly inserting 1000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 35/262/182/99
-// Randomly erasing 1000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/215/157/66
-// Randomly inserting 10000 into
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 44/330/278/114
-// Randomly erasing 10000 from
FlatMap/std::map/butil::PooledMap/butil::hash_map takes 6/307/242/90
-// [ value = 8 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
6/51/52/13
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/98/82/14
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/175/170/14
-// [ value = 32 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/52/52/14
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/84/82/13
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/164/156/14
-// [ value = 128 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/54/53/14
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/88/90/13
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/178/185/14
-// [ value = 8 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
5/51/49/14
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/86/94/14
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/177/171/14
-// [ value = 32 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/51/53/14
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/98/82/13
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/163/156/14
-// [ value = 128 bytes ]
-// Seeking 100 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
3/55/53/14
-// Seeking 1000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/88/89/13
-// Seeking 10000 from FlatMap/std::map/butil::PooledMap/butil::hash_map takes
4/177/185/14
+// [ value = 8 bytes ]
+// Sequentially inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/300/290/2010/210/230
+// Sequentially erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/1700/150/160/170/250
+// Sequentially inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 16/15/360/342/206/195/219
+// Sequentially erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/18/178/159/149/159/149
+// Sequentially inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/15/415/410/200/192/235
+// Sequentially erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 14/17/201/181/151/181/154
+// [ value = 32 bytes ]
+// Sequentially inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/280/280/250/200/230
+// Sequentially erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/2070/150/160/160/160
+// Sequentially inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 17/17/330/329/207/185/212
+// Sequentially erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/17/172/163/146/157/148
+// Sequentially inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 17/17/405/406/197/185/215
+// Sequentially erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/16/206/188/158/168/159
+// [ value = 128 bytes ]
+// Sequentially inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/30/290/290/420/220/250
+// Sequentially erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/180/150/160/160/160
+// Sequentially inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 22/25/352/349/213/193/222
+// Sequentially erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/17/170/165/160/171/157
+// Sequentially inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 21/24/416/422/210/206/242
+// Sequentially erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 16/16/213/190/163/171/159
+// [ value = 8 bytes ]
+// Randomly inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/290/260/250/220/220
+// Randomly erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/240/220/170/170/180
+// Randomly inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 16/15/315/309/206/191/215
+// Randomly erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/17/258/240/155/193/156
+// Randomly inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/16/378/363/208/191/210
+// Randomly erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 14/16/311/290/162/187/169
+// [ value = 32 bytes ]
+// Randomly inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/280/270/240/230/220
+// Randomly erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 10/20/250/220/170/180/160
+// Randomly inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 18/18/310/304/209/192/209
+// Randomly erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/16/255/247/155/175/152
+// Randomly inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 17/17/381/367/209/197/214
+// Randomly erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/17/310/296/163/188/168
+// [ value = 128 bytes ]
+// Randomly inserting 100 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 30/40/300/290/280/230/230
+// Randomly erasing 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/20/230/220/160/180/170
+// Randomly inserting 1000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 29/33/327/329/219/197/220
+// Randomly erasing 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 14/16/257/247/159/182/156
+// Randomly inserting 10000 into
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 35/39/398/400/220/213/246
+// Randomly erasing 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 35/36/330/319/221/224/200
+// [ value = 8 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 10/20/140/130/60/70/50
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/172/161/77/54/46
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/16/216/211/73/56/51
+// [ value = 32 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 10/20/130/130/70/60/50
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/174/163/73/54/49
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/218/217/75/58/52
+// [ value = 128 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/140/130/80/50/60
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/173/171/73/55/49
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 26/22/238/252/107/89/91
+// [ value = 8 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/140/130/70/50/60
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/14/180/160/68/57/47
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/221/210/72/57/51
+// [ value = 32 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 20/10/140/130/70/60/50
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/167/160/69/53/50
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 15/14/224/219/75/59/52
+// [ value = 128 bytes ]
+// Seeking 100 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 10/10/140/130/80/50/60
+// Seeking 1000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 13/13/170/167/70/54/52
+// Seeking 10000 from
FlatMap/MultiFlatMap/std::map/butil::PooledMap/std::unordered_map/std::unordered_multimap/butil::hash_map
takes 26/22/238/240/85/70/67
#ifndef BUTIL_FLAT_MAP_H
#define BUTIL_FLAT_MAP_H
@@ -128,7 +128,9 @@ template <typename _K, typename _T,
// stored-key is always on LHS, passed-key is always on RHS.
typename _Equal = DefaultEqualTo<_K>,
bool _Sparse = false,
- typename _Alloc = PtAllocator>
+ typename _Alloc = PtAllocator,
+ // If `_Multi' is true, allow containing multiple copies of each key
value.
+ bool _Multi = false>
class FlatMap {
public:
typedef _K key_type;
@@ -177,10 +179,17 @@ public:
// Returns address of the inserted value, NULL on error.
mapped_type* insert(const std::pair<key_type, mapped_type>& kv);
- // Remove |key| and the associated value
+ // For `_Multi=false'. (Default)
+ // Remove |key| and all associated value
// Returns: 1 on erased, 0 otherwise.
- template <typename K2>
- size_t erase(const K2& key, mapped_type* old_value = NULL);
+ template <typename K2, bool Multi = _Multi>
+ typename std::enable_if<!Multi, size_t >::type
+ erase(const K2& key, mapped_type* old_value = NULL);
+ // For `_Multi=true'.
+ // Returns: num of value on erased, 0 otherwise.
+ template <typename K2, bool Multi = _Multi>
+ typename std::enable_if<Multi, size_t >::type
+ erase(const K2& key, std::vector<mapped_type>* old_values = NULL);
// Remove all items. Allocated spaces are NOT returned by system.
void clear();
@@ -188,15 +197,19 @@ public:
// Remove all items and return all allocated spaces to system.
void clear_and_reset_pool();
- // Search for the value associated with |key|
- // Returns: address of the value
+ // Search for the value associated with |key|.
+ // If `_Multi=false', Search for any of multiple values associated with
|key|.
+ // Returns: address of the value.
template <typename K2> mapped_type* seek(const K2& key) const;
+ template <typename K2> std::vector<mapped_type*> seek_all(const K2& key)
const;
+ // For `_Multi=false'. (Default)
// Get the value associated with |key|. If |key| does not exist,
// insert with a default-constructed value. If size()*100/bucket_count()
// is more than load_factor, a resize will be done.
// Returns reference of the value
- mapped_type& operator[](const key_type& key);
+ template<bool Multi = _Multi>
+ typename std::enable_if<!Multi, mapped_type&>::type operator[](const
key_type& key);
// Resize this map. This is optional because resizing will be triggered by
// insert() or operator[] if there're too many items.
@@ -276,6 +289,15 @@ public:
private:
template <typename _Map, typename _Element> friend class FlatMapIterator;
template <typename _Map, typename _Element> friend class SparseFlatMapIterator;
+
+ // For `_Multi=true'.
+ // Insert a new default-constructed associated with |key| always.
+ // If size()*100/bucket_count() is more than load_factor,
+ // a resize will be done.
+ // Returns reference of the value
+ template<bool Multi = _Multi>
+ typename std::enable_if<Multi, mapped_type&>::type operator[](const
key_type& key);
+
// True if buckets need to be resized before holding `size' elements.
inline bool is_too_crowded(size_t size) const
{ return size * 100 >= _nbucket * _load_factor; }
@@ -290,6 +312,13 @@ template <typename _Map, typename _Element> friend class
SparseFlatMapIterator;
SingleThreadedPool<sizeof(Bucket), 1024, 16, allocator_type> _pool;
};
+template <typename _K, typename _T,
+ typename _Hash = DefaultHasher<_K>,
+ typename _Equal = DefaultEqualTo<_K>,
+ bool _Sparse = false,
+ typename _Alloc = PtAllocator>
+using MultiFlatMap = FlatMap<_K, _T, _Hash, _Equal, _Sparse, _Alloc, true>;
+
template <typename _K,
typename _Hash = DefaultHasher<_K>,
typename _Equal = DefaultEqualTo<_K>,
diff --git a/src/butil/containers/flat_map_inl.h
b/src/butil/containers/flat_map_inl.h
index 65830368..5218fab9 100644
--- a/src/butil/containers/flat_map_inl.h
+++ b/src/butil/containers/flat_map_inl.h
@@ -225,8 +225,8 @@ friend class SparseFlatMapIterator<Map, ConstValue>;
};
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-FlatMap<_K, _T, _H, _E, _S, _A>::FlatMap(const hasher& hashfn, const
key_equal& eql, const allocator_type& alloc)
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::FlatMap(const hasher& hashfn, const
key_equal& eql, const allocator_type& alloc)
: _size(0)
, _nbucket(0)
, _buckets(NULL)
@@ -237,8 +237,8 @@ FlatMap<_K, _T, _H, _E, _S, _A>::FlatMap(const hasher&
hashfn, const key_equal&
, _pool(alloc)
{}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-FlatMap<_K, _T, _H, _E, _S, _A>::~FlatMap() {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::~FlatMap() {
clear();
get_allocator().Free(_buckets);
_buckets = NULL;
@@ -248,8 +248,8 @@ FlatMap<_K, _T, _H, _E, _S, _A>::~FlatMap() {
_load_factor = 0;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-FlatMap<_K, _T, _H, _E, _S, _A>::FlatMap(const FlatMap& rhs)
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::FlatMap(const FlatMap& rhs)
: _size(0)
, _nbucket(0)
, _buckets(NULL)
@@ -260,9 +260,9 @@ FlatMap<_K, _T, _H, _E, _S, _A>::FlatMap(const FlatMap& rhs)
operator=(rhs);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-FlatMap<_K, _T, _H, _E, _S, _A>&
-FlatMap<_K, _T, _H, _E, _S, _A>::operator=(const FlatMap<_K, _T, _H, _E, _S,
_A>& rhs) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+FlatMap<_K, _T, _H, _E, _S, _A, _M>&
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator=(const FlatMap<_K, _T, _H, _E,
_S, _A, _M>& rhs) {
if (this == &rhs) {
return *this;
}
@@ -334,8 +334,8 @@ FlatMap<_K, _T, _H, _E, _S, _A>::operator=(const
FlatMap<_K, _T, _H, _E, _S, _A>
return *this;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-int FlatMap<_K, _T, _H, _E, _S, _A>::init(size_t nbucket, u_int load_factor) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+int FlatMap<_K, _T, _H, _E, _S, _A, _M>::init(size_t nbucket, u_int
load_factor) {
if (initialized()) {
LOG(ERROR) << "Already initialized";
return -1;
@@ -373,8 +373,8 @@ int FlatMap<_K, _T, _H, _E, _S, _A>::init(size_t nbucket,
u_int load_factor) {
return 0;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-void FlatMap<_K, _T, _H, _E, _S, _A>::swap(FlatMap<_K, _T, _H, _E, _S, _A> &
rhs) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+void FlatMap<_K, _T, _H, _E, _S, _A, _M>::swap(FlatMap<_K, _T, _H, _E, _S, _A,
_M> & rhs) {
std::swap(rhs._size, _size);
std::swap(rhs._nbucket, _nbucket);
std::swap(rhs._buckets, _buckets);
@@ -385,22 +385,25 @@ void FlatMap<_K, _T, _H, _E, _S, _A>::swap(FlatMap<_K,
_T, _H, _E, _S, _A> & rhs
rhs._pool.swap(_pool);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-_T* FlatMap<_K, _T, _H, _E, _S, _A>::insert(const key_type& key,
- const mapped_type& value) {
- mapped_type *p = &operator[](key);
+template <typename _K, typename _T, typename _H,
+ typename _E, bool _S, typename _A, bool _M>
+_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::insert(const key_type& key,
+ const mapped_type& value) {
+ mapped_type *p = &operator[]<_M>(key);
*p = value;
return p;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-_T* FlatMap<_K, _T, _H, _E, _S, _A>::insert(const std::pair<key_type,
mapped_type>& kv) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::insert(
+ const std::pair<key_type, mapped_type>& kv) {
return insert(kv.first, kv.second);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-template <typename K2>
-size_t FlatMap<_K, _T, _H, _E, _S, _A>::erase(const K2& key, _T* old_value) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+template <typename K2, bool Multi>
+typename std::enable_if<!Multi, size_t >::type
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::erase(const K2& key, _T* old_value) {
if (!initialized()) {
return 0;
}
@@ -464,8 +467,71 @@ size_t FlatMap<_K, _T, _H, _E, _S, _A>::erase(const K2&
key, _T* old_value) {
return 0;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-void FlatMap<_K, _T, _H, _E, _S, _A>::clear() {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+template <typename K2, bool Multi>
+typename std::enable_if<Multi, size_t >::type
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::erase(const K2& key,
+ std::vector<mapped_type>*
old_values) {
+ if (!initialized()) {
+ return 0;
+ }
+ // TODO: Do we need auto collapsing here?
+ const size_t index = flatmap_mod(_hashfn(key), _nbucket);
+ Bucket& first_node = _buckets[index];
+ if (!first_node.is_valid()) {
+ return 0;
+ }
+
+ Bucket* new_head = NULL;
+ Bucket* new_tail = NULL;
+ Bucket* p = &first_node;
+ size_t total = _size;
+ while (NULL != p) {
+ if (_eql(p->element().first_ref(), key)) {
+ if (NULL != old_values) {
+ old_values->push_back(p->element().second_movable_ref());
+ }
+ Bucket* temp = p;
+ p = p->next;
+ temp->element().~Element();
+ if (temp != &first_node) {
+ _pool.back(temp);
+ }
+ --_size;
+ } else {
+ if (NULL == new_head) {
+ new_head = p;
+ new_tail = p;
+ } else {
+ new_tail->next = p;
+ new_tail = new_tail->next;
+ }
+ p = p->next;
+ }
+ }
+ if (NULL != new_tail) {
+ new_tail->next = NULL;
+ }
+ if (NULL == new_head) {
+ // Erase all element.
+ first_node.set_invalid();
+ if (_S) {
+ bit_array_unset(_thumbnail, index);
+ }
+ } else if (new_head != &first_node) {
+ // The First node has been erased, need to move new head node as first
node.
+ first_node.next = new_head->next;
+ const_cast<_K&>(first_node.element().first_ref()) =
+ new_head->element().first_ref();
+ first_node.element().second_ref() =
new_head->element().second_movable_ref();
+ new_head->element().~Element();
+ _pool.back(new_head);
+ }
+ return total - _size;
+}
+
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+void FlatMap<_K, _T, _H, _E, _S, _A, _M>::clear() {
if (0 == _size) {
return;
}
@@ -491,15 +557,15 @@ void FlatMap<_K, _T, _H, _E, _S, _A>::clear() {
}
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-void FlatMap<_K, _T, _H, _E, _S, _A>::clear_and_reset_pool() {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+void FlatMap<_K, _T, _H, _E, _S, _A, _M>::clear_and_reset_pool() {
clear();
_pool.reset();
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
template <typename K2>
-_T* FlatMap<_K, _T, _H, _E, _S, _A>::seek(const K2& key) const {
+_T* FlatMap<_K, _T, _H, _E, _S, _A, _M>::seek(const K2& key) const {
if (!initialized()) {
return NULL;
}
@@ -520,8 +586,34 @@ _T* FlatMap<_K, _T, _H, _E, _S, _A>::seek(const K2& key)
const {
return NULL;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-_T& FlatMap<_K, _T, _H, _E, _S, _A>::operator[](const key_type& key) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+template <typename K2>
+std::vector<_T*> FlatMap<_K, _T, _H, _E, _S, _A, _M>::seek_all(const K2& key)
const {
+ std::vector<_T*> v;
+ if (!initialized()) {
+ return v;
+ }
+ Bucket& first_node = _buckets[flatmap_mod(_hashfn(key), _nbucket)];
+ if (!first_node.is_valid()) {
+ return v;
+ }
+ if (_eql(first_node.element().first_ref(), key)) {
+ v.push_back(&first_node.element().second_ref());
+ }
+ Bucket *p = first_node.next;
+ while (p) {
+ if (_eql(p->element().first_ref(), key)) {
+ v.push_back(&p->element().second_ref());
+ }
+ p = p->next;
+ }
+ return v;
+}
+
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+template<bool Multi>
+typename std::enable_if<!Multi, _T&>::type
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) {
const size_t index = flatmap_mod(_hashfn(key), _nbucket);
Bucket& first_node = _buckets[index];
if (!first_node.is_valid()) {
@@ -553,8 +645,28 @@ _T& FlatMap<_K, _T, _H, _E, _S, _A>::operator[](const
key_type& key) {
}
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-void FlatMap<_K, _T, _H, _E, _S, _A>::save_iterator(
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+template<bool Multi>
+typename std::enable_if<Multi, _T&>::type
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::operator[](const key_type& key) {
+ const size_t index = flatmap_mod(_hashfn(key), _nbucket);
+ Bucket& first_node = _buckets[index];
+ if (!first_node.is_valid()) {
+ ++_size;
+ if (_S) {
+ bit_array_set(_thumbnail, index);
+ }
+ new (&first_node) Bucket(key);
+ return first_node.element().second_ref();
+ }
+ Bucket* newp = new (_pool.get()) Bucket(key);
+ newp->next = first_node.next;
+ first_node.next = newp;
+ return newp->element().second_ref();
+}
+
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+void FlatMap<_K, _T, _H, _E, _S, _A, _M>::save_iterator(
const const_iterator& it, PositionHint* hint) const {
hint->nbucket = _nbucket;
hint->offset = it._entry - _buckets;
@@ -567,9 +679,9 @@ void FlatMap<_K, _T, _H, _E, _S, _A>::save_iterator(
}
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-typename FlatMap<_K, _T, _H, _E, _S, _A>::const_iterator
-FlatMap<_K, _T, _H, _E, _S, _A>::restore_iterator(const PositionHint& hint)
const {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator
+FlatMap<_K, _T, _H, _E, _S, _A, _M>::restore_iterator(const PositionHint&
hint) const {
if (hint.nbucket != _nbucket) // resized
return begin(); // restart
@@ -601,8 +713,8 @@ FlatMap<_K, _T, _H, _E, _S, _A>::restore_iterator(const
PositionHint& hint) cons
return const_iterator(this, hint.offset);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-bool FlatMap<_K, _T, _H, _E, _S, _A>::resize(size_t nbucket2) {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+bool FlatMap<_K, _T, _H, _E, _S, _A, _M>::resize(size_t nbucket2) {
nbucket2 = flatmap_round(nbucket2);
if (_nbucket == nbucket2) {
return false;
@@ -623,8 +735,8 @@ bool FlatMap<_K, _T, _H, _E, _S, _A>::resize(size_t
nbucket2) {
return true;
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-BucketInfo FlatMap<_K, _T, _H, _E, _S, _A>::bucket_info() const {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+BucketInfo FlatMap<_K, _T, _H, _E, _S, _A, _M>::bucket_info() const {
size_t max_n = 0;
size_t nentry = 0;
for (size_t i = 0; i < _nbucket; ++i) {
@@ -644,23 +756,23 @@ inline std::ostream& operator<<(std::ostream& os, const
BucketInfo& info) {
<< " avgb=" << info.average_length << '}';
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-typename FlatMap<_K, _T, _H, _E, _S, _A>::iterator FlatMap<_K, _T, _H, _E, _S,
_A>::begin() {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::iterator FlatMap<_K, _T, _H, _E,
_S, _A, _M>::begin() {
return iterator(this, 0);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-typename FlatMap<_K, _T, _H, _E, _S, _A>::iterator FlatMap<_K, _T, _H, _E, _S,
_A>::end() {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::iterator FlatMap<_K, _T, _H, _E,
_S, _A, _M>::end() {
return iterator(this, _nbucket);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-typename FlatMap<_K, _T, _H, _E, _S, _A>::const_iterator FlatMap<_K, _T, _H,
_E, _S, _A>::begin() const {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator FlatMap<_K, _T,
_H, _E, _S, _A, _M>::begin() const {
return const_iterator(this, 0);
}
-template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A>
-typename FlatMap<_K, _T, _H, _E, _S, _A>::const_iterator FlatMap<_K, _T, _H,
_E, _S, _A>::end() const {
+template <typename _K, typename _T, typename _H, typename _E, bool _S,
typename _A, bool _M>
+typename FlatMap<_K, _T, _H, _E, _S, _A, _M>::const_iterator FlatMap<_K, _T,
_H, _E, _S, _A, _M>::end() const {
return const_iterator(this, _nbucket);
}
diff --git a/test/brpc_http_message_unittest.cpp
b/test/brpc_http_message_unittest.cpp
index 568162cd..651397aa 100644
--- a/test/brpc_http_message_unittest.cpp
+++ b/test/brpc_http_message_unittest.cpp
@@ -262,6 +262,71 @@ TEST(HttpMessageTest, parse_http_head_response) {
ASSERT_EQ("chunked", *transfer_encoding);
}
+TEST(HttpMessageTest, parse_http_cookie) {
+ const char* http_request =
+ "GET /CloudApiControl HTTP/1.1\r\n"
+ "Host: api.map.baidu.com\r\n"
+ "Accept: application/json\r\n"
+ "cookie: a=1\r\n"
+ "Cookie: b=2\r\n"
+ "\r\n";
+ butil::IOBuf buf;
+ buf.append(http_request);
+ brpc::HttpMessage http_message;
+ ASSERT_EQ((ssize_t)buf.size(), http_message.ParseFromIOBuf(buf));
+ ASSERT_TRUE(http_message.Completed());
+
+ const std::string* cookie
+ = http_message.header().GetHeader("cookie");
+ ASSERT_NE(nullptr, cookie);
+ ASSERT_EQ("a=1; b=2", *cookie);
+}
+
+TEST(HttpMessageTest, parse_http_set_cookie) {
+ char response[1024] = "HTTP/1.1 200 OK\r\n"
+ "Content-Type: text/plain\r\n"
+ "Content-Length: 1024\r\n"
+ "set-cookie: a=1\r\n"
+ "Set-Cookie: b=2\r\n"
+ "\r\n";
+ butil::IOBuf request;
+ request.append(response);
+ brpc::HttpMessage http_message(false, brpc::HTTP_METHOD_HEAD);
+ ASSERT_TRUE(http_message.ParseFromIOBuf(request) >= 0);
+ ASSERT_TRUE(http_message.Completed()) << http_message.stage();
+
+ const std::string* set_cookie =
http_message.header().GetHeader("set-cookie");
+ ASSERT_NE(nullptr, set_cookie);
+ ASSERT_EQ("a=1", *set_cookie);
+ std::vector<const std::string*> all_set_cookie
+ = http_message.header().GetAllSetCookieHeader();
+ for (const std::string* sc : all_set_cookie) {
+ ASSERT_NE(nullptr, sc);
+ if (set_cookie == sc) {
+ ASSERT_EQ("a=1", *sc);
+ } else {
+ ASSERT_EQ("b=2", *sc);
+ }
+ if (http_message.header().IsSetCookie(*sc)) {
+ }
+ }
+ int set_cookie_value1_count = 0;
+ int set_cookie_value2_count = 0;
+ for (auto iter = http_message.header().HeaderBegin();
+ iter != http_message.header().HeaderEnd(); ++iter) {
+ if (!http_message.header().IsSetCookie(iter->first)) {
+ continue;
+ }
+ if (iter->second == "b=2") {
+ ++set_cookie_value2_count;
+ } else if (iter->second == "a=1") {
+ ++set_cookie_value1_count;
+ }
+ }
+ ASSERT_EQ(1, set_cookie_value1_count);
+ ASSERT_EQ(1, set_cookie_value2_count);
+}
+
TEST(HttpMessageTest, cl_and_te) {
// https://datatracker.ietf.org/doc/html/rfc2616#section-14.41
// If multiple encodings have been applied to an entity, the transfer-
@@ -436,6 +501,57 @@ TEST(HttpMessageTest, http_header) {
header.RemoveHeader("key1");
ASSERT_FALSE(header.GetHeader("key1"));
+ ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::COOKIE));
+ header.AppendHeader(brpc::HttpHeader::COOKIE, "value1=1");
+ value = header.GetHeader(brpc::HttpHeader::COOKIE);
+ ASSERT_TRUE(value && *value == "value1=1");
+ header.AppendHeader(brpc::HttpHeader::COOKIE, "value2=2");
+ value = header.GetHeader(brpc::HttpHeader::COOKIE);
+ ASSERT_TRUE(value && *value == "value1=1; value2=2");
+ header.SetHeader(brpc::HttpHeader::COOKIE, "value3");
+ value = header.GetHeader(brpc::HttpHeader::COOKIE);
+ ASSERT_TRUE(value && *value == "value3");
+ header.RemoveHeader(brpc::HttpHeader::COOKIE);
+ ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::COOKIE));
+
+ std::string set_cookie_value1 = "a=1";
+ std::string set_cookie_value2 = "b=2";
+ std::string set_cookie_value3 = "c=3";
+ ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE));
+ header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value1);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value1);
+ header.AppendHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value2);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value1);
+ header.SetHeader(brpc::HttpHeader::SET_COOKIE, set_cookie_value3);
+ value = header.GetHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_TRUE(value && *value == set_cookie_value3);
+ std::vector<const std::string*> all_set_cookie
+ = header.GetAllSetCookieHeader();
+ ASSERT_EQ(2u, all_set_cookie.size());
+ for (const std::string* sc : all_set_cookie) {
+ ASSERT_TRUE(sc);
+ ASSERT_TRUE(*sc == set_cookie_value2 || *sc == set_cookie_value3);
+ }
+ int set_cookie_value2_count = 0;
+ int set_cookie_value3_count = 0;
+ for (auto iter = header.HeaderBegin(); iter != header.HeaderEnd(); ++iter)
{
+ if (!header.IsSetCookie(brpc::HttpHeader::SET_COOKIE)) {
+ continue;
+ }
+ if (iter->second == set_cookie_value2) {
+ ++set_cookie_value2_count;
+ } else if (iter->second == set_cookie_value3) {
+ ++set_cookie_value3_count;
+ }
+ }
+ ASSERT_EQ(1, set_cookie_value2_count);
+ ASSERT_EQ(1, set_cookie_value3_count);
+ header.RemoveHeader(brpc::HttpHeader::SET_COOKIE);
+ ASSERT_FALSE(header.GetHeader(brpc::HttpHeader::SET_COOKIE));
+ ASSERT_EQ(header._first_set_cookie, nullptr);
+
ASSERT_EQ(brpc::HTTP_METHOD_GET, header.method());
header.set_method(brpc::HTTP_METHOD_POST);
ASSERT_EQ(brpc::HTTP_METHOD_POST, header.method());
diff --git a/test/brpc_http_rpc_protocol_unittest.cpp
b/test/brpc_http_rpc_protocol_unittest.cpp
index b4e0547c..f4bdec95 100644
--- a/test/brpc_http_rpc_protocol_unittest.cpp
+++ b/test/brpc_http_rpc_protocol_unittest.cpp
@@ -59,6 +59,7 @@ namespace brpc {
DECLARE_bool(rpc_dump);
DECLARE_string(rpc_dump_dir);
DECLARE_int32(rpc_dump_max_requests_in_one_file);
+DECLARE_bool(allow_chunked_length);
extern bvar::CollectorSpeedLimit g_rpc_dump_sl;
}
diff --git a/test/flat_map_unittest.cpp b/test/flat_map_unittest.cpp
index d689bdf1..5d9850d7 100644
--- a/test/flat_map_unittest.cpp
+++ b/test/flat_map_unittest.cpp
@@ -19,6 +19,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <map>
+#include <unordered_map>
#include <vector>
#include "butil/time.h"
#include "butil/macros.h"
@@ -1107,23 +1108,28 @@ TEST_F(FlatMapTest, random_insert_erase) {
<< "n_cp:" << n_cp;
}
-template <typename T> void perf_insert_erase(bool random, const T& value)
-{
+template <typename T>
+void perf_insert_erase(bool random, const T& value) {
size_t nkeys[] = { 100, 1000, 10000 };
const size_t NPASS = ARRAY_SIZE(nkeys);
std::vector<uint64_t> keys;
butil::FlatMap<uint64_t, T> id_map;
+ butil::MultiFlatMap<uint64_t, T> multi_id_map;
std::map<uint64_t, T> std_map;
butil::PooledMap<uint64_t, T> pooled_map;
+ std::unordered_map<uint64_t, T> std_unordered_map;
+ std::unordered_multimap<uint64_t, T> std_unordered_multimap;
butil::hash_map<uint64_t, T> hash_map;
- butil::Timer id_tm, std_tm, pooled_tm, hash_tm;
+ butil::Timer id_tm, multi_id_tm, std_tm, pooled_tm,
+ std_unordered_tm, std_unordered_multi_tm, hash_tm;
size_t max_nkeys = 0;
for (size_t i = 0; i < NPASS; ++i) {
max_nkeys = std::max(max_nkeys, nkeys[i]);
}
id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
+ multi_id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
// Make DS hot
for (size_t i = 0; i < max_nkeys; ++i) {
@@ -1135,6 +1141,8 @@ template <typename T> void perf_insert_erase(bool random,
const T& value)
id_map.clear();
std_map.clear();
pooled_map.clear();
+ std_unordered_map.clear();
+ std_unordered_multimap.clear();
hash_map.clear();
LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]";
@@ -1156,6 +1164,13 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
}
id_tm.stop();
+ multi_id_map.clear();
+ multi_id_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ multi_id_map[keys[i]] = value;
+ }
+ multi_id_tm.stop();
+
std_map.clear();
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
@@ -1170,6 +1185,20 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
}
pooled_tm.stop();
+ std_unordered_map.clear();
+ std_unordered_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_map[keys[i]] = value;
+ }
+ std_unordered_tm.stop();
+
+ std_unordered_multimap.clear();
+ std_unordered_multi_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_multimap.insert({ keys[i], value });
+ }
+ std_unordered_multi_tm.stop();
+
hash_map.clear();
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
@@ -1179,10 +1208,14 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
LOG(INFO) << (random ? "Randomly" : "Sequentially")
<< " inserting " << keys.size()
- << " into FlatMap/std::map/butil::PooledMap/butil::hash_map
takes "
+ << " into FlatMap/MultiFlatMap/std::map/butil::PooledMap/"
+
"std::unordered_map/std::unordered_multimap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
+ << "/" << multi_id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
+ << "/" << std_unordered_tm.n_elapsed() / keys.size()
+ << "/" << std_unordered_multi_tm.n_elapsed() / keys.size()
<< "/" << hash_tm.n_elapsed() / keys.size();
if (random) {
@@ -1195,6 +1228,12 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
}
id_tm.stop();
+ multi_id_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ multi_id_map.erase(keys[i]);
+ }
+ multi_id_tm.stop();
+
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
std_map.erase(keys[i]);
@@ -1207,6 +1246,18 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
}
pooled_tm.stop();
+ std_unordered_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_map.erase(keys[i]);
+ }
+ std_unordered_tm.stop();
+
+ std_unordered_multi_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_multimap.erase(keys[i]);
+ }
+ std_unordered_multi_tm.stop();
+
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
hash_map.erase(keys[i]);
@@ -1215,27 +1266,36 @@ template <typename T> void perf_insert_erase(bool
random, const T& value)
LOG(INFO) << (random ? "Randomly" : "Sequentially")
<< " erasing " << keys.size()
- << " from FlatMap/std::map/butil::PooledMap/butil::hash_map
takes "
+ << " from FlatMap/MultiFlatMap/std::map/butil::PooledMap/"
+
"std::unordered_map/std::unordered_multimap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
+ << "/" << multi_id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
+ << "/" << std_unordered_tm.n_elapsed() / keys.size()
+ << "/" << std_unordered_multi_tm.n_elapsed() / keys.size()
<< "/" << hash_tm.n_elapsed() / keys.size();
}
}
-template <typename T> void perf_seek(const T& value) {
+template <typename T>
+void perf_seek(const T& value) {
size_t nkeys[] = { 100, 1000, 10000 };
const size_t NPASS = ARRAY_SIZE(nkeys);
std::vector<uint64_t> keys;
std::vector<uint64_t> rkeys;
butil::FlatMap<uint64_t, T> id_map;
+ butil::MultiFlatMap<uint64_t, T> multi_id_map;
std::map<uint64_t, T> std_map;
butil::PooledMap<uint64_t, T> pooled_map;
+ std::unordered_map<uint64_t, T> std_unordered_map;
+ std::unordered_multimap<uint64_t, T> std_unordered_multimap;
butil::hash_map<uint64_t, T> hash_map;
-
- butil::Timer id_tm, std_tm, pooled_tm, hash_tm;
+ butil::Timer id_tm, multi_id_tm, std_tm, pooled_tm,
+ std_unordered_tm, std_unordered_multi_tm, hash_tm;
id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
+ multi_id_map.init((size_t)(nkeys[NPASS-1] * 1.5));
LOG(INFO) << "[ value = " << sizeof(T) << " bytes ]";
for (size_t pass = 0; pass < NPASS; ++pass) {
int start = rand();
@@ -1249,6 +1309,11 @@ template <typename T> void perf_seek(const T& value) {
id_map[keys[i]] = value;
}
+ multi_id_map.clear();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ multi_id_map[keys[i]] = value;
+ }
+
std_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
std_map[keys[i]] = value;
@@ -1259,6 +1324,16 @@ template <typename T> void perf_seek(const T& value) {
pooled_map[keys[i]] = value;
}
+ std_unordered_map.clear();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_map[keys[i]] = value;
+ }
+
+ std_unordered_multimap.clear();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ std_unordered_multimap.insert({ keys[i], value });
+ }
+
hash_map.clear();
for (size_t i = 0; i < keys.size(); ++i) {
hash_map[keys[i]] = value;
@@ -1273,6 +1348,12 @@ template <typename T> void perf_seek(const T& value) {
}
id_tm.stop();
+ multi_id_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ sum += *(long*)multi_id_map.seek(keys[i]);
+ }
+ multi_id_tm.stop();
+
std_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += (long&)std_map.find(keys[i])->second;
@@ -1285,6 +1366,18 @@ template <typename T> void perf_seek(const T& value) {
}
pooled_tm.stop();
+ std_unordered_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ sum += (long&)std_unordered_map[keys[i]];
+ }
+ std_unordered_tm.stop();
+
+ std_unordered_multi_tm.start();
+ for (size_t i = 0; i < keys.size(); ++i) {
+ sum += (long&)std_unordered_multimap.find(keys[i])->second;
+ }
+ std_unordered_multi_tm.stop();
+
hash_tm.start();
for (size_t i = 0; i < keys.size(); ++i) {
sum += (long&)hash_map.find(keys[i])->second;
@@ -1292,12 +1385,15 @@ template <typename T> void perf_seek(const T& value) {
hash_tm.stop();
LOG(INFO) << "Seeking " << keys.size()
- << " from FlatMap/std::map/butil::PooledMap/butil::hash_map
takes "
+ << " from FlatMap/MultiFlatMap/std::map/butil::PooledMap/"
+
"std::unordered_map/std::unordered_multimap/butil::hash_map takes "
<< id_tm.n_elapsed() / keys.size()
+ << "/" << multi_id_tm.n_elapsed() / keys.size()
<< "/" << std_tm.n_elapsed() / keys.size()
<< "/" << pooled_tm.n_elapsed() / keys.size()
- << "/" << hash_tm.n_elapsed() / keys.size()
- << " sum=" << sum;
+ << "/" << std_unordered_tm.n_elapsed() / keys.size()
+ << "/" << std_unordered_multi_tm.n_elapsed() / keys.size()
+ << "/" << hash_tm.n_elapsed() / keys.size();
}
}
@@ -1349,4 +1445,70 @@ TEST_F(FlatMapTest, copy) {
ASSERT_TRUE(m4.empty());
}
+TEST_F(FlatMapTest, multi) {
+ // Construct non-POD values w/o copy-construction.
+ g_foo_ctor = 0;
+ g_foo_copy_ctor = 0;
+ g_foo_assign = 0;
+ butil::MultiFlatMap<int, Foo> map;
+ int bucket_count = 32;
+ ASSERT_EQ(0, map.init(bucket_count));
+ ASSERT_EQ(0, g_foo_ctor);
+ ASSERT_EQ(0, g_foo_copy_ctor);
+ ASSERT_EQ(0, g_foo_assign);
+ Foo& f1 = map[1];
+ ASSERT_EQ(1, g_foo_ctor);
+ ASSERT_EQ(0, g_foo_copy_ctor);
+ ASSERT_EQ(0, g_foo_assign);
+ Foo& f2 = map[1];
+ ASSERT_EQ(2, g_foo_ctor);
+ ASSERT_EQ(0, g_foo_copy_ctor);
+ ASSERT_EQ(0, g_foo_assign);
+ Foo f3;
+ Foo& f4 = *map.insert(1, f3);
+ ASSERT_EQ(4, g_foo_ctor);
+ ASSERT_EQ(0, g_foo_copy_ctor);
+ ASSERT_EQ(1, g_foo_assign);
+ ASSERT_EQ(&f1, map.seek(1));
+ std::vector<Foo*> f_vec = map.seek_all(1);
+ ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f1));
+ ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f2));
+ ASSERT_NE(f_vec.end(), std::find(f_vec.begin(), f_vec.end(), &f4));
+
+ ASSERT_EQ(bucket_count, map.bucket_count());
+ int same_bucket_key = 1 + bucket_count;
+ butil::DefaultHasher<int> hasher;
+ ASSERT_EQ(butil::flatmap_mod(hasher(1), bucket_count),
+ butil::flatmap_mod(hasher(same_bucket_key), bucket_count));
+ ASSERT_EQ(0, map.erase(same_bucket_key));
+ Foo& f5 = map[same_bucket_key];
+ ASSERT_EQ(&f5, map.seek(same_bucket_key));
+ ASSERT_EQ(1UL, map.seek_all(same_bucket_key).size());
+ ASSERT_EQ(5, g_foo_ctor);
+ ASSERT_EQ(0, g_foo_copy_ctor);
+ ASSERT_EQ(1, g_foo_assign);
+ ASSERT_EQ(&f5, map.seek(same_bucket_key));
+ ASSERT_EQ(3u, map.erase(1));
+ ASSERT_EQ(nullptr, map.seek(1));
+ ASSERT_TRUE(map.seek_all(1).empty());
+ // Value node of same_bucket_key is the last one in the bucket,
+ // so it has been moved to the first node.
+ ASSERT_EQ(&f1, map.seek(same_bucket_key));
+ ASSERT_EQ(1UL, map.erase(same_bucket_key));
+ ASSERT_EQ(nullptr, map.seek(same_bucket_key));
+ ASSERT_TRUE(map.seek_all(same_bucket_key).empty());
+
+ // Zeroize POD values.
+ butil::MultiFlatMap<int, Bar> map2;
+ ASSERT_EQ(0, map2.init(32));
+ Bar& g = map2[1];
+ ASSERT_EQ(0, g.x);
+ g.x = 123;
+ ASSERT_EQ(1u, map2.erase(1));
+ ASSERT_EQ(123, g.x); // g is still accessible in this case.
+ Bar& g2 = map2[1];
+ ASSERT_EQ(&g, &g2);
+ ASSERT_EQ(0, g2.x);
+}
+
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]