#include "loop.h"
#include <cstddef>
#include <cstdint>
#include <exception>
#include <future>
#include <memory>
#include <mutex>
#include <queue>
#include <string>
#include <thread>
#include <utility>
#include "../common/threading_utils.h"
#include "xgboost/collective/poll_utils.h"
#include "xgboost/collective/result.h"
#include "xgboost/collective/socket.h"
#include "xgboost/logging.h"
namespace xgboost::collective {
Result Loop::ProcessQueue(std::queue<Op>* p_queue) const {
timer_.Start(__func__);
auto error = [this](Op op) {
op.pr->set_value();
timer_.Stop(__func__);
};
if (stop_) {
timer_.Stop(__func__);
return Success();
}
auto& qcopy = *p_queue;
while (!qcopy.empty()) {
rabit::utils::PollHelper poll;
std::size_t n_ops = qcopy.size();
for (std::size_t i = 0; i < n_ops; ++i) {
auto op = std::move(qcopy.front());
qcopy.pop();
switch (op.code) {
case Op::kRead: {
poll.WatchRead(*op.sock);
break;
}
case Op::kWrite: {
poll.WatchWrite(*op.sock);
break;
}
case Op::kSleep: {
break;
}
default: {
error(op);
return Fail("Invalid socket operation.");
}
}
qcopy.push(std::move(op));
}
timer_.Start("poll");
if (!poll.fds.empty()) {
auto rc = poll.Poll(timeout_);
if (!rc.OK()) {
timer_.Stop(__func__);
return rc;
}
}
timer_.Stop("poll");
CHECK(!qcopy.empty());
for (std::size_t i = 0; i < n_ops; ++i) {
auto op = std::move(qcopy.front());
qcopy.pop();
std::int32_t n_bytes_done{0};
if (!op.sock) {
CHECK(op.code == Op::kSleep);
} else {
CHECK(op.sock->NonBlocking());
}
switch (op.code) {
case Op::kRead: {
if (poll.CheckRead(*op.sock)) {
n_bytes_done = op.sock->Recv(op.ptr + op.off, op.n - op.off);
if (n_bytes_done == 0) {
error(op);
return Fail("Encountered EOF. The other end is likely closed.",
op.sock->GetSockError());
}
}
break;
}
case Op::kWrite: {
if (poll.CheckWrite(*op.sock)) {
n_bytes_done = op.sock->Send(op.ptr + op.off, op.n - op.off);
}
break;
}
case Op::kSleep: {
std::this_thread::sleep_for(std::chrono::seconds{op.n});
n_bytes_done = op.n;
break;
}
default: {
error(op);
return Fail("Invalid socket operation.");
}
}
if (n_bytes_done == -1 && !system::LastErrorWouldBlock()) {
auto rc = system::FailWithCode("Invalid socket output.");
error(op);
return rc;
}
op.off += n_bytes_done;
CHECK_LE(op.off, op.n);
if (op.off != op.n) {
qcopy.push(op);
} else {
op.pr->set_value();
}
}
}
timer_.Stop(__func__);
return Success();
}
void Loop::Process() {
auto set_rc = [this](Result&& rc) {
std::lock_guard lock{rc_lock_};
rc_ = std::forward<Result>(rc);
};
while (true) {
try {
std::unique_lock lock{mu_};
cv_.wait(lock, [this] { return !this->queue_.empty() || stop_; });
if (stop_) {
break; }
std::queue<Op> qcopy;
while (!queue_.empty()) {
auto op = std::move(queue_.front());
queue_.pop();
qcopy.push(op);
}
lock.unlock();
auto rc = this->ProcessQueue(&qcopy);
if (!rc.OK()) {
set_rc(std::move(rc));
} else {
std::unique_lock lock{mu_};
CHECK(qcopy.empty() || stop_);
}
} catch (std::exception const& e) {
curr_exce_ = std::current_exception();
set_rc(Fail("Exception inside the event loop:" + std::string{e.what()}));
} catch (...) {
curr_exce_ = std::current_exception();
set_rc(Fail("Unknown exception inside the event loop."));
}
}
}
Result Loop::Stop() {
CHECK_EQ(this->Block().OK(), this->rc_.OK());
std::unique_lock lock{mu_};
stop_ = true;
lock.unlock();
this->cv_.notify_one();
if (this->worker_.joinable()) {
this->worker_.join();
}
if (curr_exce_) {
std::rethrow_exception(curr_exce_);
}
return Success();
}
[[nodiscard]] Result Loop::Block() {
{
std::lock_guard<std::mutex> guard{rc_lock_};
if (!rc_.OK()) {
stop_ = true;
}
}
if (!this->worker_.joinable()) {
std::lock_guard<std::mutex> guard{rc_lock_};
return Fail("Worker has stopped.", std::move(rc_));
}
{
std::unique_lock lock{mu_};
cv_.notify_one();
}
for (auto& fut : futures_) {
if (fut.valid()) {
try {
fut.get();
} catch (std::future_error const&) {
}
}
}
futures_.clear();
{
std::lock_guard<std::mutex> lock{rc_lock_};
return std::move(rc_);
}
}
void Loop::Submit(Op op) {
auto p = std::make_shared<std::promise<void>>();
op.pr = std::move(p);
futures_.emplace_back(op.pr->get_future());
CHECK_NE(op.n, 0);
std::unique_lock lock{mu_};
queue_.push(op);
}
Loop::Loop(std::chrono::seconds timeout) : timeout_{timeout} {
timer_.Init(__func__);
worker_ = std::thread{[this] {
this->Process();
}};
common::NameThread(&worker_, "lw");
}
}