ascii-chat 0.6.0
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
rcu.c
Go to the documentation of this file.
1
6#include "options/rcu.h"
7#include "asciichat_errno.h"
8#include "common.h"
9#include "log/logging.h"
11
12#include <stdatomic.h>
13#include <stdlib.h>
14#include <string.h>
15
16// ============================================================================
17// RCU State: Global Atomic Pointer
18// ============================================================================
19
28static _Atomic(options_t *) g_options = NULL;
29
41static mutex_t g_options_write_mutex;
42
46static bool g_options_initialized = false;
47
48// ============================================================================
49// Memory Reclamation Strategy
50// ============================================================================
51
65#define MAX_DEFERRED_FREES 64
66static options_t *g_deferred_frees[MAX_DEFERRED_FREES];
67static size_t g_deferred_free_count = 0;
68static mutex_t g_deferred_free_mutex;
69
75static void deferred_free_add(options_t *old_opts) {
76 if (!old_opts) {
77 return;
78 }
79
80 mutex_lock(&g_deferred_free_mutex);
81
82 if (g_deferred_free_count < MAX_DEFERRED_FREES) {
83 g_deferred_frees[g_deferred_free_count++] = old_opts;
84 log_debug("Added options struct %p to deferred free list (count=%zu)", (void *)old_opts, g_deferred_free_count);
85 } else {
86 // List full - free immediately (risky but unlikely with infrequent updates)
87 log_warn("Deferred free list full (%d entries), freeing options struct %p immediately", MAX_DEFERRED_FREES,
88 (void *)old_opts);
89 SAFE_FREE(old_opts);
90 }
91
92 mutex_unlock(&g_deferred_free_mutex);
93}
94
100static void deferred_free_all(void) {
101 mutex_lock(&g_deferred_free_mutex);
102
103 log_debug("Freeing %zu deferred options structs", g_deferred_free_count);
104
105 for (size_t i = 0; i < g_deferred_free_count; i++) {
106 SAFE_FREE(g_deferred_frees[i]);
107 }
108
109 g_deferred_free_count = 0;
110 mutex_unlock(&g_deferred_free_mutex);
111}
112
113// ============================================================================
114// Public API Implementation
115// ============================================================================
116
118 if (g_options_initialized) {
119 log_warn("Options state already initialized");
120 return ASCIICHAT_OK;
121 }
122
123 // Initialize write mutex
124 if (mutex_init(&g_options_write_mutex) != 0) {
125 return SET_ERRNO(ERROR_THREAD, "Failed to initialize options write mutex");
126 }
127
128 // Initialize deferred free mutex
129 if (mutex_init(&g_deferred_free_mutex) != 0) {
130 mutex_destroy(&g_options_write_mutex);
131 return SET_ERRNO(ERROR_THREAD, "Failed to initialize deferred free mutex");
132 }
133
134 // Allocate initial options struct (will be populated later by options_state_populate_from_globals)
135 options_t *initial_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
136 if (!initial_opts) {
137 mutex_destroy(&g_options_write_mutex);
138 mutex_destroy(&g_deferred_free_mutex);
139 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate initial options struct");
140 }
141
142 // Zero-initialize (all fields start at 0/false/NULL)
143 memset(initial_opts, 0, sizeof(*initial_opts));
144
145 // Set non-zero defaults using defines from options.h
146 // These match the documented defaults in --help output
147
148 // Network defaults
149 SAFE_STRNCPY(initial_opts->port, OPT_PORT_DEFAULT, sizeof(initial_opts->port));
150 SAFE_STRNCPY(initial_opts->address, OPT_ADDRESS_DEFAULT, sizeof(initial_opts->address));
151 SAFE_STRNCPY(initial_opts->address6, OPT_ADDRESS6_DEFAULT, sizeof(initial_opts->address6));
152
153 // Server defaults
154 initial_opts->max_clients = OPT_MAX_CLIENTS_DEFAULT;
156
157 // Client defaults
163
164 // Color and rendering defaults (using zero values which are the correct defaults)
165 initial_opts->color_mode = COLOR_MODE_AUTO; // -1
166
167 // Publish initial struct (release semantics - make all fields visible to readers)
168 atomic_store_explicit(&g_options, initial_opts, memory_order_release);
169
170 g_options_initialized = true;
171 log_debug("Options state initialized with RCU pattern");
172
173 return ASCIICHAT_OK;
174}
175
177 if (!opts) {
178 return SET_ERRNO(ERROR_INVALID_PARAM, "opts is NULL");
179 }
180
181 if (!g_options_initialized) {
182 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized (call options_state_init first)");
183 }
184
185 // Get current struct (should be the initial zero-initialized one during startup)
186 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
187
188 // Copy provided options into current struct
189 // Note: No need for RCU update here since this is called during initialization
190 // before any reader threads exist
191 memcpy(current, opts, sizeof(options_t));
192
193 log_debug("Options state set from parsed struct");
194 return ASCIICHAT_OK;
195}
196
198 if (!g_options_initialized) {
199 return;
200 }
201
202 // Get current options pointer
203 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
204
205 // Clear the atomic pointer (prevent further reads)
206 atomic_store_explicit(&g_options, NULL, memory_order_release);
207
208 // Free current struct
209 SAFE_FREE(current);
210
211 // Free all deferred structs
212 deferred_free_all();
213
214 // Destroy mutexes
215 mutex_destroy(&g_options_write_mutex);
216 mutex_destroy(&g_deferred_free_mutex);
217
218 g_options_initialized = false;
219 log_debug("Options state shutdown complete");
220}
221
222const options_t *options_get(void) {
223 // Lock-free read with acquire semantics
224 // Guarantees we see all writes made before the pointer was published
225 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
226
227 // Should never be NULL after initialization
228 if (!current) {
229 log_fatal("Options not initialized! Call options_state_init() first");
230 log_warn("options_get() called before options initialization - this will cause a crash");
231 abort();
232 }
233
234 return current;
235}
236
237asciichat_error_t options_update(void (*updater)(options_t *, void *), void *context) {
238 if (!updater) {
239 return SET_ERRNO(ERROR_INVALID_PARAM, "updater function is NULL");
240 }
241
242 if (!g_options_initialized) {
243 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized");
244 }
245
246 // Serialize writers with mutex
247 mutex_lock(&g_options_write_mutex);
248
249 // 1. Load current options (acquire semantics)
250 options_t *old_opts = atomic_load_explicit(&g_options, memory_order_acquire);
251
252 // 2. Allocate new options struct
253 options_t *new_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
254 if (!new_opts) {
255 mutex_unlock(&g_options_write_mutex);
256 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate new options struct");
257 }
258
259 // 3. Copy current values to new struct
260 memcpy(new_opts, old_opts, sizeof(options_t));
261
262 // 4. Call user updater to modify the new struct
263 updater(new_opts, context);
264
265 // 5. Atomically swap global pointer (release semantics)
266 // This makes the new struct visible to all readers
267 atomic_store_explicit(&g_options, new_opts, memory_order_release);
268
269 // 6. Add old struct to deferred free list
270 deferred_free_add(old_opts);
271
272 mutex_unlock(&g_options_write_mutex);
273
274 log_debug("Options updated via RCU (old=%p, new=%p)", (void *)old_opts, (void *)new_opts);
275 return ASCIICHAT_OK;
276}
277
278// ============================================================================
279// Convenience Setters
280// ============================================================================
281
282// Helper struct for passing multiple params to updater
284 unsigned short int width;
285 unsigned short int height;
286};
287
288static void dimensions_updater(options_t *opts, void *context) {
289 struct dimensions_update_ctx *ctx = (struct dimensions_update_ctx *)context;
290 opts->width = ctx->width;
291 opts->height = ctx->height;
292}
293
294asciichat_error_t options_set_dimensions(unsigned short int width, unsigned short int height) {
295 struct dimensions_update_ctx ctx = {.width = width, .height = height};
296 return options_update(dimensions_updater, &ctx);
297}
298
299static void color_mode_updater(options_t *opts, void *context) {
301 opts->color_mode = *mode;
302}
303
305 return options_update(color_mode_updater, &mode);
306}
307
308static void render_mode_updater(options_t *opts, void *context) {
309 render_mode_t *mode = (render_mode_t *)context;
310 opts->render_mode = *mode;
311}
312
314 return options_update(render_mode_updater, &mode);
315}
316
317static void log_level_updater(options_t *opts, void *context) {
318 log_level_t *level = (log_level_t *)context;
319 opts->log_level = *level;
320}
321
323 return options_update(log_level_updater, &level);
324}
🔌 Cross-platform abstraction layer umbrella header for ascii-chat
⚠️‼️ Error and/or exit() when things go bad.
#define SAFE_STRNCPY(dst, src, size)
Definition common.h:358
#define SAFE_FREE(ptr)
Definition common.h:320
#define SAFE_MALLOC(size, cast)
Definition common.h:208
#define SET_ERRNO(code, context_msg,...)
Set error code with custom context message and log it.
asciichat_error_t
Error and exit codes - unified status values (0-255)
Definition error_codes.h:46
@ ERROR_INVALID_STATE
@ ERROR_MEMORY
Definition error_codes.h:53
@ ASCIICHAT_OK
Definition error_codes.h:48
@ ERROR_INVALID_PARAM
@ ERROR_THREAD
Definition error_codes.h:95
#define log_warn(...)
Log a WARN message.
#define log_fatal(...)
Log a FATAL message.
log_level_t
Logging levels enumeration.
Definition log/logging.h:59
#define log_debug(...)
Log a DEBUG message.
#define COLOR_MODE_AUTO
Backward compatibility aliases for color mode enum values.
Definition options.h:156
#define OPT_MICROPHONE_INDEX_DEFAULT
Default microphone device index (-1 means system default)
Definition options.h:237
#define OPT_RECONNECT_ATTEMPTS_DEFAULT
Default reconnect attempts (-1 means auto/infinite)
Definition options.h:243
#define OPT_COMPRESSION_LEVEL_DEFAULT
Default compression level (1-9)
Definition options.h:228
#define OPT_ADDRESS6_DEFAULT
Default IPv6 server address.
Definition options.h:222
asciichat_error_t options_set_dimensions(unsigned short int width, unsigned short int height)
Update terminal dimensions.
Definition rcu.c:294
asciichat_error_t options_set_color_mode(terminal_color_mode_t mode)
Update color mode.
Definition rcu.c:304
const options_t * options_get(void)
Get current options (lock-free read)
Definition rcu.c:222
asciichat_error_t options_set_log_level(log_level_t level)
Update log level.
Definition rcu.c:322
#define OPT_PORT_DEFAULT
Default TCP port for client/server communication.
Definition options.h:216
#define OPT_WEBCAM_INDEX_DEFAULT
Default webcam device index.
Definition options.h:234
asciichat_error_t options_set_render_mode(render_mode_t mode)
Update render mode.
Definition rcu.c:313
#define SNAPSHOT_DELAY_DEFAULT
Default snapshot delay in seconds.
Definition options.h:212
asciichat_error_t options_update(void(*updater)(options_t *, void *), void *context)
Update options using copy-on-write (thread-safe)
Definition rcu.c:237
#define OPT_MAX_CLIENTS_DEFAULT
Default maximum concurrent clients (server only)
Definition options.h:225
#define OPT_ADDRESS_DEFAULT
Default server address for client connections.
Definition options.h:219
#define OPT_SPEAKERS_INDEX_DEFAULT
Default speakers device index (-1 means system default)
Definition options.h:240
int mutex_init(mutex_t *mutex)
Initialize a mutex.
#define mutex_lock(mutex)
Lock a mutex (with debug tracking in debug builds)
Definition mutex.h:140
pthread_mutex_t mutex_t
Mutex type (POSIX: pthread_mutex_t)
Definition mutex.h:38
render_mode_t
Render mode preferences.
Definition terminal.h:467
terminal_color_mode_t
Terminal color support levels.
Definition terminal.h:424
#define mutex_unlock(mutex)
Unlock a mutex (with debug tracking in debug builds)
Definition mutex.h:175
int mutex_destroy(mutex_t *mutex)
Destroy a mutex.
📝 Logging API with multiple log levels and terminal output control
void options_state_shutdown(void)
Shutdown RCU options system.
Definition rcu.c:197
asciichat_error_t options_state_init(void)
Initialize RCU options system.
Definition rcu.c:117
#define MAX_DEFERRED_FREES
asciichat_error_t options_state_set(const options_t *opts)
Set options from a parsed options struct.
Definition rcu.c:176
unsigned short int height
Definition rcu.c:285
unsigned short int width
Definition rcu.c:284
Consolidated options structure.
Definition options.h:439
terminal_color_mode_t color_mode
Color mode (auto/none/16/256/truecolor)
Definition options.h:507
char port[256]
Server port number.
Definition options.h:464
int compression_level
zstd compression level (1-9)
Definition options.h:487
int microphone_index
Microphone device index (-1 = default)
Definition options.h:517
unsigned short int webcam_index
Webcam device index (0 = first)
Definition options.h:499
unsigned short int width
Terminal width in characters.
Definition options.h:454
log_level_t log_level
Log level threshold.
Definition options.h:536
unsigned short int height
Terminal height in characters.
Definition options.h:455
int max_clients
Maximum concurrent clients (server only)
Definition options.h:465
render_mode_t render_mode
Render mode (foreground/background/half-block)
Definition options.h:508
char address6[256]
IPv6 bind address (server only)
Definition options.h:463
int speakers_index
Speakers device index (-1 = default)
Definition options.h:518
double snapshot_delay
Snapshot delay in seconds.
Definition options.h:533
int reconnect_attempts
Number of reconnection attempts (-1=infinite, 0=none)
Definition options.h:494
char address[256]
Server address (client) or bind address (server)
Definition options.h:462
Common SIMD utilities and structures.