Skip to content

Commit b37d1dc

Browse files
author
Fuat Geleri
committed
ConsumerBase sends excessive numbers of requestN messages
1 parent 3871246 commit b37d1dc

File tree

6 files changed

+112
-21
lines changed

6 files changed

+112
-21
lines changed

rsocket/statemachine/ConsumerBase.cpp

Lines changed: 17 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,11 @@ void ConsumerBase::cancelConsumer() {
3535
consumingSubscriber_ = nullptr;
3636
}
3737

38+
void ConsumerBase::addImplicitAllowance(size_t n) {
39+
allowance_.add(n);
40+
activeRequests_.add(n);
41+
}
42+
3843
void ConsumerBase::generateRequest(size_t n) {
3944
allowance_.add(n);
4045
pendingAllowance_.add(n);
@@ -64,14 +69,14 @@ void ConsumerBase::processPayload(Payload&& payload, bool onNext) {
6469
if (payload || onNext) {
6570
// Frames carry application-level payloads are taken into account when
6671
// figuring out flow control allowance.
67-
if (allowance_.tryConsume(1)) {
72+
if (allowance_.tryConsume(1) && activeRequests_.tryConsume(1)) {
6873
sendRequests();
6974
if (consumingSubscriber_) {
7075
consumingSubscriber_->onNext(std::move(payload));
7176
} else {
7277
LOG(ERROR)
7378
<< "consuming subscriber is missing, might be a race condition on "
74-
" cancel/onNext.";
79+
" cancel/onNext.";
7580
}
7681
} else {
7782
handleFlowControlError();
@@ -97,12 +102,16 @@ void ConsumerBase::errorConsumer(folly::exception_wrapper ex) {
97102
}
98103

99104
void ConsumerBase::sendRequests() {
100-
// TODO(stupaq): batch if remote end has some spare allowance
101-
// TODO(stupaq): limit how much is synced to the other end
102-
size_t toSync = Frame_REQUEST_N::kMaxRequestN;
103-
toSync = pendingAllowance_.consumeUpTo(toSync);
104-
if (toSync > 0) {
105-
writeRequestN(static_cast<uint32_t>(toSync));
105+
auto toSync =
106+
std::min<size_t>(pendingAllowance_.get(), Frame_REQUEST_N::kMaxRequestN);
107+
auto actives = activeRequests_.get();
108+
if (actives < (toSync + 1) / 2) {
109+
toSync = toSync - actives;
110+
toSync = pendingAllowance_.consumeUpTo(toSync);
111+
if (toSync > 0) {
112+
writeRequestN(static_cast<uint32_t>(toSync));
113+
activeRequests_.add(toSync);
114+
}
106115
}
107116
}
108117

rsocket/statemachine/ConsumerBase.h

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,7 @@ class ConsumerBase : public StreamStateMachineBase,
2727
///
2828
/// This portion of allowance will not be synced to the remote end, but will
2929
/// count towards the limit of allowance the remote PublisherBase may use.
30-
void addImplicitAllowance(size_t n) {
31-
allowance_.add(n);
32-
}
30+
void addImplicitAllowance(size_t n);
3331

3432
void subscribe(
3533
yarpl::Reference<yarpl::flowable::Subscriber<Payload>> subscriber);
@@ -68,6 +66,10 @@ class ConsumerBase : public StreamStateMachineBase,
6866
/// REQUEST_N frames.
6967
Allowance pendingAllowance_;
7068

69+
/// The number of already requested payload count.
70+
/// Prevent excessive requestN calls.
71+
Allowance activeRequests_;
72+
7173
enum class State : uint8_t {
7274
RESPONDING,
7375
CLOSED,

test/RSocketTests.cpp

Lines changed: 11 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,16 @@ std::unique_ptr<TcpConnectionFactory> getConnFactory(
1717
}
1818

1919
std::unique_ptr<RSocketServer> makeServer(
20-
std::shared_ptr<rsocket::RSocketResponder> responder) {
20+
std::shared_ptr<rsocket::RSocketResponder> responder,
21+
std::shared_ptr<RSocketStats> stats) {
2122
TcpConnectionAcceptor::Options opts;
2223
opts.threads = 2;
2324
opts.address = folly::SocketAddress("0.0.0.0", 0);
2425

2526
// RSocket server accepting on TCP.
2627
auto rs = RSocket::createServer(
27-
std::make_unique<TcpConnectionAcceptor>(std::move(opts)));
28+
std::make_unique<TcpConnectionAcceptor>(std::move(opts)),
29+
std::move(stats));
2830

2931
rs->start([r = std::move(responder)](const SetupParameters&) { return r; });
3032
return rs;
@@ -45,14 +47,15 @@ std::unique_ptr<RSocketServer> makeResumableServer(
4547
folly::Future<std::unique_ptr<RSocketClient>> makeClientAsync(
4648
folly::EventBase* eventBase,
4749
uint16_t port,
48-
folly::EventBase* stateMachineEvb) {
50+
folly::EventBase* stateMachineEvb,
51+
std::shared_ptr<RSocketStats> stats) {
4952
CHECK(eventBase);
5053
return RSocket::createConnectedClient(
5154
getConnFactory(eventBase, port),
5255
SetupParameters(),
5356
std::make_shared<RSocketResponder>(),
5457
kDefaultKeepaliveInterval,
55-
RSocketStats::noop(),
58+
std::move(stats),
5659
std::shared_ptr<RSocketConnectionEvents>(),
5760
std::shared_ptr<ResumeManager>(),
5861
std::shared_ptr<ColdResumeHandler>(),
@@ -62,8 +65,10 @@ folly::Future<std::unique_ptr<RSocketClient>> makeClientAsync(
6265
std::unique_ptr<RSocketClient> makeClient(
6366
folly::EventBase* eventBase,
6467
uint16_t port,
65-
folly::EventBase* stateMachineEvb) {
66-
return makeClientAsync(eventBase, port, stateMachineEvb).get();
68+
folly::EventBase* stateMachineEvb,
69+
std::shared_ptr<RSocketStats> stats) {
70+
return makeClientAsync(
71+
eventBase, port, stateMachineEvb, std::move(stats)).get();
6772
}
6873

6974
namespace {

test/RSocketTests.h

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -13,28 +13,50 @@ namespace rsocket {
1313
namespace tests {
1414
namespace client_server {
1515

16+
class RSocketStatsFlowControl : public RSocketStats {
17+
public:
18+
void frameWritten(FrameType frameType) {
19+
if (frameType == FrameType::REQUEST_N) {
20+
++writeRequestN_;
21+
}
22+
}
23+
24+
void frameRead(FrameType frameType) {
25+
if (frameType == FrameType::REQUEST_N) {
26+
++readRequestN_;
27+
}
28+
}
29+
30+
public:
31+
int writeRequestN_{0};
32+
int readRequestN_{0};
33+
};
34+
1635
std::unique_ptr<TcpConnectionFactory> getConnFactory(
1736
folly::EventBase* eventBase,
1837
uint16_t port);
1938

2039
std::unique_ptr<RSocketServer> makeServer(
21-
std::shared_ptr<rsocket::RSocketResponder> responder);
40+
std::shared_ptr<rsocket::RSocketResponder> responder,
41+
std::shared_ptr<RSocketStats> stats = RSocketStats::noop());
2242

2343
std::unique_ptr<RSocketServer> makeResumableServer(
2444
std::shared_ptr<RSocketServiceHandler> serviceHandler);
2545

2646
std::unique_ptr<RSocketClient> makeClient(
2747
folly::EventBase* eventBase,
2848
uint16_t port,
29-
folly::EventBase* stateMachineEvb = nullptr);
49+
folly::EventBase* stateMachineEvb = nullptr,
50+
std::shared_ptr<RSocketStats> stats = RSocketStats::noop());
3051

3152
std::unique_ptr<RSocketClient> makeDisconnectedClient(
3253
folly::EventBase* eventBase);
3354

3455
folly::Future<std::unique_ptr<RSocketClient>> makeClientAsync(
3556
folly::EventBase* eventBase,
3657
uint16_t port,
37-
folly::EventBase* stateMachineEvb = nullptr);
58+
folly::EventBase* stateMachineEvb = nullptr,
59+
std::shared_ptr<RSocketStats> stats = RSocketStats::noop());
3860

3961
std::unique_ptr<RSocketClient> makeWarmResumableClient(
4062
folly::EventBase* eventBase,

test/RequestChannelTest.cpp

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ TEST(RequestChannelTest, Hello) {
6161
ts->assertValueAt(1, "[/hello] Hello Jane!");
6262
}
6363

64+
TEST(RequestChannelTest, HelloNoFlowControl) {
65+
folly::ScopedEventBaseThread worker;
66+
auto server = makeServer(std::make_shared<TestHandlerHello>());
67+
auto stats = std::make_shared<RSocketStatsFlowControl>();
68+
auto client = makeClient(
69+
worker.getEventBase(), *server->listeningPort(), nullptr, stats);
70+
auto requester = client->getRequester();
71+
72+
auto ts = TestSubscriber<std::string>::create();
73+
requester
74+
->requestChannel(
75+
Flowables::justN({"/hello", "Bob", "Jane"})->map([](std::string v) {
76+
return Payload(v);
77+
}))
78+
->map([](auto p) { return p.moveDataToString(); })
79+
->subscribe(ts);
80+
81+
ts->awaitTerminalEvent();
82+
ts->assertSuccess();
83+
ts->assertValueCount(2);
84+
// assert that we echo back the 2nd and 3rd request values
85+
// with the 1st initial payload prepended to each
86+
ts->assertValueAt(0, "[/hello] Hello Bob!");
87+
ts->assertValueAt(1, "[/hello] Hello Jane!");
88+
89+
// Make sure that the initial requestN in the Stream Request Frame
90+
// is already enough and no other requestN messages are sent.
91+
EXPECT_EQ(stats->writeRequestN_, 0);
92+
}
93+
6494
TEST(RequestChannelTest, RequestOnDisconnectedClient) {
6595
folly::ScopedEventBaseThread worker;
6696
auto client = makeDisconnectedClient(worker.getEventBase());

test/RequestStreamTest.cpp

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,28 @@ TEST(RequestStreamTest, HelloFlowControl) {
7676
ts->assertSuccess();
7777
}
7878

79+
TEST(RequestStreamTest, HelloNoFlowControl) {
80+
folly::ScopedEventBaseThread worker;
81+
auto server = makeServer(std::make_shared<TestHandlerSync>());
82+
auto stats = std::make_shared<RSocketStatsFlowControl>();
83+
auto client = makeClient(
84+
worker.getEventBase(), *server->listeningPort(), nullptr, stats);
85+
auto requester = client->getRequester();
86+
auto ts = TestSubscriber<std::string>::create();
87+
requester->requestStream(Payload("Bob"))
88+
->map([](auto p) { return p.moveDataToString(); })
89+
->subscribe(ts);
90+
ts->awaitTerminalEvent();
91+
ts->assertSuccess();
92+
ts->assertValueCount(10);
93+
ts->assertValueAt(0, "Hello Bob 1!");
94+
ts->assertValueAt(9, "Hello Bob 10!");
95+
96+
// Make sure that the initial requestN in the Stream Request Frame
97+
// is already enough and no other requestN messages are sent.
98+
EXPECT_EQ(stats->writeRequestN_, 0);
99+
}
100+
79101
class TestHandlerAsync : public rsocket::RSocketResponder {
80102
public:
81103
Reference<Flowable<Payload>> handleRequestStream(Payload request, StreamId)
@@ -98,7 +120,8 @@ class TestHandlerAsync : public rsocket::RSocketResponder {
98120
return Payload(s, "metadata");
99121
})
100122
->subscribe(subscriber);
101-
}).detach();
123+
})
124+
.detach();
102125
});
103126
}
104127
};

0 commit comments

Comments
 (0)