82 AgWorkerPool(
const std::vector<std::string>& endpoints,
double timeout_seconds)
83 : endpoints_(endpoints), timeout_(timeout_seconds) {
84 if (endpoints_.empty()) {
86 "ag: the 'cluster' execution backend needs worker endpoints: set "
87 "AgOptions::endpoints to \"host:port\" strings, each one an ag-worker "
88 "started with 'java -cp jline.jar jline.cli.AgWorker -p <port>'");
90 fds_.assign(endpoints_.size(), -1);
91 live_.assign(endpoints_.size(),
true);
103 template <
class Payload>
105 if (assigned_)
return;
106 partition(num_agents);
107 for (std::size_t w = 0; w < endpoints_.size(); ++w) {
108 if (owns_[w].empty())
continue;
111 nlohmann::json agents = nlohmann::json::array();
112 for (std::size_t t = 0; t < owns_[w].size(); ++t) {
113 agents.push_back(encode(payload(owns_[w][t])));
116 msg[
"op"] =
"assign";
117 msg[
"agents"] = agents;
118 const nlohmann::json reply = call(w, msg);
119 if (reply.value(
"op", std::string()) !=
"assigned") {
120 throw std::runtime_error(
"worker did not acknowledge the assignment");
122 }
catch (
const std::exception& e) {
123 std::cerr <<
"[LINE] Warning: AG worker " << endpoints_[w]
124 <<
" is unreachable (" << e.what()
125 <<
"); its agents run on the coordinator instead" << std::endl;
137 void sweep(
const std::vector<double>& x,
138 std::vector<std::vector<double>>& pi,
139 std::vector<bool>& pending) {
140 for (std::size_t w = 0; w < endpoints_.size(); ++w) {
141 if (!live_[w] || owns_[w].empty())
continue;
146 const nlohmann::json reply = call(w, msg);
147 if (reply.value(
"op", std::string()) !=
"swept") {
148 throw std::runtime_error(
"unexpected reply '" +
149 reply.value(
"op", std::string()) +
"'");
151 for (
const nlohmann::json& e : reply.at(
"agents")) {
152 const std::size_t k = e.at(
"k").get<std::size_t>();
153 if (k >= pi.size()) {
154 throw std::runtime_error(
"worker answered for an agent out of range");
156 pi[k] = e.at(
"pi").get<std::vector<double>>();
159 }
catch (
const std::exception& e) {
160 std::cerr <<
"[LINE] Warning: AG worker " << endpoints_[w]
161 <<
" failed mid-sweep (" << e.what()
162 <<
"); its agents are solved on the coordinator for the rest of "
163 <<
"the run" << std::endl;
177 void partition(std::size_t n) {
178 owns_.assign(endpoints_.size(), std::vector<std::size_t>());
179 for (std::size_t k = 0; k < n; ++k) owns_[k % endpoints_.size()].push_back(k);
182 static nlohmann::json encode(
const AgWirePayload& a) {
188 j[
"level"] = a.level;
190 nlohmann::json passive = nlohmann::json::array();
191 for (std::size_t i = 0; i < a.passive_c.size(); ++i) {
193 e[
"c"] = a.passive_c[i];
194 e[
"M"] = a.passive_m[i];
195 passive.push_back(e);
197 nlohmann::json active = nlohmann::json::array();
198 for (std::size_t i = 0; i < a.active_c.size(); ++i) {
200 e[
"c"] = a.active_c[i];
201 e[
"M"] = a.active_m[i];
204 j[
"passive"] = passive;
205 j[
"active"] = active;
209 void connect(std::size_t w) {
210 const std::string& ep = endpoints_[w];
211 const std::size_t colon = ep.rfind(
':');
212 if (colon == std::string::npos || colon == 0) {
213 throw std::runtime_error(
"malformed endpoint '" + ep +
"', expected host:port");
215 const std::string host = ep.substr(0, colon);
216 const std::string port = ep.substr(colon + 1);
219 hints.ai_family = AF_UNSPEC;
220 hints.ai_socktype = SOCK_STREAM;
221 addrinfo* res =
nullptr;
222 if (::getaddrinfo(host.c_str(), port.c_str(), &hints, &res) != 0 || res ==
nullptr) {
223 throw std::runtime_error(
"cannot resolve " + ep);
226 for (addrinfo* p = res; p !=
nullptr; p = p->ai_next) {
227 fd = ::socket(p->ai_family, p->ai_socktype, p->ai_protocol);
228 if (fd < 0)
continue;
230 tv.tv_sec =
static_cast<long>(timeout_);
231 tv.tv_usec =
static_cast<long>((timeout_ - tv.tv_sec) * 1e6);
232 ::setsockopt(fd, SOL_SOCKET, SO_RCVTIMEO, &tv,
sizeof(tv));
233 ::setsockopt(fd, SOL_SOCKET, SO_SNDTIMEO, &tv,
sizeof(tv));
234 if (::connect(fd, p->ai_addr, p->ai_addrlen) == 0)
break;
239 if (fd < 0)
throw std::runtime_error(
"cannot connect to " + ep);
243 nlohmann::json call(std::size_t w,
const nlohmann::json& msg) {
244 const std::string line = msg.dump() +
"\n";
245 std::size_t sent = 0;
246 while (sent < line.size()) {
247 const ssize_t n = ::send(fds_[w], line.data() + sent, line.size() - sent, 0);
248 if (n <= 0)
throw std::runtime_error(
"send failed: " + std::string(std::strerror(errno)));
249 sent +=
static_cast<std::size_t
>(n);
254 const ssize_t n = ::recv(fds_[w], &ch, 1, 0);
255 if (n <= 0)
throw std::runtime_error(
"worker closed the connection");
256 if (ch ==
'\n')
break;
259 nlohmann::json j = nlohmann::json::parse(reply);
260 if (j.contains(
"error"))
throw std::runtime_error(j.at(
"error").get<std::string>());
264 void close_one(std::size_t w) {
272 for (std::size_t w = 0; w < fds_.size(); ++w) {
273 if (fds_[w] < 0)
continue;
274 const std::string bye =
"{\"op\":\"bye\"}\n";
275 ::send(fds_[w], bye.data(), bye.size(), 0);
280 std::vector<std::string> endpoints_;
282 std::vector<int> fds_;
283 std::vector<bool> live_;
284 std::vector<std::vector<std::size_t>> owns_;
285 bool assigned_ =
false;
std::vector< std::array< double, 3 > > ag_triplets(const Matrix< double > &m)
Non-zero entries of M as [row, col, value] triplets, 0-based.