ascii-chat 0.11.33
Video chat in your terminal
Loading...
Searching...
No Matches
coordinator.c
Go to the documentation of this file.
1
10#include <ascii-chat/log/log.h>
11#include <ascii-chat/common.h>
13#include <string.h>
14
15/* Round scheduling constants */
16#define CONSENSUS_ROUND_INTERVAL_NS (5ULL * 60 * NS_PER_SEC_INT) /* 5 minutes */
17#define CONSENSUS_COLLECTION_DEADLINE_NS (30ULL * NS_PER_SEC_INT) /* 30 seconds */
18
22typedef struct consensus_coordinator {
28
29 /* Round scheduling */
30 uint64_t last_round_start_ns; /* When the last round was initiated */
31 uint32_t next_round_id; /* Next round ID to use */
32 uint64_t collection_deadline_ns; /* Deadline for current collection */
33
34 /* Elected hosts - fallback storage */
39
44 consensus_election_func_t election_func, void *election_context,
45 consensus_coordinator_t **out_coordinator) {
46 if (!my_id || !topology || !election_func || !out_coordinator) {
47 return SET_ERRNO(
48 ERROR_INVALID_PARAM, "Invalid parameter: my_id=%p, topology=%p, election_func=%p, out_coordinator=%p",
49 (const void *)my_id, (const void *)topology, (const void *)election_func, (const void *)out_coordinator);
50 }
51
53 if (!coordinator) {
54 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate coordinator");
55 }
56
57 memset(coordinator, 0, sizeof(*coordinator));
58 memcpy(coordinator->my_id, my_id, 16);
59 coordinator->topology = topology;
60 coordinator->election_func = election_func;
61 coordinator->election_context = election_context;
62
63 /* Initialize state machine */
64 asciichat_error_t err = consensus_state_create(my_id, topology, &coordinator->state);
65 if (err != ASCIICHAT_OK) {
66 SAFE_FREE(coordinator);
67 return err;
68 }
69
70 /* Initialize round scheduling */
71 coordinator->last_round_start_ns = time_get_ns();
72 coordinator->next_round_id = 1;
73 coordinator->has_stored_result = false;
74
75 /* Register coordinator with named registry */
76 char coordinator_name[64];
77 snprintf(coordinator_name, sizeof(coordinator_name), "consensus_coordinator:node_%u", my_id[0]);
78 NAMED_REGISTER(coordinator, coordinator_name, "consensus_coordinator_t", "0x%tx", NULL);
79
80 /* Register child state with coordinator as parent */
81 NAMED_REGISTER(coordinator->state, "state", "consensus_state_t", "0x%tx", (uintptr_t)(const void *)(coordinator));
82
83 log_debug("Coordinator created for node %u, first round in 5 minutes", my_id[0]);
84
85 *out_coordinator = coordinator;
86 return ASCIICHAT_OK;
87}
88
93 if (!coordinator) {
94 return;
95 }
96 consensus_state_destroy(coordinator->state);
97 SAFE_FREE(coordinator);
98}
99
103static bool should_start_new_round(consensus_coordinator_t *coordinator, uint64_t now_ns) {
104 if (!coordinator || !coordinator->state) {
105 return false;
106 }
107
109 if (current_state != CONSENSUS_STATE_IDLE) {
110 return false;
111 }
112
113 uint64_t time_since_last_round = time_elapsed_ns(coordinator->last_round_start_ns, now_ns);
114 return time_since_last_round >= CONSENSUS_ROUND_INTERVAL_NS;
115}
116
120static asciichat_error_t measure_and_add_metrics(consensus_coordinator_t *coordinator) {
121 participant_metrics_t metrics;
122 asciichat_error_t err = consensus_metrics_measure(coordinator->my_id, &metrics);
123 if (err != ASCIICHAT_OK) {
124 return err;
125 }
126
127 err = consensus_state_add_metrics(coordinator->state, &metrics);
128 if (err != ASCIICHAT_OK) {
129 return err;
130 }
131
132 log_debug("Added own metrics to collection");
133 return ASCIICHAT_OK;
134}
135
139static asciichat_error_t start_collection_round(consensus_coordinator_t *coordinator, uint64_t now_ns) {
140 /* Transition state machine */
142 if (err != ASCIICHAT_OK) {
143 return err;
144 }
145
146 coordinator->last_round_start_ns = now_ns;
148 coordinator->next_round_id++;
149
150 log_info("Starting collection round %u, deadline in 30 seconds", coordinator->next_round_id - 1);
151
152 /* Measure our own metrics immediately */
153 err = measure_and_add_metrics(coordinator);
154 if (err != ASCIICHAT_OK) {
155 return err;
156 }
157
158 return ASCIICHAT_OK;
159}
160
164static bool has_collection_timed_out(consensus_coordinator_t *coordinator, uint64_t now_ns) {
166 if (current_state != CONSENSUS_STATE_COLLECTING) {
167 return false;
168 }
169
170 return now_ns >= coordinator->collection_deadline_ns;
171}
172
176static asciichat_error_t complete_collection(consensus_coordinator_t *coordinator) {
178 if (err != ASCIICHAT_OK) {
179 return err;
180 }
181
183
184 if (current_state == CONSENSUS_STATE_ELECTION_START) {
185 /* Leader: Run election */
186 log_info("Leader: Computing election from %d metrics", consensus_state_get_metrics_count(coordinator->state));
187
188 err = consensus_state_compute_election(coordinator->state);
189 if (err != ASCIICHAT_OK) {
190 return err;
191 }
192
193 /* Optionally run election callback */
194 if (coordinator->election_func) {
195 err = coordinator->election_func(coordinator->election_context, coordinator->state);
196 if (err != ASCIICHAT_OK) {
197 log_warn("Election callback returned error: %d", err);
198 }
199 }
200
201 /* Store elected hosts */
202 uint8_t host_id[16], backup_id[16];
203 err = consensus_state_get_elected_host(coordinator->state, host_id);
204 if (err == ASCIICHAT_OK) {
205 err = consensus_state_get_elected_backup(coordinator->state, backup_id);
206 }
207 if (err == ASCIICHAT_OK) {
208 memcpy(coordinator->stored_host_id, host_id, 16);
209 memcpy(coordinator->stored_backup_id, backup_id, 16);
210 coordinator->has_stored_result = true;
211 log_info("Election complete: host=%u, backup=%u", host_id[0], backup_id[0]);
212 }
213 }
214
215 return ASCIICHAT_OK;
216}
217
222 (void)timeout_ms; /* Currently unused, but kept for future use */
223
224 if (!coordinator) {
225 return SET_ERRNO(ERROR_INVALID_PARAM, "Coordinator is NULL");
226 }
227
228 uint64_t now_ns = time_get_ns();
229
230 /* Check if we should start a new round */
231 if (should_start_new_round(coordinator, now_ns)) {
232 /* Only leader starts rounds */
233 if (consensus_topology_am_leader(coordinator->topology)) {
234 asciichat_error_t err = start_collection_round(coordinator, now_ns);
235 if (err != ASCIICHAT_OK) {
236 log_warn("Failed to start collection round: %d", err);
237 return err;
238 }
239 }
240 }
241
243
244 /* Check for collection timeout */
245 if (current_state == CONSENSUS_STATE_COLLECTING && has_collection_timed_out(coordinator, now_ns)) {
246 log_warn("Collection round timed out, completing early");
247 asciichat_error_t err = complete_collection(coordinator);
248 if (err != ASCIICHAT_OK) {
249 log_error("Failed to complete collection on timeout: %d", err);
250 }
251 }
252
253 return ASCIICHAT_OK;
254}
255
260 const consensus_topology_t *new_topology) {
261 if (!coordinator || !new_topology) {
262 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameter");
263 }
264
265 /* Update topology reference */
266 coordinator->topology = new_topology;
267
268 /* Reset state if we're in the middle of a round */
270 if (current_state != CONSENSUS_STATE_IDLE) {
271 log_warn("Ring topology changed during round, resetting state");
272 consensus_state_destroy(coordinator->state);
273 coordinator->state = NULL;
274
275 asciichat_error_t err = consensus_state_create(coordinator->my_id, new_topology, &coordinator->state);
276 if (err != ASCIICHAT_OK) {
277 return err;
278 }
279 }
280
281 log_info("Ring topology updated");
282 return ASCIICHAT_OK;
283}
284
289 uint64_t deadline_ns) {
290 if (!coordinator) {
291 return SET_ERRNO(ERROR_INVALID_PARAM, "Coordinator is NULL");
292 }
293
295 if (current_state != CONSENSUS_STATE_IDLE) {
296 return SET_ERRNO(ERROR_INVALID_STATE, "Cannot start collection, state is %d", current_state);
297 }
298
299 /* Start collection */
301 if (err != ASCIICHAT_OK) {
302 return err;
303 }
304
305 coordinator->next_round_id = round_id;
306 coordinator->collection_deadline_ns = deadline_ns;
307
308 /* Measure and add our metrics */
309 err = measure_and_add_metrics(coordinator);
310 if (err != ASCIICHAT_OK) {
311 log_warn("Failed to measure metrics: %d", err);
312 }
313
314 log_dev("Collection started: round_id=%u, deadline in %u seconds", round_id,
315 (unsigned int)((deadline_ns - time_get_ns()) / NS_PER_SEC_INT));
316
317 return ASCIICHAT_OK;
318}
319
324 const uint8_t sender_id[16],
325 const participant_metrics_t *metrics, uint8_t num_metrics) {
326 if (!coordinator || !sender_id || !metrics) {
327 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameter");
328 }
329
331 if (current_state != CONSENSUS_STATE_COLLECTING) {
332 return SET_ERRNO(ERROR_INVALID_STATE, "Cannot accept metrics, state is %d", current_state);
333 }
334
335 /* Add all metrics to state */
336 for (int i = 0; i < num_metrics; i++) {
337 asciichat_error_t err = consensus_state_add_metrics(coordinator->state, &metrics[i]);
338 if (err != ASCIICHAT_OK) {
339 log_warn("Failed to add metric %d: %d", i, err);
340 }
341 }
342
343 log_debug("Received %d metrics from sender %u", num_metrics, sender_id[0]);
344
345 return ASCIICHAT_OK;
346}
347
352 const uint8_t host_id[16], const uint8_t backup_id[16]) {
353 if (!coordinator || !host_id || !backup_id) {
354 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameter");
355 }
356
357 /* Store the elected host and backup */
358 memcpy(coordinator->stored_host_id, host_id, 16);
359 memcpy(coordinator->stored_backup_id, backup_id, 16);
360 coordinator->has_stored_result = true;
361
362 log_info("Election result received: host=%u, backup=%u", host_id[0], backup_id[0]);
363
364 /* Transition state back to IDLE */
366 if (current_state == CONSENSUS_STATE_ELECTION_COMPLETE) {
368 if (err != ASCIICHAT_OK) {
369 log_warn("Failed to reset state to IDLE: %d", err);
370 }
371 }
372
373 return ASCIICHAT_OK;
374}
375
380 uint8_t out_host_id[16], uint8_t out_backup_id[16]) {
381 if (!coordinator || !out_host_id || !out_backup_id) {
382 return SET_ERRNO(ERROR_INVALID_PARAM, "Invalid parameter");
383 }
384
385 /* Try to get from current state first */
387 if (current_state == CONSENSUS_STATE_ELECTION_COMPLETE) {
388 asciichat_error_t err = consensus_state_get_elected_host(coordinator->state, out_host_id);
389 if (err == ASCIICHAT_OK) {
390 err = consensus_state_get_elected_backup(coordinator->state, out_backup_id);
391 }
392 if (err == ASCIICHAT_OK) {
393 return ASCIICHAT_OK;
394 }
395 }
396
397 /* Fall back to stored result */
398 if (coordinator->has_stored_result) {
399 memcpy(out_host_id, coordinator->stored_host_id, 16);
400 memcpy(out_backup_id, coordinator->stored_backup_id, 16);
401 return ASCIICHAT_OK;
402 }
403
404 return SET_ERRNO(ERROR_INVALID_STATE, "No election result available");
405}
406
411 if (!coordinator || !coordinator->state) {
413 }
414 return consensus_state_get_current_state(coordinator->state);
415}
416
421 if (!coordinator) {
422 return 0;
423 }
424
425 uint64_t now_ns = time_get_ns();
426 uint64_t next_round_ns = coordinator->last_round_start_ns + CONSENSUS_ROUND_INTERVAL_NS;
427
428 if (now_ns >= next_round_ns) {
429 return 0;
430 }
431
432 return next_round_ns - now_ns;
433}
434
439 if (!coordinator || !coordinator->state) {
440 return -1;
441 }
442 return consensus_state_get_metrics_count(coordinator->state);
443}
⚙️ Common definitions, error codes, macros, and types shared throughout the application
asciichat_error_t consensus_coordinator_on_stats_update(consensus_coordinator_t *coordinator, const uint8_t sender_id[16], const participant_metrics_t *metrics, uint8_t num_metrics)
Handle STATS_UPDATE packet.
void consensus_coordinator_destroy(consensus_coordinator_t *coordinator)
Clean up coordinator resources.
Definition coordinator.c:92
#define CONSENSUS_COLLECTION_DEADLINE_NS
Definition coordinator.c:17
int consensus_coordinator_get_metrics_count(const consensus_coordinator_t *coordinator)
Get count of metrics in current round.
asciichat_error_t consensus_coordinator_on_ring_members(consensus_coordinator_t *coordinator, const consensus_topology_t *new_topology)
Update ring topology when participants change.
struct consensus_coordinator consensus_coordinator_t
Internal coordinator structure.
asciichat_error_t consensus_coordinator_process(consensus_coordinator_t *coordinator, uint32_t timeout_ms)
Main orchestration loop.
uint64_t consensus_coordinator_time_until_next_round(const consensus_coordinator_t *coordinator)
Get time until next round.
consensus_state_machine_t consensus_coordinator_get_state(const consensus_coordinator_t *coordinator)
Get current coordinator state.
asciichat_error_t consensus_coordinator_get_current_host(const consensus_coordinator_t *coordinator, uint8_t out_host_id[16], uint8_t out_backup_id[16])
Get currently elected host.
asciichat_error_t consensus_coordinator_on_collection_start(consensus_coordinator_t *coordinator, uint32_t round_id, uint64_t deadline_ns)
Handle STATS_COLLECTION_START packet.
asciichat_error_t consensus_coordinator_on_election_result(consensus_coordinator_t *coordinator, const uint8_t host_id[16], const uint8_t backup_id[16])
Handle ELECTION_RESULT packet.
asciichat_error_t consensus_coordinator_create(const uint8_t my_id[16], const consensus_topology_t *topology, consensus_election_func_t election_func, void *election_context, consensus_coordinator_t **out_coordinator)
Initialize coordinator with given parameters.
Definition coordinator.c:43
#define CONSENSUS_ROUND_INTERVAL_NS
Definition coordinator.c:16
Ring consensus coordinator orchestration.
asciichat_error_t(* consensus_election_func_t)(void *context, consensus_state_t *state)
Election callback function type.
Definition coordinator.h:35
Named object registry for debugging — log identifiable resource names.
Deterministic host election algorithm.
unsigned int uint32_t
Definition common.h:58
#define SAFE_FREE(ptr)
Definition common.h:376
#define SAFE_MALLOC(size, cast)
Definition common.h:264
unsigned long long uint64_t
Definition common.h:59
unsigned char uint8_t
Definition common.h:56
#define NAMED_REGISTER(ptr, name, type, fmt, parent_ptr)
Register any pointer with base name, type, format spec, and location (auto-suffix)
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it, returning the error code.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:49
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:56
@ ASCIICHAT_OK
Definition error_codes.h:51
@ ERROR_INVALID_PARAM
#define log_warn(...)
Log a WARN message.
Definition log/log.h:574
#define log_dev(...)
Log a DEV message (most verbose, development only)
Definition log/log.h:534
#define log_error(...)
Log an ERROR message.
Definition log/log.h:587
#define log_info(...)
Log an INFO message.
Definition log/log.h:561
#define log_debug(...)
Log a DEBUG message.
Definition log/log.h:548
uint64_t time_get_ns(void)
Get current monotonic time in nanoseconds.
Definition util/time.c:108
#define NS_PER_SEC_INT
Definition time.h:157
uint64_t time_elapsed_ns(uint64_t start_ns, uint64_t end_ns)
Calculate elapsed time with wraparound safety.
Definition util/time.c:150
📝 Logging API with multiple log levels and terminal output control
asciichat_error_t consensus_metrics_measure(const uint8_t my_id[16], participant_metrics_t *out_metrics)
Measure participant network quality metrics.
Definition metrics.c:86
Ring consensus metrics collection and wire protocol.
asciichat_error_t consensus_state_start_collection(consensus_state_t *state)
Transition to COLLECTION_START state.
Definition state.c:83
asciichat_error_t consensus_state_compute_election(consensus_state_t *state)
Run election algorithm and transition to ELECTION_COMPLETE.
Definition state.c:144
asciichat_error_t consensus_state_get_elected_backup(const consensus_state_t *state, uint8_t out_backup_id[16])
Get elected backup host ID from last election.
Definition state.c:230
void consensus_state_destroy(consensus_state_t *state)
Destroy state machine and free resources.
Definition state.c:74
asciichat_error_t consensus_state_get_elected_host(const consensus_state_t *state, uint8_t out_host_id[16])
Get elected host ID from last election.
Definition state.c:217
asciichat_error_t consensus_state_reset_to_idle(consensus_state_t *state)
Transition back to IDLE from ELECTION_COMPLETE.
Definition state.c:197
asciichat_error_t consensus_state_collection_complete(consensus_state_t *state)
Transition to COLLECTION_COMPLETE state.
Definition state.c:125
int consensus_state_get_metrics_count(const consensus_state_t *state)
Get count of metrics collected in current round.
Definition state.c:250
asciichat_error_t consensus_state_create(const uint8_t my_id[16], const consensus_topology_t *topology, consensus_state_t **out_state)
Initialize state machine with topology.
Definition state.c:50
consensus_state_machine_t consensus_state_get_current_state(const consensus_state_t *state)
Get current state machine state.
Definition state.c:210
asciichat_error_t consensus_state_add_metrics(consensus_state_t *state, const participant_metrics_t *metrics)
Add collected metrics from a participant.
Definition state.c:100
consensus_state_machine_t
State machine lifecycle states.
Definition state.h:18
@ CONSENSUS_STATE_ELECTION_COMPLETE
Definition state.h:25
@ CONSENSUS_STATE_FAILED
Definition state.h:26
@ CONSENSUS_STATE_ELECTION_START
Definition state.h:23
@ CONSENSUS_STATE_IDLE
Definition state.h:19
@ CONSENSUS_STATE_COLLECTING
Definition state.h:21
Internal coordinator structure.
Definition coordinator.c:22
uint64_t collection_deadline_ns
Definition coordinator.c:32
uint64_t last_round_start_ns
Definition coordinator.c:30
uint8_t stored_host_id[16]
Definition coordinator.c:35
uint8_t stored_backup_id[16]
Definition coordinator.c:36
const consensus_topology_t * topology
Definition coordinator.c:24
consensus_election_func_t election_func
Definition coordinator.c:26
consensus_state_t * state
Definition coordinator.c:25
State machine instance.
Definition state.c:24
Ring topology for consensus participants.
Definition topology.c:17
⏱️ High-precision timing utilities using sokol_time.h and uthash
bool consensus_topology_am_leader(const consensus_topology_t *topology)
Check if I am the ring leader (last position)
Definition topology.c:76