Skip to content

Commit 9661907

Browse files
committed
feat redis: use userver sockets in mocks
Tests: протестировано локально и в CI commit_hash:a7b16bee12544cf1eb0f5da1308f1da43c12ba8f
1 parent 321bddd commit 9661907

3 files changed

Lines changed: 87 additions & 118 deletions

File tree

redis/src/storages/redis/impl/mock_server_test.cpp

Lines changed: 52 additions & 69 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
#include <fmt/ranges.h>
88
#include <boost/algorithm/string.hpp>
99

10+
#include <userver/engine/task/cancel.hpp>
1011
#include <userver/utest/assert_macros.hpp>
1112
#include <userver/utils/numeric_cast.hpp>
1213
#include <userver/utils/text.hpp>
@@ -20,15 +21,16 @@ const std::string kCrlf = "\r\n";
2021
} // namespace
2122

2223
MockRedisServerBase::MockRedisServerBase(int port)
23-
: acceptor_(io_service_)
24+
: client_tasks_()
2425
{
25-
acceptor_.open(io::ip::tcp::v4());
26-
const boost::asio::ip::tcp::acceptor::reuse_address option(true);
27-
acceptor_.set_option(option);
28-
acceptor_.bind(io::ip::tcp::endpoint(io::ip::tcp::v4(), port));
29-
acceptor_.listen();
26+
auto addr = engine::io::Sockaddr::MakeIPv4LoopbackAddress();
27+
addr.SetPort(port);
28+
listener_ = engine::io::Socket{engine::io::AddrDomain::kInet, engine::io::SocketType::kStream};
29+
listener_.Bind(addr);
30+
port_ = listener_.Getsockname().Port();
31+
listener_.Listen();
3032

31-
thread_ = std::thread(&MockRedisServerBase::Work, this);
33+
listener_task_ = engine::AsyncNoTracing([this] { AcceptLoop(); });
3234
}
3335

3436
MockRedisServerBase::~MockRedisServerBase() { Stop(); }
@@ -45,19 +47,14 @@ void MockRedisServerBase::SendReplyData(ConnectionPtr connection, const storages
4547
SendReply(connection, ReplyDataToRedisProto(reply_data));
4648
}
4749

48-
int MockRedisServerBase::GetPort() const { return acceptor_.local_endpoint().port(); }
50+
int MockRedisServerBase::GetPort() const { return port_; }
4951

50-
void MockRedisServerBase::Stop() {
51-
io_service_.stop();
52-
if (thread_.joinable()) {
53-
thread_.join();
54-
}
55-
}
52+
void MockRedisServerBase::Stop() { listener_task_.SyncCancel(); }
5653

5754
void MockRedisServerBase::SendReply(ConnectionPtr connection, const std::string& reply) {
5855
LOG_DEBUG() << "reply: " << reply;
59-
// TODO: async?
60-
io::write(connection->socket, io::buffer(reply.c_str(), reply.size()));
56+
const auto size = connection->socket.SendAll(reply.data(), reply.size(), {});
57+
UASSERT(size == reply.size());
6158
}
6259

6360
std::string MockRedisServerBase::ReplyDataToRedisProto(const storages::redis::ReplyData& reply_data) {
@@ -84,62 +81,48 @@ std::string MockRedisServerBase::ReplyDataToRedisProto(const storages::redis::Re
8481
}
8582
}
8683

87-
void MockRedisServerBase::Accept() {
88-
auto connection = std::make_shared<Connection>(io_service_);
89-
connection
90-
->reader = std::unique_ptr<redisReader, decltype(&redisReaderFree)>(redisReaderCreate(), &redisReaderFree);
91-
acceptor_.async_accept(connection->socket, [connection, this](auto item) {
92-
OnAccept(connection, std::move(item));
93-
Accept();
94-
});
95-
}
96-
97-
void MockRedisServerBase::Work() {
98-
Accept();
99-
UEXPECT_NO_THROW(io_service_.run());
100-
}
101-
102-
void MockRedisServerBase::OnAccept(ConnectionPtr connection, boost::system::error_code ec) {
103-
LOG_DEBUG() << "accept(2): " << ec;
104-
OnConnected(connection);
105-
DoRead(connection);
106-
}
107-
108-
void MockRedisServerBase::OnRead(ConnectionPtr connection, boost::system::error_code ec, size_t count) {
109-
LOG_DEBUG() << "read " << ec << " count=" << count;
110-
if (ec) {
111-
LOG_DEBUG() << "read(2) error: " << ec;
112-
OnDisconnected(connection);
113-
connection->socket.close();
114-
return;
115-
}
84+
void MockRedisServerBase::AcceptLoop() {
85+
while (!engine::current_task::ShouldCancel()) {
86+
engine::io::Socket client_socket = listener_.Accept({});
87+
if (engine::current_task::ShouldCancel()) {
88+
return;
89+
}
11690

117-
auto ret = redisReaderFeed(connection->reader.get(), connection->data.data(), count);
118-
if (ret != REDIS_OK) {
119-
throw std::runtime_error("redisReaderFeed() returned error: " + std::string(connection->reader->errstr));
91+
auto connection = std::make_shared<Connection>(std::move(client_socket));
92+
connection
93+
->reader = std::unique_ptr<redisReader, decltype(&redisReaderFree)>(redisReaderCreate(), &redisReaderFree);
94+
OnConnected(connection);
95+
client_tasks_.AsyncDetach("mock-redis-client", [this, connection] { HandleConnection(connection); });
12096
}
97+
}
12198

122-
void* hiredis_reply = nullptr;
123-
while (redisReaderGetReply(connection->reader.get(), &hiredis_reply) == REDIS_OK && hiredis_reply) {
124-
auto reply = std::make_shared<
125-
storages::redis::Reply>("", static_cast<redisReply*>(hiredis_reply), storages::redis::ReplyStatus::kOk);
126-
LOG_DEBUG() << "command: " << reply->data.ToDebugString();
99+
void MockRedisServerBase::HandleConnection(ConnectionPtr connection) {
100+
while (!engine::current_task::ShouldCancel()) {
101+
const auto count = connection->socket.RecvSome(connection->data.data(), connection->data.size(), {});
102+
LOG_DEBUG() << "read count=" << count;
103+
if (count == 0) {
104+
LOG_DEBUG() << "read: connection closed";
105+
OnDisconnected(connection);
106+
connection->socket.Close();
107+
return;
108+
}
127109

128-
OnCommand(connection, reply);
129-
freeReplyObject(hiredis_reply);
130-
hiredis_reply = nullptr;
131-
}
110+
auto ret = redisReaderFeed(connection->reader.get(), connection->data.data(), count);
111+
if (ret != REDIS_OK) {
112+
throw std::runtime_error("redisReaderFeed() returned error: " + std::string(connection->reader->errstr));
113+
}
132114

133-
DoRead(connection);
134-
}
115+
void* hiredis_reply = nullptr;
116+
while (redisReaderGetReply(connection->reader.get(), &hiredis_reply) == REDIS_OK && hiredis_reply) {
117+
auto reply = std::make_shared<
118+
storages::redis::Reply>("", static_cast<redisReply*>(hiredis_reply), storages::redis::ReplyStatus::kOk);
119+
LOG_DEBUG() << "command: " << reply->data.ToDebugString();
135120

136-
void MockRedisServerBase::DoRead(ConnectionPtr connection) {
137-
connection->socket.async_read_some(
138-
io::buffer(connection->data),
139-
[connection, this](boost::system::error_code error_code, size_t count) {
140-
OnRead(connection, error_code, count);
121+
OnCommand(connection, reply);
122+
freeReplyObject(hiredis_reply);
123+
hiredis_reply = nullptr;
141124
}
142-
);
125+
}
143126
}
144127

145128
MockRedisServer::~MockRedisServer() { Stop(); }
@@ -361,7 +344,7 @@ void MockRedisServer::RegisterHandlerFunc(
361344
const std::vector<std::string>& args_prefix,
362345
HandlerFunc handler
363346
) {
364-
const std::lock_guard<std::mutex> lock(mutex_);
347+
const std::lock_guard lock{mutex_};
365348
AddHandlerFunc(handlers_[boost::algorithm::to_lower_copy(cmd)], args_prefix, std::move(handler));
366349
}
367350

@@ -410,16 +393,16 @@ MockRedisServer::HandlerPtr MockRedisServer::DoRegisterTimeoutHandler(
410393
}
411394

412395
size_t MockRedisServer::Handler::GetReplyCount() const {
413-
const std::lock_guard<std::mutex> lock(mutex_);
396+
const std::lock_guard lock{mutex_};
414397
return reply_count_;
415398
}
416399

417400
void MockRedisServer::Handler::AccountReply() {
418401
{
419-
const std::lock_guard<std::mutex> lock(mutex_);
402+
const std::lock_guard lock{mutex_};
420403
++reply_count_;
421404
}
422-
cv_.notify_one();
405+
cv_.NotifyOne();
423406
}
424407

425408
MockRedisServer::CommonMasterSlaveInfo::CommonMasterSlaveInfo(

redis/src/storages/redis/impl/mock_server_test.hpp

Lines changed: 22 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -1,45 +1,38 @@
11
#pragma once
22
#include <gtest/gtest.h>
33

4+
#include <atomic>
45
#include <chrono>
5-
#include <condition_variable>
66
#include <memory>
77
#include <mutex>
88
#include <stdexcept>
99
#include <unordered_map>
1010

1111
#include <hiredis/hiredis.h>
1212

13+
#include <userver/concurrent/background_task_storage.hpp>
14+
#include <userver/engine/async.hpp>
15+
#include <userver/engine/condition_variable.hpp>
16+
#include <userver/engine/io/sockaddr.hpp>
17+
#include <userver/engine/io/socket.hpp>
18+
#include <userver/engine/mutex.hpp>
19+
#include <userver/engine/task/task.hpp>
1320
#include <userver/logging/log.hpp>
1421

1522
#include <storages/redis/impl/redis.hpp>
1623
#include <userver/storages/redis/base.hpp>
1724
#include <userver/storages/redis/reply.hpp>
1825

19-
#ifdef __clang__
20-
#pragma clang diagnostic push
21-
#pragma clang diagnostic ignored "-Wimplicit-fallthrough"
22-
#endif
23-
#include <boost/asio.hpp>
24-
#ifdef __clang__
25-
#pragma clang diagnostic pop
26-
#endif
27-
28-
#include <boost/version.hpp>
29-
3026
USERVER_NAMESPACE_BEGIN
3127

32-
namespace io = boost::asio;
33-
3428
class MockRedisServerBase {
3529
public:
3630
struct Connection {
37-
template <class IoService>
38-
Connection(IoService& ios)
39-
: socket(ios)
31+
explicit Connection(engine::io::Socket socket)
32+
: socket(std::move(socket))
4033
{}
4134

42-
io::ip::tcp::socket socket;
35+
engine::io::Socket socket;
4336
std::array<char, 1024> data{};
4437
std::unique_ptr<redisReader, decltype(&redisReaderFree)> reader{nullptr, &redisReaderFree};
4538
};
@@ -63,25 +56,18 @@ class MockRedisServerBase {
6356
virtual void OnCommand(ConnectionPtr /*connection*/, std::shared_ptr<storages::redis::Reply> cmd) {
6457
LOG_DEBUG() << "Got command: " << cmd->data.ToDebugString();
6558
}
66-
void Accept();
6759

6860
private:
6961
static std::string ReplyDataToRedisProto(const storages::redis::ReplyData& reply_data);
70-
void Work();
71-
72-
void OnAccept(ConnectionPtr connection, boost::system::error_code ec);
73-
void OnRead(ConnectionPtr connection, boost::system::error_code ec, size_t count);
74-
void DoRead(ConnectionPtr connection);
62+
void AcceptLoop();
63+
void HandleConnection(ConnectionPtr connection);
7564

7665
void SendReply(ConnectionPtr connection, const std::string& reply);
7766

78-
#if BOOST_VERSION >= 107400
79-
io::io_context io_service_;
80-
#else
81-
io::io_service io_service_;
82-
#endif
83-
io::ip::tcp::acceptor acceptor_;
84-
std::thread thread_;
67+
std::uint16_t port_{};
68+
engine::io::Socket listener_;
69+
concurrent::BackgroundTaskStorage client_tasks_;
70+
engine::Task listener_task_;
8571
};
8672

8773
class MockRedisServer : public MockRedisServerBase {
@@ -168,7 +154,7 @@ class MockRedisServer : public MockRedisServerBase {
168154
);
169155

170156
const std::string description_;
171-
std::mutex mutex_;
157+
engine::Mutex mutex_;
172158
std::unordered_map<std::string, HandlerNode> handlers_;
173159
HandlerPtr ping_handler_;
174160
};
@@ -206,8 +192,8 @@ class MockRedisServer::Handler {
206192
private:
207193
void AccountReply();
208194

209-
mutable std::mutex mutex_;
210-
std::condition_variable cv_;
195+
mutable engine::Mutex mutex_;
196+
engine::ConditionVariable cv_;
211197
size_t reply_count_{0};
212198
};
213199

@@ -238,8 +224,8 @@ struct MockRedisServer::SlaveInfo : public MockRedisServer::CommonMasterSlaveInf
238224

239225
template <typename Rep, typename Period>
240226
bool MockRedisServer::Handler::WaitForFirstReply(const std::chrono::duration<Rep, Period>& duration) {
241-
std::unique_lock<std::mutex> lock(mutex_);
242-
return cv_.wait_for(lock, duration, [&] { return reply_count_; });
227+
std::unique_lock lock{mutex_};
228+
return cv_.WaitFor(lock, duration, [&] { return reply_count_; });
243229
}
244230

245231
template <typename Rep, typename Period>

0 commit comments

Comments
 (0)