diff --git a/.circleci/config.yml b/.circleci/config.yml new file mode 100644 index 0000000..ddcaa15 --- /dev/null +++ b/.circleci/config.yml @@ -0,0 +1,44 @@ +version: 2.1 + +commands: + install-bazel: + steps: + - run: + name: Installing bazel + command: | + bazel_version=2.0.0 + curl -OL https://github.com/bazelbuild/bazel/releases/download/${bazel_version}/bazel-${bazel_version}-installer-linux-x86_64.sh + chmod +x bazel-${bazel_version}-installer-linux-x86_64.sh + ./bazel-${bazel_version}-installer-linux-x86_64.sh + rm ./bazel-${bazel_version}-installer-linux-x86_64.sh + bazel --version + + sysinfo: + steps: + - run: + name: System information + command: | + uname -a + free -m + gcc --version + +jobs: + build: + docker: + - image: gcc:9 + steps: + - sysinfo + - install-bazel + - checkout + - run: + name: Building + command: bazel build //... --jobs=4 # --jobs=auto will be OOM killed + - run: + name: Testing + command: bazel test //... --test_tag_filters='-io_uring,-benchmark,-example' --test_output=errors + +workflows: + version: 2 + default_workflow: + jobs: + - build diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 1a85a9e..8301b6c 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -24,6 +24,9 @@ jobs: - name: Building run: CC=clang bazel build //... + - name: Testing + run: CC=clang bazel test //... --test_tag_filters='-io_uring,-benchmark,-example' --test_output=errors + build: name: ubuntu-gcc-9 runs-on: ubuntu-latest diff --git a/README.md b/README.md index e521eec..2c5fa0a 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,4 @@ +[![Build Status][circle-badge]][circle-link] [![Build Status][github-ci-badge]][github-link] [![Codacy Badge][codacy-badge]][codacy-link] [![Codecov Badge][codecov-badge]][codecov-link] @@ -74,6 +75,8 @@ Interested in getting involved? We would love to help you! For simple bug fixes, just submit a PR with the fix and we can discuss the fix directly in the PR. If the fix is more complex, start with an issue. +[circle-badge]: https://circleci.com/gh/condy0919/bipolar.svg?style=shield +[circle-link]: https://circleci.com/gh/condy0919/bipolar [github-ci-badge]: https://github.com/condy0919/bipolar/workflows/BIPOLAR%20CI/badge.svg [github-link]: https://github.com/condy0919/bipolar [codacy-badge]: https://api.codacy.com/project/badge/Grade/7c5e88ade2944d7ca1741d2b3e709f4f diff --git a/bipolar/net/epoll.cpp b/bipolar/net/epoll.cpp index 832cfc9..4a2c601 100644 --- a/bipolar/net/epoll.cpp +++ b/bipolar/net/epoll.cpp @@ -34,7 +34,7 @@ Result Epoll::create() noexcept { Result Epoll::poll(std::vector& events, std::chrono::milliseconds timeout) noexcept { const int ret = - ::epoll_wait(epfd_, events.data(), events.size(), timeout.count()); + ::epoll_wait(epfd_, events.data(), events.capacity(), timeout.count()); if (ret == -1) { return Err(errno); } diff --git a/bipolar/net/tcp.cpp b/bipolar/net/tcp.cpp index 342c8fe..d200e85 100644 --- a/bipolar/net/tcp.cpp +++ b/bipolar/net/tcp.cpp @@ -1,6 +1,7 @@ #include "bipolar/net/tcp.hpp" #include +#include #include #include #include @@ -132,6 +133,15 @@ Result TcpStream::shutdown(int how) noexcept { return Ok(Void{}); } +Result TcpStream::set_nonblocking(bool enable) noexcept { + int opt = static_cast(enable); + const int ret = ::ioctl(fd_, FIONBIO, &opt); + if (ret == -1) { + return Err(errno); + } + return Ok(Void{}); +} + Result TcpStream::set_nodelay(bool enable) noexcept { const int optval = static_cast(enable); const int ret = @@ -152,6 +162,38 @@ Result TcpStream::nodelay() noexcept { return Ok(static_cast(optval)); } +Result +TcpStream::set_linger(Option s) noexcept { + struct linger opt = { + .l_onoff = s.has_value(), + .l_linger = 0, + }; + + if (s.has_value()) { + opt.l_linger = s.value().count(); + } + + const int ret = ::setsockopt(fd_, SOL_SOCKET, SO_LINGER, &opt, sizeof(opt)); + if (ret == -1) { + return Err(errno); + } + return Ok(Void{}); +} + +Result, int> TcpStream::linger() noexcept { + struct linger opt; + socklen_t len = sizeof(opt); + const int ret = ::getsockopt(fd_, SOL_SOCKET, SO_LINGER, &opt, &len); + if (ret == -1) { + return Err(errno); + } + if (opt.l_onoff) { + return Ok(Some(std::chrono::seconds(opt.l_linger))); + } else { + return Ok(None); + } +} + Result TcpStream::take_error() noexcept { int optval = 0; socklen_t len = sizeof(optval); diff --git a/bipolar/net/tcp.hpp b/bipolar/net/tcp.hpp index 8e97a01..32e56e3 100644 --- a/bipolar/net/tcp.hpp +++ b/bipolar/net/tcp.hpp @@ -9,10 +9,12 @@ #include +#include #include #include #include "bipolar/core/movable.hpp" +#include "bipolar/core/option.hpp" #include "bipolar/core/result.hpp" #include "bipolar/core/void.hpp" #include "bipolar/net/socket_address.hpp" @@ -40,6 +42,9 @@ namespace bipolar { /// stream.write(buf, 4); /// stream.read(buf, 4); /// ``` +/// +/// `man 7 tcp` for more information. +/// class TcpStream final : public Movable { public: /// Constructs a TCP stream from native handle (file descriptor). @@ -166,6 +171,15 @@ class TcpStream final : public Movable { /// `man 2 shutdown` for more information. Result shutdown(int how) noexcept; + /// Moves this TCP stream into or out of nonblocking mode. + /// + /// This will result in `read`, `write`, `recv` and `send` operations + /// becoming nonblocking, i.e., immediately returning from their calls. + /// If the IO operation is successful, `Ok` is returned and no further + /// action is required. If the IO operation could not be completed and + /// needs to be retried, `EAGAIN` is returned. + Result set_nonblocking(bool enable) noexcept; + /// Sets the value of the `TCP_NODELAY` option on this socket. /// /// If set, this option disables the Nagle algorithm. This means that @@ -178,6 +192,16 @@ class TcpStream final : public Movable { /// Gets the value of the `TCP_NODELAY` option on this socket Result nodelay() noexcept; + /// Sets the linger duration of this socket by setting the `SO_LINGER` + /// option. + /// + /// It used to **RESET** connections. + Result set_linger(Option s) noexcept; + + /// Reads the linger duration for this socket by getting the `SO_LINGER` + /// option. + Result, int> linger() noexcept; + /// Gets the value of the `SO_ERROR` option on this socket. /// /// This will retrive the stored error in the underlying socket, clearing diff --git a/bipolar/net/tests/tcp_test.cpp b/bipolar/net/tests/tcp_test.cpp index df3ce58..9cf86a2 100644 --- a/bipolar/net/tests/tcp_test.cpp +++ b/bipolar/net/tests/tcp_test.cpp @@ -1,3 +1,4 @@ +#include #include #include #include @@ -8,6 +9,7 @@ #include #include #include +#include #include #include #include @@ -23,12 +25,10 @@ using namespace bipolar; using namespace std::string_literals; +using namespace std::chrono_literals; -// TODO more tests - -inline constexpr SocketAddress - server_addr(IPv4Address(127, 0, 0, 1), - hton(static_cast(8081))); +inline constexpr auto anonymous_addr = + SocketAddress(IPv4Address(127, 0, 0, 1), 0); const auto LISTENER_MAGIC_NUMBER = reinterpret_cast(static_cast(0x50043)); @@ -38,19 +38,17 @@ TEST(TcpListener, bind_and_accept) { std::vector events(10); auto listener = - TcpListener::bind(SocketAddress(IPv4Address(127, 0, 0, 1), 0)) - .expect("bind to 127.0.0.1:0 failed"); + TcpListener::bind(anonymous_addr).expect("bind to 127.0.0.1:0 failed"); auto server_addr = listener.local_addr().value(); epoll.add(listener.as_fd(), LISTENER_MAGIC_NUMBER, EPOLLIN) .expect("epoll add failed"); Barrier barrier(2); - std::thread t( - [&]() { - auto strm = TcpStream::connect(server_addr).value(); - barrier.wait(); - }); + std::thread t([&]() { + auto strm = TcpStream::connect(server_addr).value(); + barrier.wait(); + }); epoll.poll(events, std::chrono::milliseconds(-1)); EXPECT_EQ(events.size(), 1); @@ -77,7 +75,7 @@ TEST(TcpListener, bind_and_accept) { TEST(TcpStream, try_clone) { auto epoll = Epoll::create().expect("epoll_create failed"); - auto strm = TcpStream::connect(server_addr).value(); + auto strm = TcpStream::connect(anonymous_addr).value(); auto strm2 = strm.try_clone().value(); epoll.add(strm2.as_fd(), nullptr, EPOLLOUT | EPOLLET) @@ -95,8 +93,393 @@ TEST(TcpStream, try_clone) { EXPECT_EQ(strm.take_error().value(), ECONNREFUSED); } +TEST(TcpStream, connect) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(strm.as_fd(), nullptr, EPOLLIN | EPOLLOUT | EPOLLET | EPOLLRDHUP) + .expect("epoll add failed"); + + std::promise p0; + std::promise p1; + std::future f1 = p1.get_future(); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)).value(); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.ptr, nullptr); + EXPECT_TRUE(!!(events[0].events & EPOLLOUT)); + + std::thread t( + [&listener, f0 = p0.get_future(), p1 = std::move(p1)]() mutable { + auto [s, sa] = listener.accept().value(); + f0.wait(); + s.close(); + p1.set_value(Void{}); + }); + t.detach(); + + p0.set_value(Void{}); + f1.wait(); + + events.resize(10); + epoll.poll(events, std::chrono::milliseconds(-1)).value(); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.ptr, nullptr); + EXPECT_TRUE(!!(events[0].events & EPOLLIN)); +} + +TEST(TcpStream, read) { + const std::size_t N = 16 * 1024; + + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + // confirm connection established + std::vector events(10); + epoll.add(strm.as_fd(), nullptr, EPOLLOUT | EPOLLET | EPOLLONESHOT); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(strm.take_error().value(), 0); + + epoll.add(listener.as_fd(), nullptr, EPOLLIN | EPOLLET | EPOLLONESHOT); + epoll.poll(events, std::chrono::milliseconds(-1)); + + std::thread t([&listener]() { + auto [s, sa] = listener.accept().expect("accept failed"); + + s.set_nonblocking(false); + + char buf[1024]; + std::size_t amount = 0; + while (amount < N) { + amount += s.write(buf, sizeof(buf)).expect("write failed"); + } + }); + t.detach(); + + // rearm the disabled events + epoll.mod(strm.as_fd(), nullptr, EPOLLIN | EPOLLET); + + std::size_t amount = 0; + while (amount < N) { + epoll.poll(events, std::chrono::milliseconds(-1)).value(); + EXPECT_EQ(events.size(), 1); + + char buf[1024]; + while (true) { + auto result = strm.read(buf, sizeof(buf)); + if (result.is_ok()) { + amount += result.value(); + } else { + break; + } + if (amount >= N) { + break; + } + } + } +} + +// // Why it failed on GitHub CI? +// TEST(TcpStream, write) { +// const std::size_t N = 16 * 1024; +// +// auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); +// auto server_addr = listener.local_addr().value(); +// auto epoll = Epoll::create().expect("epoll_create failed"); +// auto strm = TcpStream::connect(server_addr).expect("connect failed"); +// +// // confirm connection established +// std::vector events(10); +// epoll.add(strm.as_fd(), nullptr, EPOLLOUT | EPOLLET | EPOLLONESHOT); +// epoll.poll(events, std::chrono::milliseconds(-1)); +// EXPECT_EQ(strm.take_error().value(), 0); +// +// epoll.add(listener.as_fd(), nullptr, EPOLLIN | EPOLLET | EPOLLONESHOT); +// epoll.poll(events, std::chrono::milliseconds(-1)); +// +// std::thread t([&listener]() { +// // XXX weird, EBADF returned on GitHub CI +// auto [s, sa] = listener.accept().expect("accept failed 2"); +// +// s.set_nonblocking(false); +// +// char buf[1024]; +// std::size_t amount = 0; +// while (amount < N) { +// amount += s.read(buf, sizeof(buf)).expect("read failed"); +// } +// }); +// t.detach(); +// +// // rearm the disabled events +// epoll.mod(strm.as_fd(), nullptr, EPOLLOUT | EPOLLET); +// +// std::size_t amount = 0; +// while (amount < N) { +// epoll.poll(events, std::chrono::milliseconds(-1)).value(); +// EXPECT_EQ(events.size(), 1); +// +// char buf[1024]; +// while (true) { +// auto result = strm.write(buf, sizeof(buf)); +// if (result.is_ok()) { +// amount += result.value(); +// } else { +// break; +// } +// if (amount >= N) { +// break; +// } +// } +// } +// } + +TEST(TcpStream, connect_then_close) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 1, EPOLLIN | EPOLLET); + epoll.add(strm.as_fd(), 2, EPOLLIN | EPOLLET); + + bool shutdown = false; + std::vector events(10); + while (!shutdown) { + epoll.poll(events, std::chrono::milliseconds(-1)); + + for (auto event : events) { + if (event.data.fd == 1) { + auto [s, sa] = listener.accept().value(); + epoll.add(s.as_fd(), 3, EPOLLIN | EPOLLOUT | EPOLLET); + s.close(); + } else if (event.data.fd == 2) { + shutdown = true; + } + } + } +} + +TEST(TcpStream, listen_then_close) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto epoll = Epoll::create().expect("epoll_create failed"); + + epoll.add(listener.as_fd(), nullptr, EPOLLIN | EPOLLRDHUP | EPOLLET); + listener.close(); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(100)); + EXPECT_EQ(events.size(), 0); +} + +TEST(TcpStream, connect_error) { + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(anonymous_addr).expect("connect failed"); + + epoll.add(strm.as_fd(), nullptr, EPOLLOUT | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(!!(events[0].events & EPOLLOUT)); + EXPECT_EQ(strm.take_error().value(), ECONNREFUSED); +} + +TEST(TcpStream, write_then_drop) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 1, EPOLLIN | EPOLLET); + epoll.add(strm.as_fd(), 2, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 1); + + auto strm2 = std::get<0>(listener.accept().value()); + epoll.add(strm2.as_fd(), 3, EPOLLOUT | EPOLLET); + + strm2.write("1234", 4); + strm2.close(); + + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 2); + + char buf[4] = ""; + strm.read(buf, sizeof(buf)); + EXPECT_TRUE(std::memcmp(buf, "1234", 4) == 0); +} + +TEST(TcpStream, connection_reset_by_peer) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 1, EPOLLIN | EPOLLET | EPOLLONESHOT); + epoll.add(strm.as_fd(), 2, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 1); + + auto strm2 = std::get<0>(listener.accept().value()); + + // reset the connection + strm.set_linger(Some(0s)); + strm.close(); + + epoll.add(strm2.as_fd(), 3, EPOLLIN | EPOLLET); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 3); + + char buf[10]; + auto result = strm2.read(buf, sizeof(buf)); + EXPECT_TRUE(result.is_error()); + EXPECT_EQ(result.error(), ECONNRESET); +} + +TEST(TcpStream, write_error) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 0, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + + auto [s, sa] = listener.accept().value(); + s.close(); + + char buf[10] = "miss"; + Result result; + while ((result = strm.send(buf, sizeof(buf), MSG_NOSIGNAL)).is_ok()) { + } + EXPECT_TRUE(result.is_error()); + EXPECT_EQ(result.error(), EPIPE); +} + +TEST(TcpStream, write_shutdown) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 0, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + + auto [s, sa] = listener.accept().value(); + s.shutdown(SHUT_WR); + + epoll.add(strm.as_fd(), 0, EPOLLIN | EPOLLET); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_TRUE(!!(events[0].events & EPOLLIN)); +} + +TEST(TcpStream, write_then_del) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 1, EPOLLIN | EPOLLET); + epoll.add(strm.as_fd(), 3, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 1); + + auto strm2 = std::get<0>(listener.accept().value()); + epoll.add(strm2.as_fd(), 2, EPOLLOUT | EPOLLET); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 2); + + strm2.write("1234", 4); + epoll.del(strm2.as_fd()); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 3); + + char buf[10]; + auto result = strm.read(buf, sizeof(buf)); + EXPECT_TRUE(result.is_ok()); + EXPECT_EQ(result.value(), 4); + EXPECT_TRUE(std::memcmp(buf, "1234", 4) == 0); +} + +TEST(TcpStream, tcp_no_events_after_del) { + auto listener = TcpListener::bind(anonymous_addr).expect("bind failed"); + auto server_addr = listener.local_addr().value(); + auto epoll = Epoll::create().expect("epoll_create failed"); + auto strm = TcpStream::connect(server_addr).expect("connect failed"); + + epoll.add(listener.as_fd(), 1, EPOLLIN | EPOLLET); + epoll.add(strm.as_fd(), 3, EPOLLIN | EPOLLET); + + std::vector events(10); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 1); + + auto [strm2, strm2_addr] = listener.accept().value(); + EXPECT_TRUE(strm2_addr.addr().is_loopback()); + EXPECT_EQ(strm2.peer_addr().value(), strm2_addr); + EXPECT_EQ(strm2.local_addr().value(), server_addr); + + epoll.add(strm2.as_fd(), 2, EPOLLOUT | EPOLLET); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 2); + + strm2.write("1234", 4); + epoll.poll(events, std::chrono::milliseconds(-1)); + EXPECT_EQ(events.size(), 1); + EXPECT_EQ(events[0].data.fd, 3); + EXPECT_TRUE(!!(events[0].events & EPOLLIN)); + + epoll.del(listener.as_fd()); + epoll.del(strm.as_fd()); + epoll.del(strm2.as_fd()); + + epoll.poll(events, 10ms); + EXPECT_EQ(events.size(), 0); + + char buf[10]; + strm.read(buf, sizeof(buf)); + EXPECT_TRUE(std::memcmp(buf, "1234", 4) == 0); + + strm2.write("9876", 4); + epoll.poll(events, 10ms); + EXPECT_EQ(events.size(), 0); + + std::this_thread::sleep_for(100ms); + strm.read(buf, sizeof(buf)); + EXPECT_TRUE(std::memcmp(buf, "9876", 4) == 0); + + epoll.poll(events, 10ms); + EXPECT_EQ(events.size(), 0); +} + TEST(TcpStream, shutdown) { - auto strm = TcpStream::connect(server_addr).value(); + auto strm = TcpStream::connect(anonymous_addr).value(); auto result = strm.shutdown(SHUT_RDWR); EXPECT_TRUE(result.is_error()); EXPECT_EQ(result.error(), ENOTCONN); @@ -105,16 +488,19 @@ TEST(TcpStream, shutdown) { TEST(TcpStream, send) { Barrier barrier(2); + SocketAddress server_addr = anonymous_addr; + std::thread([&]() { std::vector events(10); auto epoll = Epoll::create().expect("epoll_create failed"); - auto listener = TcpListener::bind(server_addr) - .expect("bind to 127.0.0.1:8081 failed"); + auto listener = TcpListener::bind(anonymous_addr) + .expect("bind to 127.0.0.1:0 failed"); epoll.add(listener.as_fd(), LISTENER_MAGIC_NUMBER, EPOLLIN) .expect("epoll add fd failed"); + server_addr = listener.local_addr().value(); barrier.wait(); while (true) {