Skip to content

Commit 25c2323

Browse files
committed
Update FakeStream::{trailers,headers} to address thread safety warnings
Returning reference trips thread safety analysis in clang-18, because returning a reference to a lock protected member is in general incorrect. I don't think that this is in practice causing any problems because tests call one of the waitForX functions before calling trailers/headers methods and thus by the time trailes/headers is being called synchronization would have already happened (and in the same thread). Still, we do want to migrate to a newer toolchain, so we should address those warnings to make clang-18 and transitively Envoy CI happy. I'm changing trailers to return a copy of a shared pointer instead of returning a reference to a pointer. While it changes the return type, it should not affect any callers. For headers it's a bit more complicated, as it returns a reference. To avoid changing the interface and updating multiple tests that use the headers method we went with a somewhat different approach that relies on a couple of assumptions: 1. headers() method is only called from a single thread, which may or may not be different from the thread that calls decodedHeaders() 2. headers cannot be updated in place - only replaced completely. With those two assumptions we can maintain two header maps, one for internal use (headers_) and one for the clients (client_headers_). The client_headers_ map is only accessed in the headers() method and therefore does not require a lock. headers_ map can be accessed from multiple threads, so it does require a lock, but it's purely internal and we do not expose it outside of the class. In the headers() method call we move the data from headers_ to client_headers_ if the data changed and return a reference to the new client_headers_. Signed-off-by: Mikhail Krinkin <[email protected]> Signed-off-by: Mikhail Krinkin <[email protected]>
1 parent cd68ddc commit 25c2323

File tree

2 files changed

+30
-3
lines changed

2 files changed

+30
-3
lines changed

Diff for: envoy/http/header_map.h

+2
Original file line numberDiff line numberDiff line change
@@ -744,6 +744,8 @@ class RequestTrailerMap
744744
: public HeaderMap,
745745
public CustomInlineHeaderBase<CustomInlineHeaderRegistry::Type::RequestTrailers> {};
746746
using RequestTrailerMapPtr = std::unique_ptr<RequestTrailerMap>;
747+
using RequestTrailerMapSharedPtr = std::shared_ptr<RequestTrailerMap>;
748+
using RequestTrailerMapConstSharedPtr = std::shared_ptr<const RequestTrailerMap>;
747749
using RequestTrailerMapOptRef = OptRef<RequestTrailerMap>;
748750
using RequestTrailerMapOptConstRef = OptRef<const RequestTrailerMap>;
749751

Diff for: test/integration/fake_upstream.h

+28-3
Original file line numberDiff line numberDiff line change
@@ -104,10 +104,17 @@ class FakeStream : public Http::RequestDecoder,
104104
void readDisable(bool disable);
105105
const Http::RequestHeaderMap& headers() {
106106
absl::MutexLock lock(&lock_);
107-
return *headers_;
107+
if (client_headers_ != headers_) {
108+
client_headers_ = headers_;
109+
}
110+
return *client_headers_;
108111
}
109112
void setAddServedByHeader(bool add_header) { add_served_by_header_ = add_header; }
110-
const Http::RequestTrailerMapPtr& trailers() { return trailers_; }
113+
Http::RequestTrailerMapConstSharedPtr trailers() {
114+
absl::MutexLock lock(&lock_);
115+
Http::RequestTrailerMapConstSharedPtr trailers{trailers_};
116+
return trailers;
117+
}
111118
bool receivedData() { return received_data_; }
112119
Http::Http1StreamEncoderOptionsOptRef http1StreamEncoderOptions() {
113120
return encoder_.http1StreamEncoderOptions();
@@ -256,13 +263,31 @@ class FakeStream : public Http::RequestDecoder,
256263

257264
protected:
258265
absl::Mutex lock_;
266+
// headers get updated in decodeHeaders() and accessed in headers() methods. Those methods can
267+
// be called from different threads, but we can rely on a few assumptions here:
268+
//
269+
// 1. headers() method is only called from a single thread, which may or may not be different
270+
// from the thread that calls decodeHeaders
271+
// 2. headers can only be replaced completely - they are never modified in place.
272+
//
273+
// With those two assumptions, we can use a synchronization pattern with two header maps, that
274+
// allows us to return an unprotected reference to the clients from the headers() method. The
275+
// reason why we want to return an unprotected reference is because historically that's what the
276+
// interface of the FakeStream was, and over time we accumulated quite a few tests that rely on
277+
// that.
278+
//
279+
// With that in mind we made a decision to preserve the interface and avoid migrating multiple
280+
// tests to a new interface when we addressed Clang thread sanitizer warnings.
281+
//
282+
// That's why we have both headers_ and client_headers_ map below.
259283
Http::RequestHeaderMapSharedPtr headers_ ABSL_GUARDED_BY(lock_);
260284
Buffer::OwnedImpl body_ ABSL_GUARDED_BY(lock_);
261285
FakeHttpConnection& parent_;
262286

263287
private:
264288
Http::ResponseEncoder& encoder_;
265-
Http::RequestTrailerMapPtr trailers_ ABSL_GUARDED_BY(lock_);
289+
Http::RequestHeaderMapSharedPtr client_headers_;
290+
Http::RequestTrailerMapSharedPtr trailers_ ABSL_GUARDED_BY(lock_);
266291
bool end_stream_ ABSL_GUARDED_BY(lock_){};
267292
bool saw_reset_ ABSL_GUARDED_BY(lock_){};
268293
Grpc::Decoder grpc_decoder_;

0 commit comments

Comments
 (0)