Skip to content

Commit b7949bd

Browse files
Ankit Kumarmeta-codesync[bot]
authored andcommitted
Carry the per-connection and per-request internal fields
Summary: Gives `ThriftConnContext` and `ThriftRequestContext` the `getInternalFields<T>()` slot the classic contexts carry, over fast_thrift's own `TypeErasedValue<128>` and pluggable functions rather than a dependency on `Cpp2ConnContext`. Reviewed By: robertroeser Differential Revision: D116839752 fbshipit-source-id: e7f5da7ae639c2b7a3e20c8b275adc18e323820c
1 parent acacd34 commit b7949bd

4 files changed

Lines changed: 171 additions & 0 deletions

File tree

third-party/thrift/src/thrift/lib/cpp2/fast_thrift/thrift/server/common/context/ThriftConnContext.h

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,22 @@
2727
#include <folly/SocketAddress.h>
2828
#include <folly/io/async/AsyncTransportCertificate.h>
2929

30+
#include <folly/CppAttributes.h>
31+
3032
#include <thrift/lib/cpp2/fast_thrift/rocket/common/TypeErasedPtr.h>
33+
#include <thrift/lib/cpp2/util/TypeErasedValue.h>
3134

3235
namespace apache::thrift::fast_thrift::thrift {
3336

37+
namespace detail {
38+
39+
// The classic server's internal-fields slot, which fast_thrift points at
40+
// rather than owning: the object lives inline in whichever context owns it, so
41+
// two contexts can only reach one object by one of them holding a pointer.
42+
using InternalFieldsT = apache::thrift::util::TypeErasedValue<128>;
43+
44+
} // namespace detail
45+
3446
// Per-connection context. Lives for the duration of one accepted connection.
3547
//
3648
// Refcount is non-atomic (boost::thread_unsafe_counter), which avoids an
@@ -75,11 +87,41 @@ class ThriftConnContext : public boost::intrusive_ref_counter<
7587
}
7688

7789
// Opaque per-connection slot. The deleter runs at connection close.
90+
//
91+
// There is one, so it has one owner. Anything shared between handlers, or
92+
// published from a handler to the service, belongs in the connection's
93+
// ExtensionStateStore instead — that is keyed by type and so cannot be
94+
// claimed twice.
7895
void setUserData(rocket::TypeErasedPtr userData) noexcept {
7996
userData_ = std::move(userData);
8097
}
8198
void* getUserData() const noexcept { return userData_.get(); }
8299

100+
// Points this context at the per-connection fields the security layer keeps
101+
// for this connection. Non-owning: the fields live on the Cpp2ConnContext
102+
// built for the connection, which outlives this call, and nothing is copied
103+
// — both contexts hand out the one object.
104+
void setInternalFields(detail::InternalFieldsT* fields) noexcept {
105+
internalFields_ = fields;
106+
}
107+
108+
// The security layer's per-connection fields, or null while nothing holds
109+
// any — a server with no security layer never fills the slot, unlike the
110+
// classic server, where the fields always exist.
111+
//
112+
// Unchecked: only valid for the `T` the owner constructed.
113+
template <class T>
114+
T* FOLLY_NULLABLE getInternalFields() noexcept {
115+
return hasInternalFields() ? &internalFields_->value_unchecked<T>()
116+
: nullptr;
117+
}
118+
119+
template <class T>
120+
const T* FOLLY_NULLABLE getInternalFields() const noexcept {
121+
return hasInternalFields() ? &internalFields_->value_unchecked<T>()
122+
: nullptr;
123+
}
124+
83125
void setPeerAddress(folly::SocketAddress addr) noexcept {
84126
peerAddress_ = std::move(addr);
85127
}
@@ -92,10 +134,15 @@ class ThriftConnContext : public boost::intrusive_ref_counter<
92134
}
93135

94136
private:
137+
bool hasInternalFields() const noexcept {
138+
return internalFields_ != nullptr && internalFields_->has_value();
139+
}
140+
95141
folly::SocketAddress peerAddress_{};
96142
std::string securityProtocol_;
97143
std::shared_ptr<const folly::AsyncTransportCertificate> peerCertificate_;
98144
rocket::TypeErasedPtr userData_{};
145+
detail::InternalFieldsT* internalFields_{nullptr};
99146
};
100147

101148
} // namespace apache::thrift::fast_thrift::thrift

third-party/thrift/src/thrift/lib/cpp2/fast_thrift/thrift/server/common/context/ThriftRequestContext.h

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,8 @@
2424

2525
#include <folly/container/F14Map.h>
2626

27+
#include <folly/CppAttributes.h>
28+
2729
#include <thrift/lib/cpp2/fast_thrift/thrift/server/common/context/ThriftConnContext.h>
2830
#include <thrift/lib/thrift/gen-cpp2/RpcMetadata_types.h>
2931

@@ -83,11 +85,39 @@ class ThriftRequestContext {
8385
return checksumAlgorithm_;
8486
}
8587

88+
// Points this context at the per-request fields the security layer keeps for
89+
// this request. Non-owning, as on the connection context; the fields live on
90+
// the Cpp2RequestContext built for the request, which outlives it.
91+
void setInternalFields(detail::InternalFieldsT* fields) noexcept {
92+
internalFields_ = fields;
93+
}
94+
95+
// The security layer's per-request fields, or null while nothing holds any.
96+
// The connection's own fields are on getConnectionContext().
97+
//
98+
// Unchecked: only valid for the `T` the owner constructed.
99+
template <class T>
100+
T* FOLLY_NULLABLE getInternalFields() noexcept {
101+
return hasInternalFields() ? &internalFields_->value_unchecked<T>()
102+
: nullptr;
103+
}
104+
105+
template <class T>
106+
const T* FOLLY_NULLABLE getInternalFields() const noexcept {
107+
return hasInternalFields() ? &internalFields_->value_unchecked<T>()
108+
: nullptr;
109+
}
110+
86111
private:
112+
bool hasInternalFields() const noexcept {
113+
return internalFields_ != nullptr && internalFields_->has_value();
114+
}
115+
87116
boost::intrusive_ptr<ThriftConnContext> connContext_;
88117
HeaderMap headers_;
89118
apache::thrift::ChecksumAlgorithm checksumAlgorithm_{
90119
apache::thrift::ChecksumAlgorithm::NONE};
120+
detail::InternalFieldsT* internalFields_{nullptr};
91121
};
92122

93123
} // namespace apache::thrift::fast_thrift::thrift

third-party/thrift/src/thrift/lib/cpp2/fast_thrift/thrift/server/common/context/test/ThriftConnContextTest.cpp

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,12 +18,20 @@
1818

1919
#include <gtest/gtest.h>
2020

21+
#include <utility>
22+
2123
#include <folly/SocketAddress.h>
2224

2325
#include <thrift/lib/cpp2/fast_thrift/rocket/common/TypeErasedPtr.h>
2426

2527
namespace apache::thrift::fast_thrift::thrift {
2628

29+
// Stands in for the security layer's per-connection fields. Owned outside the
30+
// context, as the real ones are — they live on the Cpp2ConnContext.
31+
struct TestConnFields {
32+
int value{0};
33+
};
34+
2735
TEST(ThriftConnContextTest, DefaultsAreEmpty) {
2836
boost::intrusive_ptr<ThriftConnContext> ctx{new ThriftConnContext()};
2937
EXPECT_TRUE(ctx->getPeerAddress().empty());
@@ -55,6 +63,53 @@ TEST(ThriftConnContextTest, IntrusivePtrSharesOwnership) {
5563
EXPECT_EQ(b->use_count(), 1);
5664
}
5765

66+
// Nothing to read until something puts fields there: a server with no
67+
// security layer has none, and asking must not invent one. An installed but
68+
// empty slot reads the same way — there is no object to hand back.
69+
TEST(ThriftConnContextTest, InternalFieldsAreNullUntilFilled) {
70+
detail::InternalFieldsT empty;
71+
boost::intrusive_ptr<ThriftConnContext> ctx{new ThriftConnContext()};
72+
EXPECT_EQ(ctx->getInternalFields<TestConnFields>(), nullptr);
73+
EXPECT_EQ(std::as_const(*ctx).getInternalFields<TestConnFields>(), nullptr);
74+
75+
ctx->setInternalFields(&empty);
76+
EXPECT_EQ(ctx->getInternalFields<TestConnFields>(), nullptr);
77+
EXPECT_EQ(std::as_const(*ctx).getInternalFields<TestConnFields>(), nullptr);
78+
}
79+
80+
// The point of the pointer: the context hands out the object its owner holds,
81+
// so a write through the owner is the write this context reads.
82+
TEST(ThriftConnContextTest, InternalFieldsAliasTheOwnersObject) {
83+
detail::InternalFieldsT owned{std::in_place_type<TestConnFields>};
84+
boost::intrusive_ptr<ThriftConnContext> ctx{new ThriftConnContext()};
85+
ctx->setInternalFields(&owned);
86+
87+
owned.value<TestConnFields>().value = 7;
88+
ASSERT_NE(ctx->getInternalFields<TestConnFields>(), nullptr);
89+
EXPECT_EQ(ctx->getInternalFields<TestConnFields>()->value, 7);
90+
EXPECT_EQ(
91+
ctx->getInternalFields<TestConnFields>(), &owned.value<TestConnFields>());
92+
93+
ctx->getInternalFields<TestConnFields>()->value = 9;
94+
EXPECT_EQ(owned.value<TestConnFields>().value, 9);
95+
}
96+
97+
// Two connections point at their own owner's fields, never at each other's.
98+
TEST(ThriftConnContextTest, InternalFieldsAreNotSharedBetweenConnections) {
99+
detail::InternalFieldsT firstFields{std::in_place_type<TestConnFields>};
100+
detail::InternalFieldsT secondFields{std::in_place_type<TestConnFields>};
101+
boost::intrusive_ptr<ThriftConnContext> first{new ThriftConnContext()};
102+
boost::intrusive_ptr<ThriftConnContext> second{new ThriftConnContext()};
103+
first->setInternalFields(&firstFields);
104+
second->setInternalFields(&secondFields);
105+
106+
first->getInternalFields<TestConnFields>()->value = 1;
107+
second->getInternalFields<TestConnFields>()->value = 2;
108+
109+
EXPECT_EQ(first->getInternalFields<TestConnFields>()->value, 1);
110+
EXPECT_EQ(second->getInternalFields<TestConnFields>()->value, 2);
111+
}
112+
58113
TEST(ThriftConnContextTest, IntrusivePtrDeletesOnLastReference) {
59114
// Probe destruction via the user-data deleter — it runs from
60115
// ~ThriftConnContext, which fires when the refcount drops to zero.

third-party/thrift/src/thrift/lib/cpp2/fast_thrift/thrift/server/common/context/test/ThriftRequestContextTest.cpp

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,10 @@
2222

2323
namespace apache::thrift::fast_thrift::thrift {
2424

25+
struct TestRequestFields {
26+
int value{0};
27+
};
28+
2529
TEST(ThriftRequestContextTest, DefaultConstructedHasNoConnContext) {
2630
ThriftRequestContext rc;
2731
EXPECT_EQ(rc.getConnectionContext(), nullptr);
@@ -37,6 +41,41 @@ TEST(ThriftRequestContextTest, SetConnectionContextStoresIt) {
3741
EXPECT_EQ(rc.getConnectionContext()->getSecurityProtocol(), "TLS1.3");
3842
}
3943

44+
// An installed but empty slot reads the same as no slot at all — there is no
45+
// object to hand back.
46+
TEST(ThriftRequestContextTest, InternalFieldsAreNullUntilFilled) {
47+
detail::InternalFieldsT empty;
48+
ThriftRequestContext rc;
49+
EXPECT_EQ(rc.getInternalFields<TestRequestFields>(), nullptr);
50+
51+
rc.setInternalFields(&empty);
52+
EXPECT_EQ(rc.getInternalFields<TestRequestFields>(), nullptr);
53+
}
54+
55+
// Request fields belong to their request: two requests on one connection point
56+
// at their own, and neither at the connection's.
57+
TEST(ThriftRequestContextTest, InternalFieldsArePerRequest) {
58+
detail::InternalFieldsT firstFields{std::in_place_type<TestRequestFields>};
59+
detail::InternalFieldsT secondFields{std::in_place_type<TestRequestFields>};
60+
boost::intrusive_ptr<ThriftConnContext> conn{new ThriftConnContext()};
61+
62+
ThriftRequestContext first;
63+
first.setConnectionContext(conn);
64+
first.setInternalFields(&firstFields);
65+
ThriftRequestContext second;
66+
second.setConnectionContext(conn);
67+
second.setInternalFields(&secondFields);
68+
69+
first.getInternalFields<TestRequestFields>()->value = 1;
70+
second.getInternalFields<TestRequestFields>()->value = 2;
71+
72+
EXPECT_EQ(firstFields.value<TestRequestFields>().value, 1);
73+
EXPECT_EQ(secondFields.value<TestRequestFields>().value, 2);
74+
EXPECT_EQ(
75+
first.getConnectionContext()->getInternalFields<TestRequestFields>(),
76+
nullptr);
77+
}
78+
4079
TEST(ThriftRequestContextTest, KeepsConnContextAliveAfterLocalReset) {
4180
boost::intrusive_ptr<ThriftConnContext> conn{new ThriftConnContext()};
4281
auto* raw = conn.get();

0 commit comments

Comments
 (0)