Loading...
Searching...
No Matches
ServeMode.cpp
Go to the documentation of this file.
1/*
2** This file is part of eOn.
3**
4** SPDX-License-Identifier: BSD-3-Clause
5**
6** Copyright (c) 2010--present, eOn Development Team
7** All rights reserved.
8**
9** Repo:
10** https://github.com/TheochemUI/eOn
11*/
12
26
27#include "eon/ServeMode.h"
28#include "eon/EonLogger.h"
29#include "eon/Potential.h"
30#include "eon/ServeRpcServer.h"
31
32#include <algorithm>
33#include <sstream>
34#include <thread>
35#include <vector>
36
37namespace eonc {
38namespace {
39
41ForceCallback makeForceCallback(std::shared_ptr<::Potential> pot) {
42 return [pot = std::move(pot)](long nAtoms, const double *positions,
43 const int *atomicNrs, double *forces,
44 double *energy, const double *box) {
45 double variance = 0.0;
46 pot->force(nAtoms, positions, atomicNrs, forces, energy, &variance, box);
47 };
48}
49
50} // anonymous namespace
51
52// ---------------------------------------------------------------------------
53// Single-model serve
54// ---------------------------------------------------------------------------
55
56void serveMode(const Parameters &params, const std::string &host,
57 uint16_t port) {
58 auto pot_type = params.potential_options.potential;
59 EONC_LOG_INFO("Creating potential: {}",
60 std::string(magic_enum::enum_name(pot_type)));
61
62 auto eon_pot = eonc::helpers::makePotential(params);
63 if (!eon_pot) {
64 EONC_LOG_ERROR("Failed to create potential of type {}",
65 std::string(magic_enum::enum_name(pot_type)));
66 return;
67 }
68
69 auto callback = makeForceCallback(std::move(eon_pot));
70
71 // Blocks until killed (runs Cap'n Proto event loop)
72 startRpcServer(std::move(callback), host, port);
73}
74
75// ---------------------------------------------------------------------------
76// Multi-model concurrent serve
77// ---------------------------------------------------------------------------
78
79void serveMultiple(const std::vector<ServeEndpoint> &endpoints,
80 const Parameters &base_params) {
81 if (endpoints.empty()) {
82 EONC_LOG_ERROR("No serve endpoints specified");
83 return;
84 }
85
86 // Single endpoint: run in the main thread (no extra overhead)
87 if (endpoints.size() == 1) {
88 auto params = base_params;
89 params.potential_options.potential = endpoints[0].potential;
90 serveMode(params, endpoints[0].host, endpoints[0].port);
91 return;
92 }
93
94 // Multiple endpoints: one thread per server
95 EONC_LOG_INFO("Starting {} concurrent RPC servers", endpoints.size());
96
97 std::vector<std::thread> threads;
98 threads.reserve(endpoints.size());
99
100 for (const auto &ep : endpoints) {
101 threads.emplace_back([&base_params, ep]() {
102 auto params = base_params;
103 params.potential_options.potential = ep.potential;
104 auto pot_name = std::string(magic_enum::enum_name(ep.potential));
105
106 EONC_LOG_INFO("[{}:{}] Creating potential: {}", ep.host, ep.port,
107 pot_name);
108
109 auto eon_pot = eonc::helpers::makePotential(params);
110 if (!eon_pot) {
111 EONC_LOG_ERROR("[{}:{}] Failed to create potential {}", ep.host,
112 ep.port, pot_name);
113 return;
114 }
115
116 auto callback = makeForceCallback(std::move(eon_pot));
117 startRpcServer(std::move(callback), ep.host, ep.port);
118 });
119 }
120
121 // Wait for all threads (they block until killed)
122 for (auto &t : threads) {
123 if (t.joinable()) {
124 t.join();
125 }
126 }
127}
128
129// ---------------------------------------------------------------------------
130// Replicated serve: N copies of same potential on sequential ports
131// ---------------------------------------------------------------------------
132
133void serveReplicated(const Parameters &params, const std::string &host,
134 uint16_t base_port, size_t replicas) {
135 if (replicas == 0) {
136 EONC_LOG_ERROR("Replicas must be >= 1");
137 return;
138 }
139 if (replicas == 1) {
140 serveMode(params, host, base_port);
141 return;
142 }
143
144 EONC_LOG_INFO("Starting {} replicated servers on ports {}-{}", replicas,
145 base_port, base_port + replicas - 1);
146
147 std::vector<std::thread> threads;
148 threads.reserve(replicas);
149
150 for (size_t i = 0; i < replicas; ++i) {
151 uint16_t port = static_cast<uint16_t>(base_port + i);
152 threads.emplace_back(
153 [&params, &host, port]() { serveMode(params, host, port); });
154 }
155
156 for (auto &t : threads) {
157 if (t.joinable()) {
158 t.join();
159 }
160 }
161}
162
163// ---------------------------------------------------------------------------
164// Gateway serve: single port backed by a pool of potential instances
165// ---------------------------------------------------------------------------
166
167void serveGateway(const Parameters &params, const std::string &host,
168 uint16_t port, size_t pool_size) {
169 if (pool_size == 0) {
170 EONC_LOG_ERROR("Pool size must be >= 1");
171 return;
172 }
173
174 auto pot_type = params.potential_options.potential;
175 EONC_LOG_INFO("Creating pool of {} {} instances for gateway on {}:{}",
176 pool_size, std::string(magic_enum::enum_name(pot_type)), host,
177 port);
178
179 std::vector<ForceCallback> pool;
180 pool.reserve(pool_size);
181
182 for (size_t i = 0; i < pool_size; ++i) {
183 auto eon_pot = eonc::helpers::makePotential(params);
184 if (!eon_pot) {
185 EONC_LOG_ERROR("Failed to create potential instance {}/{}", i + 1,
186 pool_size);
187 return;
188 }
189 pool.push_back(makeForceCallback(std::move(eon_pot)));
190 }
191
192 EONC_LOG_INFO("Pool ready, starting gateway server");
193 startPooledRpcServer(std::move(pool), host, port);
194}
195
196// ---------------------------------------------------------------------------
197// Config-driven dispatch
198// ---------------------------------------------------------------------------
199
200void serveFromConfig(const Parameters &params) {
201 const auto &opts = params.serve_options;
202
203 // Multi-model endpoints take priority
204 if (!opts.endpoints.empty()) {
205 auto endpoints = parseServeSpec(opts.endpoints);
206 if (endpoints.empty()) {
207 EONC_LOG_ERROR("No valid endpoints in spec: {}", opts.endpoints);
208 return;
209 }
210 serveMultiple(endpoints, params);
211 return;
212 }
213
214 // Gateway mode
215 if (opts.gateway_port > 0) {
216 size_t pool = (opts.replicas > 0) ? opts.replicas : 1;
217 serveGateway(params, opts.host, opts.gateway_port, pool);
218 return;
219 }
220
221 // Replicated mode (default)
222 serveReplicated(params, opts.host, opts.port, opts.replicas);
223}
224
225// ---------------------------------------------------------------------------
226// Spec parser: "pot:port,pot:host:port,..."
227// ---------------------------------------------------------------------------
228
229std::vector<ServeEndpoint> parseServeSpec(const std::string &spec) {
230 std::vector<ServeEndpoint> endpoints;
231 std::istringstream stream(spec);
232 std::string token;
233
234 while (std::getline(stream, token, ',')) {
235 // Trim whitespace
236 token.erase(0, token.find_first_not_of(" \t"));
237 token.erase(token.find_last_not_of(" \t") + 1);
238 if (token.empty())
239 continue;
240
241 // Parse "potential:port" or "potential:host:port"
242 size_t first_colon = token.find(':');
243 if (first_colon == std::string::npos) {
244 EONC_LOG_ERROR("Invalid serve spec '{}': expected 'potential:port'",
245 token);
246 continue;
247 }
248
249 std::string pot_str = token.substr(0, first_colon);
250 std::string rest = token.substr(first_colon + 1);
251
252 // Trim parts after colon split
253 pot_str.erase(0, pot_str.find_first_not_of(" \t"));
254 pot_str.erase(pot_str.find_last_not_of(" \t") + 1);
255 rest.erase(0, rest.find_first_not_of(" \t"));
256 rest.erase(rest.find_last_not_of(" \t") + 1);
257
258 // Lowercase the potential name
259 std::transform(pot_str.begin(), pot_str.end(), pot_str.begin(), ::tolower);
260
261 ServeEndpoint ep;
262 ep.potential =
263 magic_enum::enum_cast<PotType>(pot_str, magic_enum::case_insensitive)
264 .value_or(PotType::UNKNOWN);
265
266 if (ep.potential == PotType::UNKNOWN) {
267 EONC_LOG_ERROR("Unknown potential type '{}'", pot_str);
268 continue;
269 }
270
271 size_t second_colon = rest.find(':');
272 if (second_colon != std::string::npos) {
273 // "host:port" format
274 ep.host = rest.substr(0, second_colon);
275 ep.host.erase(0, ep.host.find_first_not_of(" \t"));
276 ep.host.erase(ep.host.find_last_not_of(" \t") + 1);
277 std::string port_str = rest.substr(second_colon + 1);
278 port_str.erase(0, port_str.find_first_not_of(" \t"));
279 port_str.erase(port_str.find_last_not_of(" \t") + 1);
280 ep.port = static_cast<uint16_t>(std::stoi(port_str));
281 } else {
282 // "port" only
283 ep.host = "localhost";
284 ep.port = static_cast<uint16_t>(std::stoi(rest));
285 }
286
287 QUILL_LOG_INFO(eonc::log::get(), "Parsed endpoint: {} on {}:{}",
288 std::string(magic_enum::enum_name(ep.potential)), ep.host,
289 ep.port);
290 endpoints.push_back(ep);
291 }
292
293 return endpoints;
294}
295
296} // namespace eonc
#define EONC_LOG_ERROR(...)
Definition EonLogger.h:262
#define EONC_LOG_INFO(...)
Definition EonLogger.h:250
struct eonc::Parameters::potential_options_t potential_options
struct eonc::Parameters::serve_options_t serve_options
std::shared_ptr< Potential > makePotential(const Parameters &params)
quill::Logger * get() noexcept
Get or create the default "combi" logger.
Definition EonLogger.h:44
RAII resource manager for the ARTn C library with global synchronization.
std::vector< ServeEndpoint > parseServeSpec(const std::string &spec)
Parse a serve configuration string into endpoints.
void serveGateway(const Parameters &params, const std::string &host, uint16_t port, size_t pool_size)
Start a gateway server backed by a pool of potential instances.
void serveMode(const Parameters &params, const std::string &host, uint16_t port)
Start a single rgpot-compatible Cap'n Proto RPC server.
Definition ServeMode.cpp:56
void serveFromConfig(const Parameters &params)
Start serve mode from config-file parameters.
std::function< void(long nAtoms, const double *positions, const int *atomicNrs, double *forces, double *energy, const double *box)> ForceCallback
Callback type for potential energy/force evaluation.
void startPooledRpcServer(std::vector< ForceCallback > pool, const std::string &host, uint16_t port)
Start a blocking Cap'n Proto RPC server backed by a pool of force callbacks dispatched round-robin.
void serveMultiple(const std::vector< ServeEndpoint > &endpoints, const Parameters &base_params)
Serve multiple potentials concurrently on different ports.
Definition ServeMode.cpp:79
void serveReplicated(const Parameters &params, const std::string &host, uint16_t base_port, size_t replicas)
Serve N replicas of the same potential across sequential ports.
void startRpcServer(ForceCallback callback, const std::string &host, uint16_t port)
Start a blocking Cap'n Proto RPC server using a force callback.
Configuration for a single serve endpoint.
Definition ServeMode.h:27
std::string host
Definition ServeMode.h:29