mirror of
https://github.com/deepseek-ai/3FS
synced 2025-06-26 18:16:45 +00:00
Initial commit
This commit is contained in:
32
tests/common/net/Echo.h
Normal file
32
tests/common/net/Echo.h
Normal file
@@ -0,0 +1,32 @@
|
||||
#pragma once
|
||||
|
||||
#include "common/net/ib/RDMABuf.h"
|
||||
#include "common/serde/Service.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
|
||||
struct EchoReq {
|
||||
SERDE_STRUCT_FIELD(val, std::string{});
|
||||
SERDE_STRUCT_FIELD(rdma_bufs, std::vector<RDMARemoteBuf>{});
|
||||
};
|
||||
|
||||
struct EchoRsp {
|
||||
SERDE_STRUCT_FIELD(val, std::string{});
|
||||
};
|
||||
|
||||
struct HelloReq {
|
||||
SERDE_STRUCT_FIELD(val, std::string{});
|
||||
};
|
||||
|
||||
struct HelloRsp {
|
||||
SERDE_STRUCT_FIELD(val, std::string{});
|
||||
SERDE_STRUCT_FIELD(idx, uint32_t{});
|
||||
};
|
||||
|
||||
SERDE_SERVICE(Echo, 86) {
|
||||
SERDE_SERVICE_METHOD(echo, 1, EchoReq, EchoRsp);
|
||||
SERDE_SERVICE_METHOD(hello, 2, HelloReq, HelloRsp);
|
||||
SERDE_SERVICE_METHOD(fail, 3, HelloReq, HelloRsp);
|
||||
};
|
||||
|
||||
} // namespace hf3fs::net::test
|
||||
441
tests/common/net/TestEcho.cc
Normal file
441
tests/common/net/TestEcho.cc
Normal file
@@ -0,0 +1,441 @@
|
||||
#include <chrono>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/executors/IOThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/Listener.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/net/sync/Client.h"
|
||||
#include "common/serde/ClientContext.h"
|
||||
#include "common/utils/Address.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/common/net/Echo.h"
|
||||
#include "tests/common/net/ib/SetupIB.h"
|
||||
|
||||
DEFINE_uint32(test_echo_loops, 1000, "run echo RPC times per coroutine in TestEcho.MultiThreads");
|
||||
DEFINE_uint32(test_echo_threads, 8, "threads number in TestEcho.MultiThreads");
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
namespace {
|
||||
|
||||
class EchoServiceImpl : public serde::ServiceWrapper<EchoServiceImpl, Echo> {
|
||||
public:
|
||||
CoTryTask<EchoRsp> echo(serde::CallContext &ctx, const EchoReq &req) {
|
||||
EchoRsp rsp;
|
||||
rsp.val = req.val;
|
||||
co_return rsp;
|
||||
}
|
||||
|
||||
CoTryTask<HelloRsp> hello(serde::CallContext &ctx, const HelloReq &req) {
|
||||
HelloRsp rsp;
|
||||
rsp.val = "Hello, " + req.val;
|
||||
rsp.idx = ++idx_;
|
||||
co_return rsp;
|
||||
}
|
||||
|
||||
CoTryTask<HelloRsp> fail(serde::CallContext &ctx, const HelloReq &) {
|
||||
fmt::print("Request from {}\n", ctx.transport()->describe());
|
||||
co_return makeError(RPCCode::kInvalidMessageType, "failed");
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t idx_ = 0;
|
||||
};
|
||||
static_assert(EchoServiceImpl::kServiceID == 86, "check service id failed");
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class TestEcho : public testing::TestWithParam<std::tuple<Address::Type, bool>> {
|
||||
public:
|
||||
static void SetUpTestSuite() { SetupIB::SetUpTestSuite(); }
|
||||
|
||||
void TearDown() override {
|
||||
client_.stopAndJoin();
|
||||
server_.stopAndJoin();
|
||||
}
|
||||
|
||||
Address serverAddr() const { return server_.groups().front()->addressList().front(); }
|
||||
|
||||
protected:
|
||||
Client::Config clientConfig_;
|
||||
bool initClientConfig = [this] {
|
||||
auto rwInEventThread = std::get<1>(GetParam());
|
||||
clientConfig_.io_worker().set_read_write_rdma_in_event_thread(rwInEventThread);
|
||||
clientConfig_.io_worker().set_read_write_tcp_in_event_thread(rwInEventThread);
|
||||
return true;
|
||||
}();
|
||||
Client client_{clientConfig_};
|
||||
|
||||
Server::Config serverConfig_;
|
||||
bool initServerConfig = [this] {
|
||||
serverConfig_.groups(0).set_network_type(std::get<0>(GetParam()));
|
||||
|
||||
auto rwInEventThread = std::get<1>(GetParam());
|
||||
serverConfig_.groups(0).io_worker().set_read_write_rdma_in_event_thread(rwInEventThread);
|
||||
serverConfig_.groups(0).io_worker().set_read_write_tcp_in_event_thread(rwInEventThread);
|
||||
return true;
|
||||
}();
|
||||
Server server_{serverConfig_};
|
||||
};
|
||||
|
||||
TEST_P(TestEcho, RequestAndResponse) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
if (result.hasError()) {
|
||||
std::cout << result.error().describe() << std::endl;
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, "foo");
|
||||
}
|
||||
|
||||
{
|
||||
HelloReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.hello(ctx, req);
|
||||
CO_ASSERT_OK(result);
|
||||
|
||||
CO_ASSERT_EQ(result->val, "Hello, foo");
|
||||
CO_ASSERT_EQ(result->idx, 1);
|
||||
|
||||
result = co_await client.hello(ctx, req);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->idx, 2);
|
||||
}
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, "foo");
|
||||
}
|
||||
|
||||
{
|
||||
HelloReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.fail(ctx, req);
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
CO_ASSERT_EQ(result.error().code(), RPCCode::kInvalidMessageType);
|
||||
CO_ASSERT_EQ(result.error().message(), "failed");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, AddrType) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
auto addr = serverAddr();
|
||||
addr.type = addr.isTCP() ? Address::RDMA : Address::TCP;
|
||||
auto ctx = client_.serdeCtx(addr);
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
if (addr.isTCP()) {
|
||||
CO_ASSERT_TRUE(result.hasValue());
|
||||
} else {
|
||||
CO_ASSERT_FALSE(result.hasValue());
|
||||
}
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, TimeoutStopped) {
|
||||
// Timeout caused by a stopped service
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
net::UserRequestOptions options{};
|
||||
options.timeout = 100_ms;
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
server_.stopAndJoin();
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req, &options);
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
CO_ASSERT_EQ(result.error().code(), RPCCode::kTimeout);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, WithTimestamp) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
serde::Timestamp timestamp;
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req, nullptr, ×tamp);
|
||||
if (result.hasError()) {
|
||||
XLOGF(CRITICAL, "error: {}", result.error().describe());
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, "foo");
|
||||
}));
|
||||
|
||||
ASSERT_NE(timestamp.serverLatency(), Duration{});
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, Frozen) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
server_.setFrozen(true);
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
CO_ASSERT_EQ(result.error().code(), RPCCode::kTimeout);
|
||||
}
|
||||
|
||||
server_.setFrozen(false);
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, "foo");
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, SendFailed) {
|
||||
client_.start();
|
||||
net::UserRequestOptions options;
|
||||
options.timeout = 1_s;
|
||||
auto ctx = client_.serdeCtx(Address{0});
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
{
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req, &options);
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
CO_ASSERT_EQ(result.error().code(), RPCCode::kSendFailed);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, LargeMessage) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
net::UserRequestOptions options;
|
||||
options.timeout = 5_s;
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
EchoReq req;
|
||||
req.val = std::string(10_MB + 1, 'A');
|
||||
auto result = co_await client.echo(ctx, req, &options);
|
||||
if (result.hasError()) {
|
||||
XLOGF(CRITICAL, "error: {}", result.error().describe());
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, req.val);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, CompressedLargeMessage) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
net::UserRequestOptions options;
|
||||
options.timeout = 5_s;
|
||||
options.compression = {1, 1_KB};
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
|
||||
EchoReq req;
|
||||
req.val = std::string(10_MB + 1, 'A');
|
||||
auto result = co_await client.echo(ctx, req, &options);
|
||||
if (result.hasError()) {
|
||||
XLOGF(CRITICAL, "error: {}", result.error().describe());
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, req.val);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_P(TestEcho, MultiThreads) {
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server_.setup());
|
||||
ASSERT_TRUE(server_.start());
|
||||
// some CI machine enable kmemleak test, may case long delay.
|
||||
clientConfig_.set_default_timeout(10000_ms);
|
||||
client_.start();
|
||||
net::UserRequestOptions options;
|
||||
options.timeout = 5_s;
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
auto testCoro = [&]() -> CoTask<bool> {
|
||||
for (size_t i = 0; i < FLAGS_test_echo_loops; i++) {
|
||||
Echo<> client;
|
||||
|
||||
EchoReq req;
|
||||
req.val = std::to_string(folly::Random::rand32());
|
||||
auto result = co_await client.echo(ctx, req, &options);
|
||||
if (result.hasError()) {
|
||||
XLOGF(CRITICAL, "error: {}", result.error().describe());
|
||||
co_return false;
|
||||
}
|
||||
EXPECT_EQ(result->val, req.val);
|
||||
}
|
||||
co_return true;
|
||||
};
|
||||
|
||||
for (size_t numThreads = 2; numThreads <= FLAGS_test_echo_threads; numThreads *= 2) {
|
||||
folly::CPUThreadPoolExecutor exec(numThreads);
|
||||
std::vector<folly::SemiFuture<bool>> tasks;
|
||||
for (size_t coroId = 0; coroId < 16 * numThreads; coroId++) {
|
||||
tasks.emplace_back(testCoro().scheduleOn(folly::Executor::getKeepAliveToken(exec)).start());
|
||||
}
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
std::vector<bool> results = co_await folly::coro::collectAllRange(std::move(tasks));
|
||||
CO_ASSERT_TRUE(std::all_of(results.begin(), results.end(), [](auto ok) { return ok; }));
|
||||
}));
|
||||
exec.join();
|
||||
}
|
||||
}
|
||||
|
||||
INSTANTIATE_TEST_SUITE_P(TestEcho,
|
||||
TestEcho,
|
||||
testing::Combine(testing::Values(Address::RDMA, Address::TCP, Address::UNIX),
|
||||
testing::Values(true, false)));
|
||||
|
||||
TEST(TestEchoSync, Normal) {
|
||||
std::array<Address::Type, 4> networks{Address::LOCAL, Address::TCP /*, Address::IPoIB */};
|
||||
for (auto network : networks) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(network);
|
||||
Server server_{serverConfig};
|
||||
|
||||
server_.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_OK(server_.setup());
|
||||
auto initResult = server_.start();
|
||||
if (!initResult) {
|
||||
fmt::print("init failed: {}\n", initResult.error().describe());
|
||||
ASSERT_TRUE(initResult);
|
||||
}
|
||||
auto serverAddr = server_.groups().front()->addressList().front();
|
||||
|
||||
sync::Client::Config clientConfig;
|
||||
sync::Client client_{clientConfig};
|
||||
auto ctx = client_.serdeCtx(serverAddr);
|
||||
Echo<> client;
|
||||
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = client.echoSync(ctx, req);
|
||||
if (result.hasError()) {
|
||||
std::cout << result.error().describe() << std::endl;
|
||||
}
|
||||
ASSERT_FALSE(result.hasError());
|
||||
|
||||
auto &rsp = result.value();
|
||||
ASSERT_EQ(rsp.val, "foo");
|
||||
|
||||
req.val = "bar";
|
||||
result = client.echoSync(ctx, req);
|
||||
if (result.hasError()) {
|
||||
std::cout << result.error().describe() << std::endl;
|
||||
}
|
||||
ASSERT_FALSE(result.hasError());
|
||||
ASSERT_EQ(rsp.val, "bar");
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestEchoListener, Normal) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(Address::TCP);
|
||||
serverConfig.groups(0).listener().set_listen_port(folly::Random::rand32(10000, 20000)); // same port.
|
||||
Server server1{serverConfig};
|
||||
Server server2{serverConfig};
|
||||
ASSERT_OK(server1.setup());
|
||||
ASSERT_FALSE(server2.setup());
|
||||
ASSERT_OK(server1.start());
|
||||
ASSERT_FALSE(server2.start());
|
||||
}
|
||||
|
||||
TEST(TestEchoListener, StartAndStop) {
|
||||
constexpr auto N = 100;
|
||||
for (auto i = 0; i < N; ++i) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(Address::TCP);
|
||||
Server server{serverConfig};
|
||||
ASSERT_OK(server.setup());
|
||||
server.stopAndJoin();
|
||||
}
|
||||
}
|
||||
|
||||
TEST(TestEchoListener, DomainSocket) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(Address::UNIX);
|
||||
serverConfig.groups(0).listener().set_listen_port(folly::Random::rand32(10000, 20000)); // same port.
|
||||
Server server{serverConfig};
|
||||
ASSERT_OK(server.setup());
|
||||
ASSERT_OK(server.start());
|
||||
std::this_thread::sleep_for(1_s);
|
||||
server.stopAndJoin();
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::net::test
|
||||
58
tests/common/net/TestEventLoop.cc
Normal file
58
tests/common/net/TestEventLoop.cc
Normal file
@@ -0,0 +1,58 @@
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/net/EventLoop.h"
|
||||
#include "common/net/Network.h"
|
||||
#include "common/utils/FdWrapper.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
namespace {
|
||||
|
||||
TEST(TestEventLoop, Normal) {
|
||||
auto eventLoop = EventLoop::create();
|
||||
ASSERT_TRUE(eventLoop->start());
|
||||
eventLoop->stopAndJoin();
|
||||
}
|
||||
|
||||
TEST(TestEventLoop, AddObject) {
|
||||
auto eventLoop = EventLoop::create();
|
||||
ASSERT_TRUE(eventLoop->start());
|
||||
|
||||
class PipeReader : public EventLoop::EventHandler {
|
||||
public:
|
||||
PipeReader(int fd)
|
||||
: fd_(fd) {}
|
||||
|
||||
int fd() const final { return fd_; }
|
||||
void handleEvents(uint32_t epollEvents) final {
|
||||
ASSERT_TRUE(epollEvents & EPOLLIN);
|
||||
uint32_t val;
|
||||
ASSERT_EQ(::read(fd_, &val, sizeof(val)), sizeof(val));
|
||||
val_ = val;
|
||||
}
|
||||
|
||||
auto &val() const { return val_; }
|
||||
|
||||
private:
|
||||
FdWrapper fd_;
|
||||
std::atomic<uint32_t> val_{0};
|
||||
};
|
||||
|
||||
int p[2];
|
||||
ASSERT_EQ(::pipe(p), 0);
|
||||
auto pipeReader = std::make_shared<PipeReader>(p[0]);
|
||||
ASSERT_TRUE(eventLoop->add(pipeReader, EPOLLIN | EPOLLET));
|
||||
ASSERT_EQ(pipeReader->val().load(), 0);
|
||||
|
||||
uint32_t val = rand();
|
||||
ASSERT_EQ(::write(p[1], &val, sizeof(val)), sizeof(val));
|
||||
while (pipeReader->val().load() == 0) {
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
ASSERT_EQ(pipeReader->val().load(), val);
|
||||
|
||||
ASSERT_TRUE(eventLoop->remove(pipeReader.get()));
|
||||
std::this_thread::sleep_for(10ms);
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::net::test
|
||||
25
tests/common/net/TestIOWorker.cc
Normal file
25
tests/common/net/TestIOWorker.cc
Normal file
@@ -0,0 +1,25 @@
|
||||
#include <folly/net/NetworkSocket.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/net/IOWorker.h"
|
||||
#include "common/net/Network.h"
|
||||
#include "common/net/Processor.h"
|
||||
#include "common/net/Transport.h"
|
||||
|
||||
namespace hf3fs::net {
|
||||
|
||||
TEST(TestIOWorker, Normal) {
|
||||
CPUExecutorGroup procExecutor(2, "");
|
||||
CPUExecutorGroup ioExecutor(2, "");
|
||||
folly::IOThreadPoolExecutor connExecutor(2);
|
||||
serde::Services serdeServices;
|
||||
Processor::Config processorConfig{};
|
||||
Processor processor(serdeServices, procExecutor, processorConfig);
|
||||
|
||||
IOWorker::Config ioWorkerConfig{};
|
||||
IOWorker ioWorker(processor, ioExecutor, connExecutor, ioWorkerConfig);
|
||||
|
||||
ASSERT_TRUE(ioWorker.start("Test"));
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net
|
||||
97
tests/common/net/TestProcessor.cc
Normal file
97
tests/common/net/TestProcessor.cc
Normal file
@@ -0,0 +1,97 @@
|
||||
#include <atomic>
|
||||
#include <cstdint>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/Future.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/Network.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/serde/Service.h"
|
||||
#include "tests/common/net/Echo.h"
|
||||
#include "tests/common/net/ib/SetupIB.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
namespace {
|
||||
|
||||
class EchoServiceImpl : public serde::ServiceWrapper<EchoServiceImpl, Echo> {
|
||||
public:
|
||||
CoTryTask<EchoRsp> echo(serde::CallContext &ctx, const EchoReq &) {
|
||||
co_await folly::coro::sleep(50ms);
|
||||
co_return EchoRsp{};
|
||||
}
|
||||
CoTryTask<HelloRsp> hello(serde::CallContext &ctx, const HelloReq &) { co_return HelloRsp{}; }
|
||||
CoTryTask<HelloRsp> fail(serde::CallContext &ctx, const HelloReq &) { co_return HelloRsp{}; }
|
||||
|
||||
Status onError(Status status) {
|
||||
++error_;
|
||||
return status;
|
||||
}
|
||||
|
||||
uint32_t error() { return error_; }
|
||||
|
||||
private:
|
||||
std::atomic<uint32_t> error_ = 0;
|
||||
};
|
||||
|
||||
} // namespace
|
||||
|
||||
class TestProcessor : public SetupIB {};
|
||||
|
||||
TEST_F(TestProcessor, TooManyProcessingRequests) {
|
||||
constexpr auto kMaxProcessingRequestsNum = 10;
|
||||
constexpr auto kConcurrentRequestsNum = 16;
|
||||
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).processor().set_max_processing_requests_num(kMaxProcessingRequestsNum);
|
||||
Server server(serverConfig);
|
||||
ASSERT_TRUE(server.setup());
|
||||
auto impl = std::make_unique<EchoServiceImpl>();
|
||||
auto ptr = impl.get();
|
||||
server.addSerdeService(std::move(impl));
|
||||
ASSERT_TRUE(server.start());
|
||||
ASSERT_EQ(ptr->error(), 0);
|
||||
|
||||
Client::Config clientConfig;
|
||||
Client client{clientConfig};
|
||||
client.start();
|
||||
auto ctx = client.serdeCtx(server.groups().front()->addressList().front());
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
std::vector<folly::SemiFuture<Result<EchoRsp>>> requests;
|
||||
requests.reserve(kConcurrentRequestsNum);
|
||||
EchoReq echoReq{};
|
||||
for (auto i = 0; i < kConcurrentRequestsNum; ++i) {
|
||||
requests.push_back(client.echo(ctx, echoReq).semi());
|
||||
}
|
||||
auto results = co_await folly::collectAll(std::move(requests));
|
||||
|
||||
auto succ = 0;
|
||||
auto fail = 0;
|
||||
for (auto &result : results) {
|
||||
auto &rsp = *result;
|
||||
if (rsp.hasError()) {
|
||||
++fail;
|
||||
CO_ASSERT_EQ(rsp.error().code(), RPCCode::kRequestRefused);
|
||||
} else {
|
||||
++succ;
|
||||
}
|
||||
}
|
||||
|
||||
CO_ASSERT_GE(succ, kMaxProcessingRequestsNum);
|
||||
CO_ASSERT_GT(fail, 0);
|
||||
CO_ASSERT_LE(fail, kConcurrentRequestsNum - kMaxProcessingRequestsNum);
|
||||
CO_ASSERT_NE(ptr->error(), 0);
|
||||
CO_ASSERT_EQ(ptr->error(), fail);
|
||||
|
||||
auto [rsp, _] = co_await folly::coro::collectAll(client.echo(ctx, EchoReq{}), [&]() -> CoTask<void> {
|
||||
co_await folly::coro::sleep(10ms);
|
||||
server.stopAndJoin();
|
||||
}());
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net::test
|
||||
51
tests/common/net/TestRDMAControl.cc
Normal file
51
tests/common/net/TestRDMAControl.cc
Normal file
@@ -0,0 +1,51 @@
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/RDMAControl.h"
|
||||
#include "common/net/RequestOptions.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/serde/ClientContext.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/common/net/ib/SetupIB.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
namespace {
|
||||
|
||||
struct RDMAControlService : serde::ServiceWrapper<RDMAControlService, RDMAControl> {
|
||||
CoTryTask<RDMATransmissionRsp> apply(serde::CallContext &ctx, const RDMATransmissionReq &req) {
|
||||
auto &tr = ctx.transport();
|
||||
if (!tr->isRDMA()) {
|
||||
co_return makeError(StatusCode::kInvalidArg);
|
||||
}
|
||||
serde::ClientContext clientCtx(tr);
|
||||
auto result = co_await RDMAControl<>::apply(clientCtx, req);
|
||||
co_return result;
|
||||
}
|
||||
};
|
||||
|
||||
TEST(TestRDMAControl, Normal) {
|
||||
SetupIB::SetUpTestSuite();
|
||||
|
||||
net::Server::Config serverConfig;
|
||||
net::Server server_{serverConfig};
|
||||
ASSERT_OK(server_.setup());
|
||||
ASSERT_OK(server_.start());
|
||||
ASSERT_OK(server_.addSerdeService(std::make_unique<RDMAControlService>()));
|
||||
|
||||
net::Client::Config clientConfig;
|
||||
net::Client client_{clientConfig};
|
||||
ASSERT_OK(client_.start());
|
||||
auto ctx = client_.serdeCtx(server_.groups().front()->addressList().front());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
{
|
||||
RDMATransmissionReq req;
|
||||
auto result = co_await RDMAControl<>::apply(ctx, req);
|
||||
if (result.hasError()) {
|
||||
std::cout << result.error().describe() << std::endl;
|
||||
}
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::net::test
|
||||
62
tests/common/net/TestWaiter.cc
Normal file
62
tests/common/net/TestWaiter.cc
Normal file
@@ -0,0 +1,62 @@
|
||||
#include <chrono>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <thread>
|
||||
|
||||
#include "common/net/Waiter.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
namespace {
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
TEST(TestTimer, Normal) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
Waiter::Item item;
|
||||
auto uuid = Waiter::instance().bind(item);
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
|
||||
Waiter::instance().schedule(uuid, std::chrono::milliseconds(50));
|
||||
co_await item.baton;
|
||||
|
||||
auto elapsed = std::chrono::steady_clock::now() - start;
|
||||
CO_ASSERT_TRUE(30ms <= elapsed && elapsed <= 70ms);
|
||||
CO_ASSERT_EQ(item.status.code(), RPCCode::kTimeout);
|
||||
}());
|
||||
}
|
||||
|
||||
TEST(TestTimer, TwoUUIDs) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
Waiter::Item item1;
|
||||
auto uuid1 = Waiter::instance().bind(item1);
|
||||
Waiter::Item item2;
|
||||
auto uuid2 = Waiter::instance().bind(item2);
|
||||
|
||||
auto start = std::chrono::steady_clock::now();
|
||||
Waiter::instance().schedule(uuid1, std::chrono::milliseconds(50));
|
||||
Waiter::instance().schedule(uuid2, std::chrono::milliseconds(80));
|
||||
|
||||
co_await item1.baton;
|
||||
co_await item2.baton;
|
||||
|
||||
auto elapsed = std::chrono::steady_clock::now() - start;
|
||||
CO_ASSERT_TRUE(60ms <= elapsed);
|
||||
CO_ASSERT_TRUE(elapsed <= 100ms);
|
||||
}());
|
||||
}
|
||||
|
||||
TEST(TestTimer, TimeoutBeforeWait) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
Waiter::Item item;
|
||||
auto uuid = Waiter::instance().bind(item);
|
||||
Waiter::instance().schedule(uuid, std::chrono::milliseconds(0));
|
||||
|
||||
std::this_thread::sleep_for(10ms);
|
||||
co_await item.baton;
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace
|
||||
} // namespace hf3fs::net::test
|
||||
22
tests/common/net/ib/SetupIB.h
Normal file
22
tests/common/net/ib/SetupIB.h
Normal file
@@ -0,0 +1,22 @@
|
||||
#pragma once
|
||||
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gtest/gtest.h>
|
||||
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/utils/ConfigBase.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
|
||||
class SetupIB : public ::testing::Test {
|
||||
public:
|
||||
static void SetUpTestSuite() {
|
||||
static IBConfig config;
|
||||
auto ib = IBManager::start(config);
|
||||
XLOGF_IF(FATAL, ib.hasError(), "IBManager start failed, result {}", ib.error());
|
||||
ASSERT_FALSE(IBDevice::all().empty());
|
||||
}
|
||||
};
|
||||
|
||||
} // namespace hf3fs::net::test
|
||||
263
tests/common/net/ib/TestIBDevice.cc
Normal file
263
tests/common/net/ib/TestIBDevice.cc
Normal file
@@ -0,0 +1,263 @@
|
||||
#include <algorithm>
|
||||
#include <array>
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <cstdlib>
|
||||
#include <fmt/compile.h>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/Conv.h>
|
||||
#include <folly/IPAddressV4.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/ScopeGuard.h>
|
||||
#include <folly/String.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/futures/detail/Types.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <infiniband/verbs.h>
|
||||
#include <iostream>
|
||||
#include <set>
|
||||
#include <string>
|
||||
#include <string_view>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <vector>
|
||||
|
||||
#include "SetupIB.h"
|
||||
#include "common/net/IfAddrs.h"
|
||||
#include "common/net/ib/IBConnect.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/MagicEnum.hpp"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
DEFINE_uint64(ibdev_reg_mem_size, 1ull << 30, "Total memory size to register.");
|
||||
DEFINE_uint32(ibdev_reg_mem_ms, 100, "Time to run reg memory test for each case.");
|
||||
|
||||
DEFINE_uint64(reg_gb, 1, "reg memory size.");
|
||||
DEFINE_bool(reg_odp, false, "odp");
|
||||
DEFINE_bool(reg_releaxed_ording, false, "releaxed ording");
|
||||
DEFINE_uint32(reg_split, 1, "reg split large memory");
|
||||
|
||||
namespace hf3fs::net {
|
||||
|
||||
class TestIBDevice : public test::SetupIB {};
|
||||
|
||||
TEST_F(TestIBDevice, IfAddrs) {
|
||||
auto result = IfAddrs::load();
|
||||
ASSERT_TRUE(!result.hasError());
|
||||
for (auto [name, addr] : *result) {
|
||||
fmt::print("{} -> ip {}, mask {}, up {}\n", name, addr.ip.str(), addr.mask, addr.up);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestIBDevice, Normal) {
|
||||
for (auto &dev : IBDevice::all()) {
|
||||
fmt::print("dev: {}\n", dev->name());
|
||||
ASSERT_NE(dev->ports().size(), 0);
|
||||
ASSERT_NE(dev->context(), nullptr);
|
||||
for (size_t portNum = 1; portNum <= dev->ports().size(); portNum++) {
|
||||
auto port = dev->openPort(portNum);
|
||||
ASSERT_FALSE(port.hasError()) << port.error().describe();
|
||||
fmt::print("\tport {}, zones {}\n", *port, fmt::join(port->zones().begin(), port->zones().end(), ";"));
|
||||
ASSERT_EQ(port->dev(), dev.get());
|
||||
|
||||
if (port->isRoCE()) {
|
||||
auto r = port->getRoCEv2Gid();
|
||||
fmt::print("\tRoCE v2 GID[{}]: {}\n", r.second, r.first);
|
||||
}
|
||||
|
||||
for (int i = 0; true; i++) {
|
||||
ibv_gid gid;
|
||||
auto ret = ibv_query_gid(dev->context(), port->portNum(), i, &gid);
|
||||
if (ret || std::all_of(&gid.raw[0], &gid.raw[sizeof(gid)], [](auto v) { return v == 0; })) {
|
||||
break;
|
||||
}
|
||||
auto ip = folly::IPAddressV6::fromBinary(folly::ByteRange(gid.raw, sizeof(gid.raw)));
|
||||
fmt::print("\t\tGID[{}]: {}, {} {}\n", i, gid, ip.str(), ip.is6To4());
|
||||
}
|
||||
|
||||
for (int i = 0; i < 1000; i++) {
|
||||
__be16 pkey;
|
||||
auto ret = ibv_query_pkey(dev->context(), portNum, i, &pkey);
|
||||
if (ret != 0 || pkey == 0) {
|
||||
break;
|
||||
}
|
||||
fmt::print("dev {}, port {}, pkey {} => {:x}\n", dev->name(), portNum, i, pkey);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestIBDevice, UpdatePortStatus) {
|
||||
ASSERT_FALSE(IBDevice::all().empty());
|
||||
ASSERT_FALSE(IBDevice::get(0)->ports().empty());
|
||||
|
||||
for (auto &dev : IBDevice::all()) {
|
||||
for (auto &[portNum, port] : dev->ports()) {
|
||||
auto state = port.attr.rlock()->state;
|
||||
|
||||
// disable a port by set state to DOWN
|
||||
port.attr.withWLock([](auto &attr) { attr.state = IBV_PORT_DOWN; });
|
||||
// open port, then port state should be updated
|
||||
auto result = dev->openPort(portNum);
|
||||
ASSERT_OK(result);
|
||||
ASSERT_EQ(result->attr().state, state);
|
||||
ASSERT_EQ(port.attr.rlock()->state, state);
|
||||
|
||||
// disable a port by set state to DOWN
|
||||
port.attr.withWLock([](auto &attr) { attr.state = IBV_PORT_DOWN; });
|
||||
// wait sometime, period runner will update port status
|
||||
auto begin = SteadyClock::now();
|
||||
while (true) {
|
||||
auto attr = *port.attr.rlock();
|
||||
if (attr.state == state) {
|
||||
break;
|
||||
}
|
||||
ASSERT_TRUE(SteadyClock::now() < begin + 30_s) << "port status doesn't update in 30s";
|
||||
std::this_thread::sleep_for(1_s);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
auto regFlags() {
|
||||
auto flags = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE;
|
||||
if (FLAGS_reg_odp) {
|
||||
flags |= IBV_ACCESS_ON_DEMAND;
|
||||
}
|
||||
if (FLAGS_reg_releaxed_ording) {
|
||||
flags |= IBV_ACCESS_RELAXED_ORDERING;
|
||||
}
|
||||
return flags;
|
||||
}
|
||||
|
||||
TEST_F(TestIBDevice, RegMemory) {
|
||||
void *mem = malloc(FLAGS_ibdev_reg_mem_size);
|
||||
auto guard = folly::makeGuard([&]() { free(mem); });
|
||||
auto benchmark = [&]() {
|
||||
for (uint64_t bs = 4096; bs <= (16 << 20); bs *= 4) {
|
||||
auto numBlock = FLAGS_ibdev_reg_mem_size / bs;
|
||||
std::chrono::nanoseconds regDur(0), deregDur(0);
|
||||
auto begin = std::chrono::steady_clock::now();
|
||||
size_t iters = 0;
|
||||
while (std::chrono::steady_clock::now() - begin < std::chrono::milliseconds(FLAGS_ibdev_reg_mem_ms)) {
|
||||
std::array<ibv_mr *, 100> mrs;
|
||||
auto regBegin = std::chrono::steady_clock::now();
|
||||
for (size_t i = 0; i < mrs.size(); i++) {
|
||||
size_t offset = (i % numBlock) * bs;
|
||||
void *ptr = (uint8_t *)mem + offset;
|
||||
mrs[i] = IBDevice::get(0)->regMemory(ptr, bs, regFlags());
|
||||
|
||||
iters++;
|
||||
}
|
||||
regDur += std::chrono::steady_clock::now() - regBegin;
|
||||
|
||||
auto deregBegin = std::chrono::steady_clock::now();
|
||||
for (auto mr : mrs) {
|
||||
IBDevice::get(0)->deregMemory(mr);
|
||||
}
|
||||
deregDur += std::chrono::steady_clock::now() - deregBegin;
|
||||
}
|
||||
|
||||
fmt::print("bs: {:8}, reg {:192.168f} us, dereg {:192.168f} us\n",
|
||||
bs,
|
||||
regDur.count() / 1000.0 / iters,
|
||||
deregDur.count() / 1000.0 / iters);
|
||||
}
|
||||
};
|
||||
|
||||
benchmark();
|
||||
auto mr = IBDevice::get(0)->regMemory(mem, FLAGS_ibdev_reg_mem_size, regFlags());
|
||||
fmt::print("Register reged memory.\n");
|
||||
benchmark();
|
||||
IBDevice::get(0)->deregMemory(mr);
|
||||
}
|
||||
|
||||
TEST_F(TestIBDevice, DISABLED_RegMR) {
|
||||
std::atomic_bool exit = false;
|
||||
auto progress = std::jthread([&]() {
|
||||
while (!exit) {
|
||||
sleep(5);
|
||||
XLOGF(INFO, "still alive");
|
||||
}
|
||||
});
|
||||
|
||||
auto memsize = FLAGS_reg_gb * (1ULL << 30);
|
||||
XLOGF(INFO, "malloc begin {}", memsize);
|
||||
auto *mem = (uint8_t *)malloc(memsize);
|
||||
XLOGF(INFO, "malloc success, {} {}", memsize, (void *)mem);
|
||||
|
||||
std::vector<ibv_mr *> mrs;
|
||||
auto bs = memsize / FLAGS_reg_split;
|
||||
XLOGF(INFO, "begin reg");
|
||||
for (size_t i = 0; i < FLAGS_reg_split; i++) {
|
||||
auto mr = IBDevice::get(0)->regMemory(mem + bs * i, bs, regFlags());
|
||||
XLOGF(INFO, "reg get {}", (void *)mr);
|
||||
ASSERT_NE(mr, nullptr) << memsize;
|
||||
mrs.push_back(mr);
|
||||
}
|
||||
|
||||
XLOGF(INFO, "dereg begin");
|
||||
for (auto mr : mrs) {
|
||||
IBDevice::get(0)->deregMemory(mr);
|
||||
}
|
||||
XLOGF(INFO, "dereg finish");
|
||||
exit = true;
|
||||
}
|
||||
|
||||
TEST_F(TestIBDevice, DISABLED_VerbsRegMR) {
|
||||
int num_devices;
|
||||
struct ibv_device **dev_list = ibv_get_device_list(&num_devices);
|
||||
SCOPE_EXIT { ibv_free_device_list(dev_list); };
|
||||
ASSERT_NE(dev_list, nullptr);
|
||||
XLOGF(INFO, "Use device {}", ibv_get_device_name(dev_list[0]));
|
||||
|
||||
auto dev = dev_list[0];
|
||||
auto ctx = ibv_open_device(dev);
|
||||
ASSERT_NE(ctx, nullptr);
|
||||
auto pd = ibv_alloc_pd(ctx);
|
||||
ASSERT_NE(pd, nullptr);
|
||||
|
||||
// malloc
|
||||
auto memsize = FLAGS_reg_gb * (1ULL << 30);
|
||||
XLOGF(INFO, "malloc begin {}", memsize);
|
||||
auto *mem = (uint8_t *)malloc(memsize);
|
||||
XLOGF(INFO, "malloc success, {} {}", memsize, (void *)mem);
|
||||
|
||||
// ibv_reg_mr
|
||||
std::vector<ibv_mr *> mrs;
|
||||
auto bs = memsize / FLAGS_reg_split;
|
||||
XLOGF(INFO, "reg begin");
|
||||
for (size_t i = 0; i < FLAGS_reg_split; i++) {
|
||||
auto mr = ibv_reg_mr(pd, mem + bs * i, bs, regFlags());
|
||||
XLOGF(INFO, "reg get {}", (void *)mr);
|
||||
ASSERT_NE(mr, nullptr) << memsize;
|
||||
mrs.push_back(mr);
|
||||
}
|
||||
|
||||
XLOGF(INFO, "dereg begin");
|
||||
for (auto mr : mrs) {
|
||||
ibv_dereg_mr(mr);
|
||||
}
|
||||
XLOGF(INFO, "dereg finish");
|
||||
}
|
||||
|
||||
// on CI machine with virtualized RDMA NIC, this test will fail.
|
||||
TEST_F(TestIBDevice, DISABLED_reregMR) {
|
||||
static constexpr size_t kMemFlags = IBV_ACCESS_LOCAL_WRITE | IBV_ACCESS_REMOTE_READ | IBV_ACCESS_REMOTE_WRITE;
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
void *mem = malloc(4 << 10);
|
||||
auto guard = folly::makeGuard([&]() { free(mem); });
|
||||
|
||||
auto mr1 = IBDevice::get(0)->regMemory(mem, 4 << 10, kMemFlags);
|
||||
auto rkey1 = mr1->rkey;
|
||||
ASSERT_EQ(IBDevice::get(0)->deregMemory(mr1), 0);
|
||||
auto mr2 = IBDevice::get(0)->regMemory(mem, 4 << 10, kMemFlags);
|
||||
ASSERT_NE(rkey1, mr2->rkey);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net
|
||||
105
tests/common/net/ib/TestIBNotInitialized.cc
Normal file
105
tests/common/net/ib/TestIBNotInitialized.cc
Normal file
@@ -0,0 +1,105 @@
|
||||
#include <chrono>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/Listener.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/net/WriteItem.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/net/sync/Client.h"
|
||||
#include "common/serde/ClientContext.h"
|
||||
#include "common/utils/Address.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "gtest/gtest.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/common/net/Echo.h"
|
||||
|
||||
namespace hf3fs::net {
|
||||
using namespace test;
|
||||
|
||||
class TestIBNotInitialized : public ::testing::Test {
|
||||
protected:
|
||||
void SetUp() override { IBManager::stop(); }
|
||||
};
|
||||
|
||||
TEST_F(TestIBNotInitialized, Basic) {
|
||||
ASSERT_FALSE(IBManager::initialized());
|
||||
ASSERT_TRUE(IBDevice::all().empty());
|
||||
}
|
||||
|
||||
class EchoServiceImpl : public serde::ServiceWrapper<EchoServiceImpl, Echo> {
|
||||
public:
|
||||
CoTryTask<EchoRsp> echo(serde::CallContext &ctx, const EchoReq &req) {
|
||||
EchoRsp rsp;
|
||||
rsp.val = req.val;
|
||||
co_return rsp;
|
||||
}
|
||||
|
||||
CoTryTask<HelloRsp> hello(serde::CallContext &ctx, const HelloReq &req) {
|
||||
co_return makeError(StatusCode::kNotImplemented);
|
||||
}
|
||||
|
||||
CoTryTask<HelloRsp> fail(serde::CallContext &ctx, const HelloReq &) {
|
||||
co_return makeError(StatusCode::kNotImplemented);
|
||||
}
|
||||
};
|
||||
|
||||
TEST_F(TestIBNotInitialized, TCP) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(Address::TCP);
|
||||
Server server{serverConfig};
|
||||
ASSERT_TRUE(server.setup());
|
||||
server.addSerdeService(std::make_unique<EchoServiceImpl>());
|
||||
ASSERT_TRUE(server.start());
|
||||
|
||||
Client::Config clientConfig;
|
||||
Client client{clientConfig};
|
||||
client.start();
|
||||
|
||||
auto ctx = client.serdeCtx(server.groups().front()->addressList().front());
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(result->val, "foo");
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestIBNotInitialized, RDMAServer) {
|
||||
Server::Config serverConfig;
|
||||
serverConfig.groups(0).set_network_type(Address::RDMA);
|
||||
Server server{serverConfig};
|
||||
ASSERT_OK(server.setup());
|
||||
ASSERT_ERROR(server.start(), RPCCode::kIBDeviceNotInitialized);
|
||||
}
|
||||
|
||||
TEST_F(TestIBNotInitialized, RDMAClient) {
|
||||
Client::Config clientConfig;
|
||||
Client client{clientConfig};
|
||||
client.start();
|
||||
|
||||
auto ctx = client.serdeCtx(Address::fromString("rdma://127.0.0.1:8000"));
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
Echo<> client;
|
||||
EchoReq req;
|
||||
req.val = "foo";
|
||||
auto result = co_await client.echo(ctx, req);
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
CO_ASSERT_EQ(result.error().code(), RPCCode::kSendFailed);
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net
|
||||
817
tests/common/net/ib/TestIBSocket.cc
Normal file
817
tests/common/net/ib/TestIBSocket.cc
Normal file
@@ -0,0 +1,817 @@
|
||||
#include <algorithm>
|
||||
#include <atomic>
|
||||
#include <bits/types/struct_iovec.h>
|
||||
#include <boost/thread/barrier.hpp>
|
||||
#include <chrono>
|
||||
#include <cstdint>
|
||||
#include <cstdlib>
|
||||
#include <fmt/core.h>
|
||||
#include <folly/IPAddress.h>
|
||||
#include <folly/IPAddressV4.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/Range.h>
|
||||
#include <folly/ScopeGuard.h>
|
||||
#include <folly/SocketAddress.h>
|
||||
#include <folly/Synchronized.h>
|
||||
#include <folly/Utility.h>
|
||||
#include <folly/experimental/coro/Baton.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/CurrentExecutor.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/detail/Types.h>
|
||||
#include <folly/hash/Checksum.h>
|
||||
#include <folly/io/async/AsyncServerSocket.h>
|
||||
#include <folly/io/async/EventBase.h>
|
||||
#include <folly/io/coro/ServerSocket.h>
|
||||
#include <folly/io/coro/Transport.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <folly/net/NetworkSocket.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <infiniband/verbs.h>
|
||||
#include <limits>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <optional>
|
||||
#include <pthread.h>
|
||||
#include <queue>
|
||||
#include <sys/poll.h>
|
||||
#include <sys/select.h>
|
||||
#include <sys/socket.h>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "SetupIB.h"
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/MessageHeader.h"
|
||||
#include "common/net/Network.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/net/WriteItem.h"
|
||||
#include "common/net/ib/IBConnect.h"
|
||||
#include "common/net/ib/IBConnectService.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/net/ib/IBSocket.h"
|
||||
#include "common/net/ib/RDMABuf.h"
|
||||
#include "common/serde/ClientContext.h"
|
||||
#include "common/utils/Address.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Duration.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
|
||||
namespace hf3fs::net::test {
|
||||
|
||||
// DEFINE_uint32(gid_index, 0, "RoCE gid index.");
|
||||
DEFINE_bool(ib_wait_fd, true, "IBSocket wait on fd instead of busy polling.");
|
||||
DEFINE_uint64(ib_bench_mb, (4 << 10), "IBSocket benchmark send data size (mb).");
|
||||
DEFINE_uint32(ib_bench_buf_kb, 0, "IBSocket benchmark send buf size");
|
||||
DEFINE_uint32(ib_bench_buf_cnt, 0, "IBSocket benchmark send buf cnt");
|
||||
DEFINE_uint64(ib_pingpong_loops, 10000, "IBSocket pingpong loops.");
|
||||
DEFINE_uint64(ib_pingpong_size, 64, "IBSocket pingpong msg size (bytes).");
|
||||
DEFINE_uint64(ib_rdma_iters, 500, "IBSocket RDMA test iters.");
|
||||
|
||||
struct TestUtils {
|
||||
static IBSocket::Config testCfg() {
|
||||
IBSocket::Config cfg;
|
||||
cfg.set_retry_cnt(0);
|
||||
cfg.set_buf_size(512);
|
||||
cfg.set_send_buf_cnt(32);
|
||||
cfg.set_buf_ack_batch(4);
|
||||
cfg.set_buf_signal_batch(4);
|
||||
cfg.set_max_rdma_wr(7);
|
||||
cfg.set_max_rdma_wr_per_post(5);
|
||||
cfg.set_max_sge(3);
|
||||
return cfg;
|
||||
}
|
||||
|
||||
static std::vector<std::jthread> poll(std::vector<IBSocket *> sockets, std::atomic<bool> &stop) {
|
||||
std::vector<std::jthread> threads;
|
||||
threads.reserve(sockets.size());
|
||||
for (auto socket : sockets) {
|
||||
threads.emplace_back([&stop, socket]() {
|
||||
while (!stop) {
|
||||
TestUtils::wait(*socket, 0, 100ms);
|
||||
}
|
||||
});
|
||||
}
|
||||
return threads;
|
||||
}
|
||||
|
||||
static Result<Socket::Events> wait(IBSocket &socket, Socket::Events events, std::chrono::milliseconds timeout) {
|
||||
while (true) {
|
||||
if (FLAGS_ib_wait_fd && timeout.count() != 0) {
|
||||
pollfd fd{
|
||||
.fd = socket.fd(),
|
||||
.events = POLL_IN,
|
||||
.revents = 0,
|
||||
};
|
||||
int ret = ::poll(&fd, 1, timeout.count());
|
||||
if (ret < 0 || (ret == 0 && timeout.count())) {
|
||||
return makeError(RPCCode::kTimeout, fmt::format("poll ret is {}", ret));
|
||||
}
|
||||
}
|
||||
|
||||
auto result = socket.poll(0);
|
||||
if (result.hasError()) {
|
||||
return makeError(std::move(result.error()));
|
||||
}
|
||||
if (events == 0 || (result.value() & events) != 0) {
|
||||
return result.value();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static Result<Void> send(IBSocket &socket, folly::ByteRange buf, bool poll = true) {
|
||||
while (!buf.empty()) {
|
||||
// try send first
|
||||
auto sendResult = socket.send(buf);
|
||||
RETURN_ON_ERROR(sendResult);
|
||||
buf.advance(sendResult.value());
|
||||
|
||||
if (buf.empty()) {
|
||||
break;
|
||||
}
|
||||
|
||||
// wait writable
|
||||
if (poll) {
|
||||
auto waitResult = wait(socket, Socket::kEventWritableFlag, 1000ms);
|
||||
RETURN_ON_ERROR(waitResult);
|
||||
}
|
||||
}
|
||||
|
||||
return socket.flush();
|
||||
}
|
||||
|
||||
static Result<Void> send(IBSocket &socket, iovec *iovs, int iovCnts, bool poll) {
|
||||
for (int i = 0; i < iovCnts; i++) {
|
||||
auto result = send(socket, folly::ByteRange((uint8_t *)iovs[i].iov_base, iovs[i].iov_len), poll);
|
||||
RETURN_ON_ERROR(result);
|
||||
}
|
||||
|
||||
return Void{};
|
||||
}
|
||||
|
||||
static Result<Void> recv(IBSocket &socket, folly::MutableByteRange buf, bool poll = true) {
|
||||
if (buf.empty()) {
|
||||
return Void{};
|
||||
}
|
||||
while (true) {
|
||||
auto recvResult = socket.recv(buf);
|
||||
RETURN_ON_ERROR(recvResult);
|
||||
buf.advance(recvResult.value());
|
||||
|
||||
if (buf.empty()) {
|
||||
break;
|
||||
}
|
||||
// wait readable
|
||||
if (poll) {
|
||||
auto waitResult = wait(socket, Socket::kEventReadableFlag, 1000ms);
|
||||
RETURN_ON_ERROR(waitResult);
|
||||
}
|
||||
}
|
||||
|
||||
return Void{};
|
||||
}
|
||||
};
|
||||
|
||||
class TestMsg {
|
||||
public:
|
||||
static Result<Void> send(IBSocket &socket, size_t bytes, bool poll = true) {
|
||||
TestMsg msg;
|
||||
msg.bytes_ = bytes;
|
||||
msg.data_.resize(bytes);
|
||||
for (size_t i = 0; i < bytes; i++) {
|
||||
msg.data_[i] = (uint8_t)folly::Random::rand32();
|
||||
}
|
||||
msg.crc32c_ = folly::crc32c(msg.data_.data(), msg.data_.size(), 0);
|
||||
|
||||
iovec iovs[] = {
|
||||
{.iov_base = &msg.bytes_, .iov_len = sizeof(msg.bytes_)},
|
||||
{.iov_base = &msg.crc32c_, .iov_len = sizeof(msg.crc32c_)},
|
||||
{.iov_base = msg.data_.data(), .iov_len = msg.data_.size()},
|
||||
};
|
||||
|
||||
return TestUtils::send(socket, iovs, sizeof(iovs) / sizeof(iovs[0]), poll);
|
||||
}
|
||||
|
||||
static Result<Void> recv(IBSocket &socket, bool poll = true) {
|
||||
TestMsg msg;
|
||||
auto result =
|
||||
TestUtils::recv(socket,
|
||||
folly::MutableByteRange((uint8_t *)&msg.bytes_, sizeof(msg.bytes_) + sizeof(msg.crc32c_)),
|
||||
poll);
|
||||
RETURN_ON_ERROR(result);
|
||||
msg.data_.resize(msg.bytes_);
|
||||
result = TestUtils::recv(socket, folly::MutableByteRange(msg.data_.data(), msg.data_.size()), poll);
|
||||
RETURN_ON_ERROR(result);
|
||||
|
||||
uint32_t crc32c = folly::crc32c(msg.data_.data(), msg.data_.size(), 0);
|
||||
EXPECT_EQ(msg.crc32c_, crc32c);
|
||||
if (msg.crc32c_ != crc32c) {
|
||||
return makeError(StatusCode::kDataCorruption);
|
||||
}
|
||||
return Void{};
|
||||
}
|
||||
|
||||
private:
|
||||
uint32_t bytes_ = 0;
|
||||
uint32_t crc32c_ = 0;
|
||||
std::vector<uint8_t> data_;
|
||||
};
|
||||
|
||||
class TestIBSocket : public SetupIB {
|
||||
public:
|
||||
auto &group() { return *server_.groups().front(); }
|
||||
const auto &group() const { return *server_.groups().front(); }
|
||||
Address serverAddr() const { return group().addressList().front(); }
|
||||
|
||||
void SetUp() override {
|
||||
ASSERT_TRUE(server_.setup());
|
||||
auto accept = [&](auto socket) { accepted_.lock()->push(std::move(socket)); };
|
||||
auto service = std::make_unique<IBConnectService>(serverConfig_.groups(0).io_worker().ibsocket(), accept, []() {
|
||||
return 5_s;
|
||||
});
|
||||
group().addSerdeService(std::move(service), Address::Type::TCP);
|
||||
ASSERT_TRUE(server_.start());
|
||||
client_.start();
|
||||
}
|
||||
|
||||
void TearDown() override {
|
||||
client_.stopAndJoin();
|
||||
server_.stopAndJoin();
|
||||
}
|
||||
|
||||
CoTryTask<std::pair<std::unique_ptr<IBSocket>, std::unique_ptr<IBSocket>>> connect(
|
||||
IBSocket::Config &cfg,
|
||||
std::chrono::milliseconds timeout = 220ms) {
|
||||
// cfg.set_gid_index(FLAGS_gid_index);
|
||||
auto addr = serverAddr();
|
||||
Address tcpAddr(addr.ip, addr.port, Address::TCP);
|
||||
auto ctx = client_.serdeCtx(tcpAddr);
|
||||
|
||||
auto clientSocket = std::make_unique<IBSocket>(cfg);
|
||||
auto result = co_await clientSocket->connect(ctx, Duration{timeout});
|
||||
CO_RETURN_ON_ERROR(result);
|
||||
|
||||
auto queue = accepted_.lock();
|
||||
EXPECT_EQ(queue->size(), 1);
|
||||
auto serverSocket = std::move(queue->front());
|
||||
queue->pop();
|
||||
queue.unlock();
|
||||
|
||||
EXPECT_FALSE(serverSocket->check().hasError());
|
||||
EXPECT_FALSE(clientSocket->check().hasError());
|
||||
|
||||
auto serverReady = TestUtils::wait(*serverSocket, Socket::kEventWritableFlag, 500ms);
|
||||
auto clientReady = TestUtils::wait(*clientSocket, Socket::kEventWritableFlag, 500ms);
|
||||
EXPECT_FALSE(serverReady.hasError()) << serverReady.error().describe();
|
||||
EXPECT_FALSE(clientReady.hasError()) << serverReady.error().describe();
|
||||
CO_RETURN_ON_ERROR(serverReady);
|
||||
CO_RETURN_ON_ERROR(clientReady);
|
||||
|
||||
co_return std::pair(std::move(serverSocket), std::move(clientSocket));
|
||||
}
|
||||
|
||||
Result<std::pair<std::unique_ptr<IBSocket>, std::unique_ptr<IBSocket>>> connectSync(
|
||||
IBSocket::Config &cfg,
|
||||
std::chrono::milliseconds timeout = 100ms) {
|
||||
return folly::coro::blockingWait(connect(cfg, timeout));
|
||||
}
|
||||
|
||||
protected:
|
||||
Client::Config clientConfig_;
|
||||
Client client_{clientConfig_};
|
||||
|
||||
Server::Config serverConfig_;
|
||||
bool initServerConfig = [this] {
|
||||
serverConfig_.groups(0).set_network_type(Address::TCP);
|
||||
serverConfig_.groups(0).listener().set_reuse_port(true);
|
||||
return true;
|
||||
}();
|
||||
Server server_{serverConfig_};
|
||||
folly::Synchronized<std::queue<std::unique_ptr<IBSocket>>, std::mutex> accepted_;
|
||||
};
|
||||
|
||||
TEST_F(TestIBSocket, InvalidConfig) {
|
||||
auto dev = IBDevice::get(0);
|
||||
ASSERT_NE(dev, nullptr);
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
IBSocket::Config cfg;
|
||||
cfg.set_max_rd_atomic(dev->attr().max_qp_rd_atom + 1);
|
||||
|
||||
auto result = co_await connect(cfg);
|
||||
CO_ASSERT_ERROR(result, StatusCode::kInvalidConfig);
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, ConnectTimeout) {
|
||||
server_.stopAndJoin();
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = co_await connect(config);
|
||||
CO_ASSERT_ERROR(result, RPCCode::kSendFailed);
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, Connect) {
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = co_await connect(config);
|
||||
CO_ASSERT_OK(result);
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, ClientToServer) {
|
||||
static constexpr int msgs = 100;
|
||||
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
|
||||
std::jthread server([socket = std::move(result->first)]() {
|
||||
while (true) {
|
||||
auto msg = TestMsg::recv(*socket);
|
||||
if (msg.hasError()) {
|
||||
ASSERT_ERROR(msg, RPCCode::kSocketClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
std::jthread client([socket = std::move(result->second)]() mutable {
|
||||
for (int i = 0; i < msgs; i++) {
|
||||
ASSERT_OK(TestMsg::send(*socket, folly::Random::rand32(4, 2 << 20)));
|
||||
}
|
||||
IBManager::close(std::move(socket));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, ServerToClient) {
|
||||
static constexpr int msgs = 100;
|
||||
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
|
||||
std::jthread server([socket = std::move(result->first)]() mutable {
|
||||
for (int i = 0; i < msgs; i++) {
|
||||
ASSERT_OK(TestMsg::send(*socket, folly::Random::rand32(4, 2 << 20)));
|
||||
}
|
||||
IBManager::close(std::move(socket));
|
||||
});
|
||||
std::jthread clntTh([socket = std::move(result->second)]() {
|
||||
for ([[maybe_unused]] int i = 0; true; i++) {
|
||||
auto msg = TestMsg::recv(*socket);
|
||||
if (msg.hasError()) {
|
||||
ASSERT_ERROR(msg, RPCCode::kSocketClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, BIDirection) {
|
||||
static constexpr int msgs = 100;
|
||||
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
SCOPE_EXIT {
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
};
|
||||
|
||||
std::atomic<uint32_t> cnt(0);
|
||||
auto send = [&](IBSocket &socket) {
|
||||
SCOPE_EXIT { cnt++; };
|
||||
for (int i = 0; i < msgs; i++) {
|
||||
ASSERT_OK(TestMsg::send(socket, folly::Random::rand32(4, 2 << 20), false));
|
||||
}
|
||||
};
|
||||
auto recv = [&](IBSocket &socket) {
|
||||
SCOPE_EXIT { cnt++; };
|
||||
for (int i = 0; i < msgs; i++) {
|
||||
ASSERT_OK(TestMsg::recv(socket, false));
|
||||
}
|
||||
};
|
||||
auto poll = [&](IBSocket &socket) {
|
||||
while (cnt < 4) {
|
||||
auto result = socket.poll(0);
|
||||
if (result.hasError()) {
|
||||
ASSERT_ERROR(result, RPCCode::kSocketClosed);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::jthread server_send([&]() { send(*result->first); });
|
||||
std::jthread server_recv([&]() { recv(*result->first); });
|
||||
std::jthread server_poll([&]() { poll(*result->first); });
|
||||
std::jthread client_send([&]() { send(*result->second); });
|
||||
std::jthread client_recv([&]() { recv(*result->second); });
|
||||
std::jthread client_poll([&]() { poll(*result->second); });
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, BIDirectionFaultInjection) {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
SCOPE_EXIT {
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
};
|
||||
|
||||
std::atomic<uint32_t> cnt(0);
|
||||
|
||||
auto inject = [&](IBSocket &socket) {
|
||||
SCOPE_EXIT { cnt++; };
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
// try to inject a error by post a invalid RDMA operation, shouldn't receive corruption message.
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(1000));
|
||||
// underlying RDMABuf is deregistered immediately, so remote buffer is invalid
|
||||
auto remote = RDMABuf::allocate(1024).toRemoteBuf();
|
||||
auto local = RDMABuf::allocate(1024);
|
||||
auto batch = socket.rdmaWriteBatch();
|
||||
batch.add(remote, local);
|
||||
auto result = co_await batch.post();
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
}());
|
||||
};
|
||||
auto send = [&](IBSocket &socket) {
|
||||
SCOPE_EXIT { cnt++; };
|
||||
while (true) {
|
||||
auto result = TestMsg::send(socket, folly::Random::rand32(4, 2 << 20), false);
|
||||
if (result.hasError()) {
|
||||
ASSERT_ERROR(result, RPCCode::kSocketError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
auto recv = [&](IBSocket &socket) {
|
||||
SCOPE_EXIT { cnt++; };
|
||||
while (true) {
|
||||
auto result = TestMsg::recv(socket, false);
|
||||
if (result.hasError()) {
|
||||
ASSERT_ERROR(result, RPCCode::kSocketError);
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
auto poll = [&](IBSocket &socket) {
|
||||
while (cnt < 5) {
|
||||
auto result = socket.poll(0);
|
||||
if (result.hasError()) {
|
||||
bool closed = result.error().code() == RPCCode::kSocketClosed;
|
||||
bool error = result.error().code() == RPCCode::kSocketError;
|
||||
ASSERT_TRUE(closed || error) << result.error().describe();
|
||||
break;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
std::jthread inject_th([&]() { inject(*result->first); });
|
||||
std::jthread server_send([&]() { send(*result->first); });
|
||||
std::jthread server_recv([&]() { recv(*result->first); });
|
||||
std::jthread server_poll([&]() { poll(*result->first); });
|
||||
std::jthread client_send([&]() { send(*result->second); });
|
||||
std::jthread client_recv([&]() { recv(*result->second); });
|
||||
std::jthread client_poll([&]() { poll(*result->second); });
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, Benchmark) {
|
||||
size_t benchSize = FLAGS_ib_bench_mb * (1ULL << 20);
|
||||
auto config = IBSocket::Config();
|
||||
if (FLAGS_ib_bench_buf_kb) config.set_buf_size(FLAGS_ib_bench_buf_kb * 1024);
|
||||
if (FLAGS_ib_bench_buf_cnt) config.set_send_buf_cnt(FLAGS_ib_bench_buf_cnt);
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
|
||||
UtcTime begin = UtcClock::now();
|
||||
std::jthread server([benchSize, socket = std::move(result->first)]() {
|
||||
std::vector<uint8_t> buf(4 << 20, 0);
|
||||
size_t recved = 0;
|
||||
while (recved < benchSize) {
|
||||
size_t rsize = std::min(buf.size(), benchSize - recved);
|
||||
auto result = TestUtils::recv(*socket, folly::MutableByteRange(buf.data(), rsize));
|
||||
ASSERT_OK(result);
|
||||
recved += rsize;
|
||||
}
|
||||
});
|
||||
std::jthread client([benchSize, socket = std::move(result->second)]() {
|
||||
std::vector<uint8_t> buf(4 << 20, 0);
|
||||
size_t sended = 0;
|
||||
while (sended < benchSize) {
|
||||
size_t wsize = std::min(buf.size(), benchSize - sended);
|
||||
auto result = TestUtils::send(*socket, folly::ByteRange(buf.data(), wsize));
|
||||
ASSERT_OK(result);
|
||||
sended += wsize;
|
||||
}
|
||||
});
|
||||
server.join();
|
||||
client.join();
|
||||
|
||||
auto dur = UtcClock::now() - begin;
|
||||
std::cout << "IBSocket benchmark bandwidth: "
|
||||
<< ((double)FLAGS_ib_bench_mb / std::chrono::duration_cast<std::chrono::milliseconds>(dur).count() * 1000.0)
|
||||
<< " MB/s" << std::endl;
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, PingPong) {
|
||||
size_t kLoops = FLAGS_ib_pingpong_loops;
|
||||
size_t kMsgSize = FLAGS_ib_pingpong_size;
|
||||
|
||||
auto config = IBSocket::Config();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
UtcTime begin = UtcClock::now();
|
||||
std::jthread server([kMsgSize, kLoops, socket = std::move(result->first)]() {
|
||||
std::vector<uint8_t> buf(kMsgSize, 0);
|
||||
for (size_t i = 0; i < kLoops; i++) {
|
||||
ASSERT_OK(TestUtils::recv(*socket, folly::MutableByteRange(buf.data(), kMsgSize)));
|
||||
ASSERT_OK(TestUtils::send(*socket, folly::ByteRange(buf.data(), kMsgSize)));
|
||||
}
|
||||
});
|
||||
std::jthread client([kMsgSize, kLoops, socket = std::move(result->second)]() {
|
||||
std::vector<uint8_t> buf(kMsgSize, 0);
|
||||
for (size_t i = 0; i < kLoops; i++) {
|
||||
ASSERT_OK(TestUtils::send(*socket, folly::ByteRange(buf.data(), kMsgSize)));
|
||||
ASSERT_OK(TestUtils::recv(*socket, folly::MutableByteRange(buf.data(), kMsgSize)));
|
||||
}
|
||||
});
|
||||
server.join();
|
||||
client.join();
|
||||
|
||||
auto dur = UtcClock::now() - begin;
|
||||
std::cout << "IBSocket pingpong latency: "
|
||||
<< std::chrono::duration_cast<std::chrono::nanoseconds>(dur).count() / 1000.0 / kLoops << "us" << std::endl;
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, RDMAWrite) {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
SCOPE_EXIT {
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
};
|
||||
|
||||
std::atomic<bool> stop = false;
|
||||
auto jthreads = TestUtils::poll({result->first.get(), result->second.get()}, stop);
|
||||
|
||||
folly::coro::blockingWait([&, &socket = *result->first]() -> CoTask<void> {
|
||||
auto pool = RDMABufPool::create(4 << 10, 10);
|
||||
for (size_t i = 0; i < FLAGS_ib_rdma_iters; i++) {
|
||||
XLOGF_IF(INFO, i % 100 == 0, "Run {}", i);
|
||||
// RDMABufPool
|
||||
auto src = co_await pool->allocate();
|
||||
auto dst = co_await pool->allocate();
|
||||
CO_ASSERT_TRUE(src);
|
||||
CO_ASSERT_TRUE(dst);
|
||||
|
||||
for (size_t i = 0; i < src.size(); i++) {
|
||||
((uint8_t *)src.ptr())[i] = folly::Random::rand32();
|
||||
}
|
||||
auto crc32c = folly::crc32c((uint8_t *)src.ptr(), src.size());
|
||||
|
||||
auto dstRemote = dst.toRemoteBuf();
|
||||
CO_ASSERT_TRUE(dstRemote);
|
||||
|
||||
auto result = co_await socket.rdmaWrite(dstRemote, src);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(folly::crc32c((uint8_t *)dst.ptr(), dst.size()), crc32c);
|
||||
}
|
||||
|
||||
auto remotePool = RDMABufPool::create(4096 * 16, -1);
|
||||
auto localPool = RDMABufPool::create(4096, -1);
|
||||
for (size_t i = 0; i < FLAGS_ib_rdma_iters; i++) {
|
||||
XLOGF_IF(INFO, i % 100 == 0, "Run {}", i);
|
||||
std::vector<RDMABuf> remotes;
|
||||
std::vector<RDMABuf> locals;
|
||||
auto batch = socket.rdmaWriteBatch();
|
||||
int numReqs = folly::Random::rand32(1, 64);
|
||||
for (int i = 0; i < numReqs; i++) {
|
||||
size_t total = 0;
|
||||
int numLocalBufs = folly::Random::rand32(1, 16);
|
||||
for (int j = 0; j < numLocalBufs; j++) {
|
||||
auto size = folly::Random::rand32(4, 4096);
|
||||
auto local = co_await localPool->allocate();
|
||||
local = local.first(size);
|
||||
CO_ASSERT_TRUE(local);
|
||||
locals.push_back(local);
|
||||
total += size;
|
||||
}
|
||||
auto remote = co_await remotePool->allocate();
|
||||
remote = remote.first(total);
|
||||
CO_ASSERT_TRUE(remote);
|
||||
remotes.push_back(remote);
|
||||
|
||||
batch.add(remote.toRemoteBuf(), std::span(locals).last(numLocalBufs));
|
||||
}
|
||||
|
||||
auto crc32c = 0;
|
||||
for (auto buf : locals) {
|
||||
for (size_t i = 0; i < buf.size(); i++) {
|
||||
((uint8_t *)buf.ptr())[i] = folly::Random::rand32();
|
||||
}
|
||||
crc32c = folly::crc32c((uint8_t *)buf.ptr(), buf.size(), crc32c);
|
||||
}
|
||||
|
||||
auto result = co_await batch.post();
|
||||
CO_ASSERT_OK(result);
|
||||
|
||||
auto crc32cOut = 0;
|
||||
for (auto buf : remotes) {
|
||||
crc32cOut = folly::crc32c((uint8_t *)buf.ptr(), buf.size(), crc32cOut);
|
||||
}
|
||||
CO_ASSERT_EQ(crc32c, crc32cOut);
|
||||
}
|
||||
|
||||
stop = true;
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, RDMARead) {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
SCOPE_EXIT {
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
};
|
||||
|
||||
std::atomic<bool> stop = false;
|
||||
auto jthreads = TestUtils::poll({result->first.get(), result->second.get()}, stop);
|
||||
|
||||
folly::coro::blockingWait([&, &socket = *result->first]() -> CoTask<void> {
|
||||
auto pool = RDMABufPool::create(4 << 10, 10);
|
||||
for (size_t i = 0; i < FLAGS_ib_rdma_iters; i++) {
|
||||
XLOGF_IF(INFO, i % 100 == 0, "Run {}", i);
|
||||
// RDMABufPool
|
||||
auto src = co_await pool->allocate();
|
||||
auto dst = co_await pool->allocate();
|
||||
CO_ASSERT_TRUE(src);
|
||||
CO_ASSERT_TRUE(dst);
|
||||
|
||||
for (size_t i = 0; i < src.size(); i++) {
|
||||
((uint8_t *)src.ptr())[i] = folly::Random::rand32();
|
||||
}
|
||||
auto crc32c = folly::crc32c((uint8_t *)src.ptr(), src.size());
|
||||
|
||||
auto srcRemote = src.toRemoteBuf();
|
||||
CO_ASSERT_TRUE(srcRemote);
|
||||
|
||||
auto result = co_await socket.rdmaRead(srcRemote, dst);
|
||||
CO_ASSERT_OK(result);
|
||||
CO_ASSERT_EQ(folly::crc32c((uint8_t *)dst.ptr(), dst.size()), crc32c);
|
||||
}
|
||||
|
||||
auto remotePool = RDMABufPool::create(4096 * 16, -1);
|
||||
auto localPool = RDMABufPool::create(4096, -1);
|
||||
for (size_t i = 0; i < FLAGS_ib_rdma_iters; i++) {
|
||||
XLOGF_IF(INFO, i % 100 == 0, "Run {}", i);
|
||||
std::vector<RDMABuf> remotes;
|
||||
std::vector<RDMABuf> locals;
|
||||
auto batch = socket.rdmaReadBatch();
|
||||
int numReqs = folly::Random::rand32(1, 64);
|
||||
for (int i = 0; i < numReqs; i++) {
|
||||
size_t total = 0;
|
||||
int numLocalBufs = folly::Random::rand32(1, 16);
|
||||
for (int j = 0; j < numLocalBufs; j++) {
|
||||
auto size = folly::Random::rand32(4, 4 << 10);
|
||||
auto local = co_await localPool->allocate();
|
||||
local = local.first(size);
|
||||
CO_ASSERT_TRUE(local);
|
||||
locals.push_back(local);
|
||||
total += size;
|
||||
}
|
||||
auto remote = co_await remotePool->allocate();
|
||||
remote = remote.first(total);
|
||||
CO_ASSERT_TRUE(remote);
|
||||
remotes.push_back(remote);
|
||||
|
||||
batch.add(remote.toRemoteBuf(), std::span(locals).last(numLocalBufs));
|
||||
}
|
||||
|
||||
auto crc32c = 0;
|
||||
for (auto buf : remotes) {
|
||||
for (size_t i = 0; i < buf.size(); i++) {
|
||||
((uint8_t *)buf.ptr())[i] = folly::Random::rand32();
|
||||
}
|
||||
crc32c = folly::crc32c((uint8_t *)buf.ptr(), buf.size(), crc32c);
|
||||
}
|
||||
|
||||
auto result = co_await batch.post();
|
||||
CO_ASSERT_OK(result);
|
||||
|
||||
auto crc32cOut = 0;
|
||||
for (auto buf : locals) {
|
||||
crc32cOut = folly::crc32c((uint8_t *)buf.ptr(), buf.size(), crc32cOut);
|
||||
}
|
||||
CO_ASSERT_EQ(crc32c, crc32cOut);
|
||||
}
|
||||
|
||||
stop = true;
|
||||
}());
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, RemoteClosed) {
|
||||
auto config = IBSocket::Config();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
|
||||
boost::barrier barrier(2);
|
||||
std::jthread server([socket = std::move(result->first)]() mutable {
|
||||
ASSERT_OK(TestMsg::send(*socket, 4 << 20));
|
||||
IBManager::close(std::move(socket));
|
||||
});
|
||||
std::jthread client([socket = std::move(result->second)]() mutable {
|
||||
ASSERT_OK(TestMsg::recv(*socket));
|
||||
ASSERT_ERROR(TestMsg::recv(*socket), RPCCode::kSocketClosed);
|
||||
ASSERT_ERROR(TestMsg::send(*socket, 1 << 20), RPCCode::kSocketClosed);
|
||||
ASSERT_ERROR(TestMsg::send(*socket, 1 << 20), RPCCode::kSocketClosed);
|
||||
IBManager::close(std::move(socket));
|
||||
});
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, RDMAFailure) {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto result = connectSync(config);
|
||||
ASSERT_OK(result);
|
||||
SCOPE_EXIT {
|
||||
IBManager::close(std::move(result->first));
|
||||
IBManager::close(std::move(result->second));
|
||||
};
|
||||
|
||||
// connection is OK
|
||||
{
|
||||
auto &client = *result->first;
|
||||
auto deadline = RelativeTime::now() + 5_s;
|
||||
while (RelativeTime::now() < deadline) {
|
||||
ASSERT_OK(client.check());
|
||||
auto result = client.poll(0);
|
||||
ASSERT_OK(result);
|
||||
}
|
||||
}
|
||||
|
||||
// post invalid RDMA request on server side to make
|
||||
std::atomic<bool> stop = false;
|
||||
auto jthreads = TestUtils::poll({result->second.get()}, stop);
|
||||
folly::coro::blockingWait([&, &server = *result->second]() -> CoTask<void> {
|
||||
// post RDMA with invalid rkey
|
||||
// underlying RDMABuf is deregistered immediately, so remote buffer is invalid
|
||||
auto remote = RDMABuf::allocate(1024).toRemoteBuf();
|
||||
auto local = RDMABuf::allocate(1024);
|
||||
for (int i = 0; i < 2; i++) {
|
||||
auto batch = server.rdmaWriteBatch();
|
||||
batch.add(remote, local);
|
||||
batch.add(remote, local);
|
||||
batch.add(remote, local);
|
||||
batch.add(remote, local);
|
||||
auto result = co_await batch.post();
|
||||
CO_ASSERT_TRUE(result.hasError());
|
||||
std::cout << result.error().describe() << std::endl;
|
||||
}
|
||||
stop = true;
|
||||
}());
|
||||
|
||||
// connection is broken, send check message on client side
|
||||
auto &client = *result->first;
|
||||
auto deadline = RelativeTime::now() + 10_s;
|
||||
auto error = false;
|
||||
while (RelativeTime::now() < deadline) {
|
||||
ASSERT_OK(client.check());
|
||||
auto result = client.poll(0);
|
||||
if (result.hasError()) {
|
||||
error = true;
|
||||
fmt::print("{}\n", result.error());
|
||||
break;
|
||||
} else {
|
||||
fmt::print(".");
|
||||
}
|
||||
std::this_thread::sleep_for(1_s);
|
||||
}
|
||||
ASSERT_TRUE(error) << "client side doesn't get error after send check message";
|
||||
}
|
||||
|
||||
TEST_F(TestIBSocket, CloseBeforeConnect) {
|
||||
auto config = TestUtils::testCfg();
|
||||
auto addr = serverAddr();
|
||||
Address tcpAddr(addr.ip, addr.port, Address::TCP);
|
||||
auto ctx = client_.serdeCtx(tcpAddr);
|
||||
|
||||
auto socket = std::make_unique<IBSocket>(config);
|
||||
folly::coro::blockingWait(socket->close());
|
||||
ASSERT_ERROR(folly::coro::blockingWait(socket->connect(ctx, 1_s)), RPCCode::kSocketClosed);
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net::test
|
||||
405
tests/common/net/ib/TestRDMA.cc
Normal file
405
tests/common/net/ib/TestRDMA.cc
Normal file
@@ -0,0 +1,405 @@
|
||||
#include <algorithm>
|
||||
#include <chrono>
|
||||
#include <folly/Executor.h>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/ScopeGuard.h>
|
||||
#include <folly/executors/CPUThreadPoolExecutor.h>
|
||||
#include <folly/executors/IOThreadPoolExecutor.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/Collect.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <folly/experimental/coro/Invoke.h>
|
||||
#include <folly/experimental/coro/Sleep.h>
|
||||
#include <folly/futures/Future.h>
|
||||
#include <folly/logging/LogConfig.h>
|
||||
#include <folly/logging/LogConfigParser.h>
|
||||
#include <folly/logging/Logger.h>
|
||||
#include <folly/logging/LoggerDB.h>
|
||||
#include <folly/logging/ObjectToString.h>
|
||||
#include <folly/logging/xlog.h>
|
||||
#include <gflags/gflags.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <memory>
|
||||
#include <random>
|
||||
#include <thread>
|
||||
#include <tuple>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "common/net/Client.h"
|
||||
#include "common/net/Listener.h"
|
||||
#include "common/net/Server.h"
|
||||
#include "common/net/ib/IBConnectService.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/net/ib/RDMABuf.h"
|
||||
#include "common/net/sync/Client.h"
|
||||
#include "common/serde/ClientContext.h"
|
||||
#include "common/serde/Serde.h"
|
||||
#include "common/serde/Service.h"
|
||||
#include "common/utils/Address.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "common/utils/Result.h"
|
||||
#include "common/utils/UtcTime.h"
|
||||
#include "tests/GtestHelpers.h"
|
||||
#include "tests/common/net/ib/SetupIB.h"
|
||||
|
||||
DEFINE_bool(test_rdma_disable_log, false, "disable in TestRDMA");
|
||||
DEFINE_double(test_rdma_fault_injection_rate, 0.001, "fault injection rate in TestRDMA");
|
||||
DEFINE_int32(test_rdma_connections, 4, "connections in TestRDMA");
|
||||
DEFINE_int32(test_rdma_pkey_index, 0, "pkey_index in TestRDMA");
|
||||
namespace hf3fs::net {
|
||||
|
||||
struct RDMAReq {
|
||||
SERDE_STRUCT_FIELD(bufs, std::vector<RDMARemoteBuf>{});
|
||||
SERDE_STRUCT_FIELD(val, uint64_t(0));
|
||||
};
|
||||
|
||||
struct RDMARsp {
|
||||
SERDE_STRUCT_FIELD(dummy, Void());
|
||||
};
|
||||
|
||||
SERDE_SERVICE(RDMAService, 87) { SERDE_SERVICE_METHOD(test, 1, RDMAReq, RDMARsp); };
|
||||
|
||||
class RDMAServiceImpl : public serde::ServiceWrapper<RDMAServiceImpl, RDMAService> {
|
||||
public:
|
||||
RDMAServiceImpl()
|
||||
: bufPool_() {
|
||||
bufPool_ = net::RDMABufPool::create(1 << 20, 1024);
|
||||
}
|
||||
|
||||
CoTryTask<RDMARsp> test(serde::CallContext &ctx, const RDMAReq &req) {
|
||||
RDMARsp rsp;
|
||||
auto *ibsocket = ctx.transport()->ibSocket();
|
||||
XLOGF_IF(FATAL, ibsocket == nullptr, "Not IBSocket");
|
||||
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(folly::Random::rand32(100)));
|
||||
if (!req.bufs.empty()) {
|
||||
auto buf = co_await bufPool_->allocate();
|
||||
if (!buf) {
|
||||
co_return makeError(RPCCode::kRDMANoBuf);
|
||||
}
|
||||
auto batch = ibsocket->rdmaWriteBatch();
|
||||
for (auto remote : req.bufs) {
|
||||
if (buf.size() < remote.size()) {
|
||||
auto result = co_await batch.post();
|
||||
if (result.hasError()) {
|
||||
co_return makeError(std::move(result.error()));
|
||||
}
|
||||
batch.clear();
|
||||
buf.resetRange();
|
||||
}
|
||||
auto subbuf = buf.takeFirst(remote.size());
|
||||
if (!subbuf) {
|
||||
EXPECT_TRUE(false);
|
||||
co_return makeError(StatusCode::kInvalidArg);
|
||||
}
|
||||
XLOGF_IF(FATAL, req.val == 0, "here");
|
||||
*(uint64_t *)subbuf.ptr() = req.val;
|
||||
if (subbuf.size() >= 16) {
|
||||
*(uint64_t *)(subbuf.ptr() + subbuf.size() - 8) = req.val;
|
||||
}
|
||||
batch.add(remote, subbuf);
|
||||
}
|
||||
auto result = co_await batch.post();
|
||||
if (result.hasError()) {
|
||||
co_return makeError(std::move(result.error()));
|
||||
}
|
||||
}
|
||||
co_return rsp;
|
||||
}
|
||||
|
||||
private:
|
||||
std::shared_ptr<net::RDMABufPool> bufPool_;
|
||||
};
|
||||
|
||||
using namespace std::chrono_literals;
|
||||
|
||||
class TestRDMA : public test::SetupIB {
|
||||
public:
|
||||
void SetUp() override {
|
||||
auto &logger = folly::LoggerDB::get();
|
||||
if (FLAGS_test_rdma_disable_log) {
|
||||
oldLogConfig_ = logger.getConfig();
|
||||
logger.updateConfig(folly::parseLogConfig("CRITICAL"));
|
||||
}
|
||||
|
||||
server_ = std::make_unique<Server>(serverConfig_);
|
||||
server_->addSerdeService(std::make_unique<RDMAServiceImpl>());
|
||||
ASSERT_TRUE(server_->setup());
|
||||
ASSERT_TRUE(server_->start());
|
||||
ASSERT_TRUE(client_.start());
|
||||
|
||||
clientConfig_.io_worker().ibsocket().set_pkey_index(FLAGS_test_rdma_pkey_index);
|
||||
}
|
||||
void TearDown() override {
|
||||
if (FLAGS_test_rdma_disable_log) {
|
||||
folly::LoggerDB::get().updateConfig(oldLogConfig_);
|
||||
}
|
||||
client_.stopAndJoin();
|
||||
if (server_) {
|
||||
server_->stopAndJoin();
|
||||
server_.reset();
|
||||
}
|
||||
}
|
||||
|
||||
Address serverAddr() const { return server_->groups().front()->addressList().front(); }
|
||||
|
||||
void serverRestart() {
|
||||
if (server_) {
|
||||
serverConfig_.groups(0).listener().set_listen_port(serverAddr().port);
|
||||
server_->stopAndJoin();
|
||||
corpse_.push_back(std::move(server_));
|
||||
}
|
||||
server_ = std::make_unique<Server>(serverConfig_);
|
||||
server_->addSerdeService(std::make_unique<RDMAServiceImpl>());
|
||||
ASSERT_TRUE(server_->setup());
|
||||
ASSERT_TRUE(server_->start());
|
||||
}
|
||||
|
||||
void serverDropConnections() {
|
||||
for (auto &grp : server_->groups()) {
|
||||
grp->ioWorker().dropConnections(true, true);
|
||||
}
|
||||
}
|
||||
|
||||
protected:
|
||||
Client::Config clientConfig_;
|
||||
bool initClientConfig = [this] {
|
||||
clientConfig_.io_worker().transport_pool().set_max_connections(FLAGS_test_rdma_connections);
|
||||
clientConfig_.io_worker().ibsocket().set_max_rdma_wr(16);
|
||||
clientConfig_.io_worker().ibsocket().set_max_rdma_wr_per_post(4);
|
||||
clientConfig_.io_worker().set_num_event_loop(2);
|
||||
clientConfig_.thread_pool().set_num_connect_threads(2);
|
||||
clientConfig_.thread_pool().set_num_io_threads(4);
|
||||
clientConfig_.thread_pool().set_num_proc_threads(4);
|
||||
clientConfig_.set_default_timeout(2_s);
|
||||
return true;
|
||||
}();
|
||||
Client client_{clientConfig_};
|
||||
|
||||
Server::Config serverConfig_;
|
||||
bool initServerConfig = [this] {
|
||||
serverConfig_.groups(0).set_network_type(Address::RDMA);
|
||||
serverConfig_.thread_pool().set_num_io_threads(4);
|
||||
serverConfig_.thread_pool().set_num_proc_threads(4);
|
||||
return true;
|
||||
}();
|
||||
std::unique_ptr<Server> server_;
|
||||
std::vector<std::unique_ptr<Server>> corpse_;
|
||||
|
||||
folly::LogConfig oldLogConfig_;
|
||||
};
|
||||
|
||||
CoTask<void> runClient(RDMAService<> client,
|
||||
serde::ClientContext ctx,
|
||||
size_t bufSize,
|
||||
size_t bufCnt,
|
||||
bool injectFault,
|
||||
bool allowError,
|
||||
Duration dur,
|
||||
net::UserRequestOptions opts = {}) {
|
||||
auto invalidMR = net::RDMABuf::allocate(bufSize).toRemoteBuf();
|
||||
auto buf = net::RDMABuf::allocate(bufSize * bufCnt);
|
||||
CO_ASSERT_TRUE(buf);
|
||||
std::vector<net::RDMABuf> rdmaBufs;
|
||||
for (size_t i = 0; i < bufCnt; i++) {
|
||||
auto subBuf = buf.takeFirst(bufSize);
|
||||
CO_ASSERT_TRUE(subBuf);
|
||||
rdmaBufs.push_back(subBuf);
|
||||
}
|
||||
auto begin = SteadyClock::now();
|
||||
do {
|
||||
// call RPCs here
|
||||
auto val = folly::Random::rand64();
|
||||
RDMAReq req;
|
||||
req.val = val;
|
||||
|
||||
XLOGF(DBG, "request {}", req.val);
|
||||
|
||||
// use random number of buf
|
||||
size_t numBuf = folly::Random::rand32(rdmaBufs.size());
|
||||
for (size_t bufIdx = 0; bufIdx < numBuf; bufIdx++) {
|
||||
// buf with random length
|
||||
auto buf = rdmaBufs.at(bufIdx);
|
||||
auto len = folly::Random::rand32(8, buf.size() + 1);
|
||||
auto subbuf = buf.first(len);
|
||||
CO_ASSERT_TRUE(subbuf);
|
||||
*(uint64_t *)subbuf.ptr() = 0;
|
||||
if (subbuf.size() >= 16) {
|
||||
*(uint64_t *)(subbuf.ptr() + subbuf.size() - 8) = 0;
|
||||
}
|
||||
auto mr = subbuf.toRemoteBuf();
|
||||
if (injectFault && folly::Random::randDouble01() < FLAGS_test_rdma_fault_injection_rate) {
|
||||
mr = invalidMR.first(mr.size());
|
||||
}
|
||||
req.bufs.push_back(mr);
|
||||
}
|
||||
std::shuffle(req.bufs.begin(), req.bufs.end(), std::mt19937());
|
||||
|
||||
auto result = co_await client.test(ctx, req, &opts);
|
||||
if (!allowError) {
|
||||
CO_ASSERT_OK(result);
|
||||
}
|
||||
if (!result.hasError()) {
|
||||
for (auto subbuf : req.bufs) {
|
||||
// check data
|
||||
CO_ASSERT_EQ(*(uint64_t *)subbuf.addr(), val);
|
||||
if (subbuf.size() >= 16) {
|
||||
CO_ASSERT_EQ(*(uint64_t *)(subbuf.addr() + subbuf.size() - 8), val);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
XLOGF(DBG, "req {}, error {}", req.val, result.error());
|
||||
}
|
||||
} while (SteadyClock::now() - begin <= dur);
|
||||
}
|
||||
|
||||
CoTask<void> startMultiClient(folly::Executor::KeepAlive<> exec,
|
||||
size_t nClients,
|
||||
RDMAService<> client,
|
||||
serde::ClientContext ctx,
|
||||
size_t bufSize,
|
||||
size_t bufCnt,
|
||||
bool injectFault,
|
||||
bool allowError,
|
||||
Duration dur,
|
||||
net::UserRequestOptions opts = {}) {
|
||||
std::vector<folly::SemiFuture<Void>> tasks;
|
||||
for (size_t i = 0; i < nClients; i++) {
|
||||
tasks.push_back(
|
||||
runClient(client, ctx, bufSize, bufCnt, injectFault, allowError, dur, opts).scheduleOn(exec).start());
|
||||
}
|
||||
co_await folly::coro::collectAllRange(std::move(tasks));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, Basic) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, false, 5_s);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, Concurrent) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
co_await startMultiClient(&exec, 128, client, ctx, 512 << 10, 8, false, false, 10_s);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, ShortTimeout) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
net::UserRequestOptions opt;
|
||||
opt.timeout = 100_ms;
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, true, 5_s, opt);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, ShortTimeoutConcurrent) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
net::UserRequestOptions opt;
|
||||
opt.timeout = 100_ms;
|
||||
co_await startMultiClient(&exec, 128, client, ctx, 512 << 10, 8, false, true, 10_s, opt);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, FaultInject) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
co_await runClient(client, ctx, 512 << 10, 8, true, true, 5_s);
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, true, 5_s);
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, false, 5_s);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, FaultInjectConcurrent) {
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
co_await startMultiClient(&exec, 32, client, ctx, 512 << 10, 8, true, true, 5_s);
|
||||
co_await startMultiClient(&exec, 32, client, ctx, 512 << 10, 8, false, true, 5_s);
|
||||
co_await startMultiClient(&exec, 32, client, ctx, 512 << 10, 8, false, false, 5_s);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, SlowConnect) {
|
||||
IBConnectService::delayMs() = 200;
|
||||
SCOPE_EXIT { IBConnectService::delayMs() = 0; };
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
net::UserRequestOptions opt;
|
||||
opt.timeout = 200_ms;
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, true, 5_s, opt);
|
||||
co_await startMultiClient(&exec, 128, client, ctx, 512 << 10, 8, false, true, 10_s, opt);
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, ConnectitonLost) {
|
||||
IBConnectService::connectionLost() = true;
|
||||
SCOPE_EXIT { IBConnectService::connectionLost() = false; };
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
net::UserRequestOptions opt;
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, true, 100_ms, opt);
|
||||
co_await folly::coro::sleep(std::chrono::seconds(20));
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, ServerDropConnections) {
|
||||
auto opts = net::UserRequestOptions{};
|
||||
opts.sendRetryTimes = 0;
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
for (size_t i = 0; i < 3; i++) {
|
||||
serverDropConnections();
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(200));
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, false, 500_ms, opts);
|
||||
serverDropConnections();
|
||||
co_await folly::coro::sleep(std::chrono::milliseconds(200));
|
||||
co_await startMultiClient(&exec, 128, client, ctx, 512 << 10, 8, false, false, 500_ms, opts);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
TEST_F(TestRDMA, ServerRestart) {
|
||||
auto opts = net::UserRequestOptions{};
|
||||
opts.sendRetryTimes = 0;
|
||||
auto ctx = client_.serdeCtx(serverAddr());
|
||||
|
||||
folly::CPUThreadPoolExecutor exec(8);
|
||||
folly::coro::blockingWait(folly::coro::co_invoke([&]() -> CoTask<void> {
|
||||
RDMAService<> client;
|
||||
for (size_t i = 0; i < 5; i++) {
|
||||
serverRestart();
|
||||
co_await runClient(client, ctx, 512 << 10, 8, false, false, 500_ms, opts);
|
||||
serverRestart();
|
||||
co_await startMultiClient(&exec, 128, client, ctx, 512 << 10, 8, false, false, 500_ms, opts);
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net
|
||||
230
tests/common/net/ib/TestRDMABuf.cc
Normal file
230
tests/common/net/ib/TestRDMABuf.cc
Normal file
@@ -0,0 +1,230 @@
|
||||
#include <atomic>
|
||||
#include <chrono>
|
||||
#include <folly/Random.h>
|
||||
#include <folly/experimental/coro/BlockingWait.h>
|
||||
#include <folly/experimental/coro/GtestHelpers.h>
|
||||
#include <gtest/gtest.h>
|
||||
#include <map>
|
||||
#include <thread>
|
||||
#include <vector>
|
||||
|
||||
#include "SetupIB.h"
|
||||
#include "common/net/ib/IBDevice.h"
|
||||
#include "common/net/ib/RDMABuf.h"
|
||||
#include "common/utils/Coroutine.h"
|
||||
#include "gtest/gtest.h"
|
||||
|
||||
namespace hf3fs::net {
|
||||
|
||||
class TestRDMARemoteBuf : public test::SetupIB {};
|
||||
|
||||
TEST_F(TestRDMARemoteBuf, Basic) {
|
||||
RDMARemoteBuf buf;
|
||||
ASSERT_FALSE(buf);
|
||||
for (auto rkey : buf.rkeys_) {
|
||||
EXPECT_EQ(rkey, RDMARemoteBuf::Rkey());
|
||||
}
|
||||
|
||||
auto rdmaBuf = RDMABuf::allocate(4 << 20);
|
||||
ASSERT_TRUE(rdmaBuf);
|
||||
buf = rdmaBuf.toRemoteBuf();
|
||||
ASSERT_TRUE(buf);
|
||||
}
|
||||
|
||||
TEST_F(TestRDMARemoteBuf, Subrange) {
|
||||
auto rdmaBuf = RDMABuf::allocate(4 << 20);
|
||||
ASSERT_TRUE(rdmaBuf);
|
||||
const auto buf = rdmaBuf.toRemoteBuf();
|
||||
ASSERT_TRUE(buf);
|
||||
ASSERT_EQ(buf.size(), 4 << 20);
|
||||
|
||||
auto buf1 = buf;
|
||||
ASSERT_EQ(buf1, buf);
|
||||
ASSERT_EQ(buf1.advance(1 << 20), true);
|
||||
ASSERT_EQ(buf1.size(), 3 << 20);
|
||||
ASSERT_EQ(buf1.subtract(1 << 20), true);
|
||||
ASSERT_EQ(buf1.size(), 2 << 20);
|
||||
ASSERT_FALSE(buf1.advance(buf1.size() + 1));
|
||||
ASSERT_FALSE(buf1.subtract(buf1.size() + 1));
|
||||
|
||||
ASSERT_NE(buf, buf1);
|
||||
ASSERT_EQ(buf.subrange(1 << 20, 2 << 20), buf1);
|
||||
ASSERT_EQ(buf.subrange(2 << 20, 1 << 20), buf1.subrange(1 << 20, 1 << 20));
|
||||
|
||||
auto buf2 = buf;
|
||||
auto buf3 = buf2.first(1 << 20);
|
||||
auto buf4 = buf2.takeFirst(1 << 20);
|
||||
ASSERT_EQ(buf3, buf4);
|
||||
ASSERT_EQ(buf2, buf.subrange(1 << 20, 3 << 20));
|
||||
ASSERT_EQ(buf3.addr(), buf.addr());
|
||||
ASSERT_EQ(buf2.size(), 3 << 20);
|
||||
ASSERT_EQ(buf2.addr(), buf.addr() + (1 << 20));
|
||||
|
||||
auto buf5 = buf;
|
||||
auto buf6 = buf.last(1 << 20);
|
||||
auto buf7 = buf5.takeLast(1 << 20);
|
||||
ASSERT_EQ(buf6, buf7);
|
||||
ASSERT_EQ(buf6, buf.subrange(3 << 20, 1 << 20));
|
||||
ASSERT_EQ(buf5.size(), 3 << 20);
|
||||
ASSERT_EQ(buf5.addr(), buf.addr());
|
||||
|
||||
ASSERT_TRUE(buf.subrange(1 << 20, 3 << 20));
|
||||
ASSERT_FALSE(buf.subrange(4 << 20, 1));
|
||||
ASSERT_TRUE(buf.subrange(4 << 20, 0));
|
||||
ASSERT_TRUE(buf.first(buf.size()));
|
||||
ASSERT_TRUE(buf.last(buf.size()));
|
||||
ASSERT_FALSE(buf.first(buf.size() + 1));
|
||||
ASSERT_FALSE(buf.last(buf.size() + 1));
|
||||
|
||||
auto buf8 = buf;
|
||||
ASSERT_FALSE(buf8.advance(buf.size() + 1));
|
||||
ASSERT_FALSE(buf8.subtract(buf.size() + 1));
|
||||
}
|
||||
|
||||
class TestRDMABuf : public test::SetupIB {};
|
||||
|
||||
TEST_F(TestRDMABuf, Default) {
|
||||
RDMABuf buf;
|
||||
ASSERT_FALSE(buf);
|
||||
ASSERT_FALSE(buf.toRemoteBuf());
|
||||
ASSERT_FALSE(buf.toRemoteBuf().getRkey(0));
|
||||
|
||||
RDMABuf buf2 = buf;
|
||||
ASSERT_FALSE(buf2);
|
||||
}
|
||||
|
||||
TEST_F(TestRDMABuf, Allocate) {
|
||||
std::queue<RDMABuf> bufs;
|
||||
for (auto i = 0; i < 100; i++) {
|
||||
auto size = folly::Random::rand32(8, 8 << 20);
|
||||
auto buf = RDMABuf::allocate(size);
|
||||
ASSERT_TRUE(buf);
|
||||
ASSERT_NE(buf.ptr(), nullptr);
|
||||
ASSERT_EQ(buf.size(), size);
|
||||
bufs.push(buf);
|
||||
}
|
||||
|
||||
while (!bufs.empty()) {
|
||||
auto buf = std::move(bufs.front());
|
||||
bufs.pop();
|
||||
|
||||
std::map<int, RDMABufMR> map;
|
||||
for (auto &dev : IBDevice::all()) {
|
||||
auto mr = buf.getMR(dev->id());
|
||||
map[dev->id()] = mr;
|
||||
ASSERT_NE(mr, nullptr);
|
||||
}
|
||||
ASSERT_TRUE(buf.toRemoteBuf());
|
||||
// ASSERT_EQ(buf.reregMR(), 0);
|
||||
// for (auto &dev : IBDevice::all()) {
|
||||
// auto mr = buf.getMR(dev->id());
|
||||
// ASSERT_NE(mr, nullptr);
|
||||
// ASSERT_NE(mr, map[dev->id()]);
|
||||
// }
|
||||
}
|
||||
}
|
||||
|
||||
TEST_F(TestRDMABuf, Subrange) {
|
||||
const auto buf = RDMABuf::allocate(4 << 20);
|
||||
ASSERT_TRUE(buf);
|
||||
ASSERT_EQ(buf.size(), 4 << 20);
|
||||
ASSERT_EQ(buf.capacity(), buf.size());
|
||||
|
||||
auto buf1 = buf;
|
||||
ASSERT_EQ(buf1, buf);
|
||||
ASSERT_EQ(buf1.advance(1 << 20), true);
|
||||
ASSERT_EQ(buf1.capacity(), 4 << 20);
|
||||
ASSERT_EQ(buf1.size(), 3 << 20);
|
||||
ASSERT_EQ(buf1.subtract(1 << 20), true);
|
||||
ASSERT_EQ(buf1.capacity(), 4 << 20);
|
||||
ASSERT_EQ(buf1.size(), 2 << 20);
|
||||
ASSERT_FALSE(buf1.advance(buf1.size() + 1));
|
||||
ASSERT_FALSE(buf1.subtract(buf1.size() + 1));
|
||||
|
||||
ASSERT_NE(buf, buf1);
|
||||
ASSERT_EQ(buf.subrange(1 << 20, 2 << 20), buf1);
|
||||
ASSERT_EQ(buf.subrange(2 << 20, 1 << 20), buf1.subrange(1 << 20, 1 << 20));
|
||||
|
||||
auto buf2 = buf;
|
||||
auto buf3 = buf2.first(1 << 20);
|
||||
auto buf4 = buf2.takeFirst(1 << 20);
|
||||
ASSERT_EQ(buf3, buf4);
|
||||
ASSERT_EQ(buf2, buf.subrange(1 << 20, 3 << 20));
|
||||
ASSERT_EQ(buf3.ptr(), buf.ptr());
|
||||
ASSERT_EQ(buf2.size(), 3 << 20);
|
||||
ASSERT_EQ(buf2.ptr(), buf.ptr() + (1 << 20));
|
||||
|
||||
auto buf5 = buf;
|
||||
auto buf6 = buf.last(1 << 20);
|
||||
auto buf7 = buf5.takeLast(1 << 20);
|
||||
ASSERT_EQ(buf6, buf7);
|
||||
ASSERT_EQ(buf6, buf.subrange(3 << 20, 1 << 20));
|
||||
ASSERT_EQ(buf5.size(), 3 << 20);
|
||||
ASSERT_EQ(buf5.ptr(), buf.ptr());
|
||||
|
||||
ASSERT_TRUE(buf.subrange(1 << 20, 3 << 20));
|
||||
ASSERT_FALSE(buf.subrange(4 << 20, 1));
|
||||
ASSERT_TRUE(buf.subrange(4 << 20, 0));
|
||||
ASSERT_TRUE(buf.first(buf.size()));
|
||||
ASSERT_TRUE(buf.last(buf.size()));
|
||||
ASSERT_FALSE(buf.first(buf.size() + 1));
|
||||
ASSERT_FALSE(buf.last(buf.size() + 1));
|
||||
|
||||
auto buf8 = buf;
|
||||
ASSERT_FALSE(buf8.advance(buf.size() + 1));
|
||||
ASSERT_FALSE(buf8.subtract(buf.size() + 1));
|
||||
}
|
||||
|
||||
class TestRDMABufPool : public test::SetupIB {};
|
||||
|
||||
TEST_F(TestRDMABufPool, Basic) {
|
||||
constexpr size_t kBufSize = 4 << 10L;
|
||||
constexpr size_t kBufNum = 10;
|
||||
auto pool = RDMABufPool::create(kBufSize, kBufNum);
|
||||
|
||||
folly::coro::blockingWait([&]() -> CoTask<void> {
|
||||
std::queue<RDMABuf> bufs;
|
||||
for (size_t i = 0; i < kBufNum; i++) {
|
||||
auto buf = co_await pool->allocate();
|
||||
CO_ASSERT_TRUE(buf);
|
||||
bufs.push(buf);
|
||||
}
|
||||
|
||||
auto buf = co_await pool->allocate(std::chrono::milliseconds(10));
|
||||
CO_ASSERT_FALSE(buf);
|
||||
|
||||
auto firstPtr = bufs.front().ptr();
|
||||
std::atomic_bool allocated(false);
|
||||
auto firstBuf = bufs.front();
|
||||
bufs.pop();
|
||||
std::jthread proc([&allocated, first = std::move(firstBuf)]() {
|
||||
std::this_thread::sleep_for(std::chrono::milliseconds(500));
|
||||
EXPECT_EQ(allocated.load(), false);
|
||||
});
|
||||
buf = co_await pool->allocate();
|
||||
CO_ASSERT_EQ(firstPtr, buf.ptr());
|
||||
bufs.push(buf);
|
||||
|
||||
while (!bufs.empty()) {
|
||||
auto buf = bufs.front();
|
||||
bufs.pop();
|
||||
CO_ASSERT_EQ(buf.capacity(), kBufSize);
|
||||
CO_ASSERT_NE(buf.ptr(), nullptr);
|
||||
std::map<int, RDMABufMR> map;
|
||||
for (auto &dev : IBDevice::all()) {
|
||||
auto mr = buf.getMR(dev->id());
|
||||
map[dev->id()] = mr;
|
||||
CO_ASSERT_NE(mr, nullptr);
|
||||
}
|
||||
buf.toRemoteBuf();
|
||||
// CO_ASSERT_EQ(buf.reregMR(), 0);
|
||||
// for (auto &dev : IBDevice::all()) {
|
||||
// auto mr = buf.getMR(dev->id());
|
||||
// CO_ASSERT_NE(mr, nullptr);
|
||||
// CO_ASSERT_NE(mr, map[dev->id()]);
|
||||
// }
|
||||
}
|
||||
}());
|
||||
}
|
||||
|
||||
} // namespace hf3fs::net
|
||||
Reference in New Issue
Block a user