ascii-chat 0.8.38
Real-time terminal-based video chat with ASCII art conversion
Loading...
Searching...
No Matches
rcu.c File Reference

Go to the source code of this file.

Data Structures

struct  int_field_ctx_t
 Context for integer field updates in RCU updater callback. More...
 
struct  bool_field_ctx_t
 Context for boolean field updates in RCU updater callback. More...
 
struct  string_field_ctx_t
 Context for string field updates in RCU updater callback. More...
 
struct  double_field_ctx_t
 Context for double field updates in RCU updater callback. More...
 

Macros

#define MAX_DEFERRED_FREES   64
 Deferred free list for old options structs.
 

Functions

 __attribute__ ((constructor))
 Constructor to initialize fork handlers at startup.
 
asciichat_error_t options_state_init (void)
 
asciichat_error_t options_state_set (const options_t *opts)
 
void options_state_destroy (void)
 
void options_cleanup_schema (void)
 
const options_t * options_get (void)
 
asciichat_error_t options_set_int (const char *field_name, int value)
 
asciichat_error_t options_set_bool (const char *field_name, bool value)
 
asciichat_error_t options_set_string (const char *field_name, const char *value)
 
asciichat_error_t options_set_double (const char *field_name, double value)
 

Macro Definition Documentation

◆ MAX_DEFERRED_FREES

#define MAX_DEFERRED_FREES   64

Deferred free list for old options structs.

We can't immediately free old structs because readers might still be using them. Simple strategy: Keep old pointers in a list, free them at shutdown.

Future improvements:

  • Hazard pointers: Track which structs readers are using
  • Epoch-based reclamation: Free after grace period
  • Reference counting: Free when no readers

Current approach: Never free until shutdown (acceptable for rare updates)

Definition at line 132 of file rcu.c.

Function Documentation

◆ __attribute__()

__attribute__ ( (constructor)  )

Constructor to initialize fork handlers at startup.

This is called before main() by the linker, ensuring fork handlers are registered early.

Definition at line 208 of file rcu.c.

208 {
209 // Register the child handler that will be called after fork() in the child process
210 pthread_atfork(NULL, NULL, options_rcu_atfork_child);
211}

◆ options_cleanup_schema()

void options_cleanup_schema ( void  )

Definition at line 343 of file rcu.c.

343 {
345}
void config_schema_destroy(void)
Definition schema.c:446

References config_schema_destroy().

Referenced by main().

◆ options_get()

const options_t * options_get ( void  )

Definition at line 347 of file rcu.c.

347 {
348 // Lock-free read with acquire semantics
349 // Guarantees we see all writes made before the pointer was published
350 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
351
352 // If options not yet published, return safe static default instead of crashing
353 // This allows atexit handlers to safely call GET_OPTION() during cleanup
354 if (!current) {
355 // Return static default - safe for atexit handlers to read
356 return (const options_t *)&g_default_options;
357 }
358
359 return current;
360}

Referenced by acds_main(), client_crypto_init(), client_main(), discovery_session_process(), log_set_terminal_output(), main(), nat_detect_quality(), server_crypto_handshake(), session_settings_from_options(), and threaded_send_terminal_size_with_auto_detect().

◆ options_set_bool()

asciichat_error_t options_set_bool ( const char *  field_name,
bool  value 
)

Definition at line 562 of file rcu.c.

562 {
563 if (!field_name) {
564 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
565 return ERROR_INVALID_PARAM;
566 }
567
568 // Validate field exists
569 if (strcmp(field_name, "no_compress") != 0 && strcmp(field_name, "encode_audio") != 0 &&
570 strcmp(field_name, "flip_x") != 0 && strcmp(field_name, "flip_y") != 0 &&
571 strcmp(field_name, "test_pattern") != 0 && strcmp(field_name, "no_audio_mixer") != 0 &&
572 strcmp(field_name, "show_capabilities") != 0 && strcmp(field_name, "force_utf8") != 0 &&
573 strcmp(field_name, "audio_enabled") != 0 && strcmp(field_name, "audio_analysis_enabled") != 0 &&
574 strcmp(field_name, "audio_no_playback") != 0 && strcmp(field_name, "stretch") != 0 &&
575 strcmp(field_name, "snapshot_mode") != 0 && strcmp(field_name, "strip_ansi") != 0 &&
576 strcmp(field_name, "quiet") != 0 && strcmp(field_name, "encrypt_enabled") != 0 &&
577 strcmp(field_name, "no_encrypt") != 0 && strcmp(field_name, "discovery") != 0 &&
578 strcmp(field_name, "discovery_expose_ip") != 0 && strcmp(field_name, "discovery_insecure") != 0 &&
579 strcmp(field_name, "webrtc") != 0 && strcmp(field_name, "lan_discovery") != 0 &&
580 strcmp(field_name, "no_mdns_advertise") != 0 && strcmp(field_name, "prefer_webrtc") != 0 &&
581 strcmp(field_name, "no_webrtc") != 0 && strcmp(field_name, "webrtc_skip_stun") != 0 &&
582 strcmp(field_name, "webrtc_disable_turn") != 0 && strcmp(field_name, "enable_upnp") != 0 &&
583 strcmp(field_name, "require_server_identity") != 0 && strcmp(field_name, "require_client_identity") != 0 &&
584 strcmp(field_name, "require_server_verify") != 0 && strcmp(field_name, "require_client_verify") != 0 &&
585 strcmp(field_name, "palette_custom_set") != 0 && strcmp(field_name, "media_loop") != 0 &&
586 strcmp(field_name, "media_from_stdin") != 0 && strcmp(field_name, "auto_width") != 0 &&
587 strcmp(field_name, "auto_height") != 0 && strcmp(field_name, "splash") != 0 &&
588 strcmp(field_name, "status_screen") != 0 && strcmp(field_name, "matrix_rain") != 0) {
589 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown boolean field: %s", field_name);
590 return ERROR_INVALID_PARAM;
591 }
592
593 bool_field_ctx_t ctx = {.field_name = field_name, .value = value};
594 return options_update(bool_field_updater, &ctx);
595}
Context for boolean field updates in RCU updater callback.
Definition rcu.c:473
const char * field_name
Name of the field to update.
Definition rcu.c:474

References bool_field_ctx_t::field_name.

Referenced by client_main(), session_handle_keyboard_input(), set_flip_x(), and set_matrix_rain().

◆ options_set_double()

asciichat_error_t options_set_double ( const char *  field_name,
double  value 
)

Definition at line 695 of file rcu.c.

695 {
696 if (!field_name) {
697 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
698 return ERROR_INVALID_PARAM;
699 }
700
701 // Normalize option names to internal field names
702 const char *internal_name = field_name;
703 if (strcmp(field_name, "microphone-volume") == 0 || strcmp(field_name, "ivolume") == 0) {
704 internal_name = "microphone_sensitivity";
705 } else if (strcmp(field_name, "speakers-volume") == 0 || strcmp(field_name, "volume") == 0) {
706 internal_name = "speakers_volume";
707 }
708
709 if (strcmp(internal_name, "snapshot_delay") != 0 && strcmp(internal_name, "microphone_sensitivity") != 0 &&
710 strcmp(internal_name, "speakers_volume") != 0) {
711 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown double field: %s", field_name);
712 return ERROR_INVALID_PARAM;
713 }
714
715 double_field_ctx_t ctx = {.field_name = internal_name, .value = value};
716 return options_update(double_field_updater, &ctx);
717}
Context for double field updates in RCU updater callback.
Definition rcu.c:680
const char * field_name
Name of the field to update.
Definition rcu.c:681

References double_field_ctx_t::field_name.

Referenced by session_handle_keyboard_input().

◆ options_set_int()

asciichat_error_t options_set_int ( const char *  field_name,
int  value 
)

Definition at line 449 of file rcu.c.

449 {
450 if (!field_name) {
451 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
452 return ERROR_INVALID_PARAM;
453 }
454
455 if (strcmp(field_name, "width") != 0 && strcmp(field_name, "height") != 0 && strcmp(field_name, "max_clients") != 0 &&
456 strcmp(field_name, "compression_level") != 0 && strcmp(field_name, "reconnect_attempts") != 0 &&
457 strcmp(field_name, "microphone_index") != 0 && strcmp(field_name, "speakers_index") != 0 &&
458 strcmp(field_name, "discovery_port") != 0 && strcmp(field_name, "port") != 0 && strcmp(field_name, "fps") != 0 &&
459 strcmp(field_name, "color_mode") != 0 && strcmp(field_name, "color_filter") != 0 &&
460 strcmp(field_name, "render_mode") != 0 && strcmp(field_name, "log_level") != 0 &&
461 strcmp(field_name, "palette_type") != 0) {
462 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown integer field: %s", field_name);
463 return ERROR_INVALID_PARAM;
464 }
465
466 int_field_ctx_t ctx = {.field_name = field_name, .value = value};
467 return options_update(int_field_updater, &ctx);
468}
Context for integer field updates in RCU updater callback.
Definition rcu.c:410
const char * field_name
Name of the field to update.
Definition rcu.c:411

References int_field_ctx_t::field_name.

Referenced by session_handle_keyboard_input(), session_settings_apply_to_options(), set_color_filter(), set_color_mode(), set_height(), set_palette(), set_render_mode(), set_target_fps(), and set_width().

◆ options_set_string()

asciichat_error_t options_set_string ( const char *  field_name,
const char *  value 
)

Definition at line 647 of file rcu.c.

647 {
648 if (!field_name) {
649 SET_ERRNO(ERROR_INVALID_PARAM, "field_name is NULL");
650 return ERROR_INVALID_PARAM;
651 }
652
653 if (!value) {
654 SET_ERRNO(ERROR_INVALID_PARAM, "value is NULL");
655 return ERROR_INVALID_PARAM;
656 }
657
658 // Validate field exists
659 if (strcmp(field_name, "address") != 0 && strcmp(field_name, "address6") != 0 &&
660 strcmp(field_name, "encrypt_key") != 0 && strcmp(field_name, "password") != 0 &&
661 strcmp(field_name, "encrypt_keyfile") != 0 && strcmp(field_name, "server_key") != 0 &&
662 strcmp(field_name, "client_keys") != 0 && strcmp(field_name, "discovery_server") != 0 &&
663 strcmp(field_name, "discovery_service_key") != 0 && strcmp(field_name, "discovery_database_path") != 0 &&
664 strcmp(field_name, "log_file") != 0 && strcmp(field_name, "media_file") != 0 &&
665 strcmp(field_name, "palette_custom") != 0 && strcmp(field_name, "stun_servers") != 0 &&
666 strcmp(field_name, "turn_servers") != 0 && strcmp(field_name, "turn_username") != 0 &&
667 strcmp(field_name, "turn_credential") != 0 && strcmp(field_name, "turn_secret") != 0 &&
668 strcmp(field_name, "session_string") != 0) {
669 SET_ERRNO(ERROR_INVALID_PARAM, "Unknown string field: %s", field_name);
670 return ERROR_INVALID_PARAM;
671 }
672
673 string_field_ctx_t ctx = {.field_name = field_name, .value = value};
674 return options_update(string_field_updater, &ctx);
675}
Context for string field updates in RCU updater callback.
Definition rcu.c:600
const char * field_name
Name of the field to update.
Definition rcu.c:601

References string_field_ctx_t::field_name.

Referenced by set_palette_chars().

◆ options_state_destroy()

void options_state_destroy ( void  )

Definition at line 309 of file rcu.c.

309 {
310 // Check and clear initialization flag under lock
311 static_mutex_lock(&g_options_init_mutex);
312 if (!g_options_initialized) {
313 static_mutex_unlock(&g_options_init_mutex);
314 return;
315 }
316 g_options_initialized = false; // Clear flag first to prevent new operations
317 static_mutex_unlock(&g_options_init_mutex);
318
319 // Get current options pointer
320 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
321
322 // Clear the atomic pointer (prevent further reads)
323 atomic_store_explicit(&g_options, NULL, memory_order_release);
324
325 // Free current struct
326 SAFE_FREE(current);
327
328 // Free all deferred structs
329 deferred_free_all();
330
331 // Cleanup schema (frees all dynamically allocated config strings)
333
334 // Destroy dynamically created mutexes
335 // NOTE: Do NOT destroy g_options_init_mutex - it's statically initialized
336 // and needs to persist across shutdown/init cycles (especially in tests)
337 mutex_destroy(&g_options_write_mutex);
338 mutex_destroy(&g_deferred_free_mutex);
339
340 log_debug("Options state shutdown complete");
341}
int mutex_destroy(mutex_t *mutex)
Definition threading.c:21

References config_schema_destroy(), and mutex_destroy().

Referenced by asciichat_shared_destroy(), client_cleanup(), main(), mirror_cleanup(), and server_main().

◆ options_state_init()

asciichat_error_t options_state_init ( void  )

Definition at line 218 of file rcu.c.

218 {
219 // Detect fork: after fork(), child process must reinitialize mutexes BEFORE locking
220 // The inherited mutex is in an inconsistent state and can cause deadlocks/signals
221 // We must reset it before calling static_mutex_lock
222 pid_t current_pid = platform_get_pid();
223 if ((g_options_initialized || g_init_pid != -1) && g_init_pid != current_pid) {
224// We're in a forked child - reset the static mutex state before using it
225// On POSIX, set initialized=0 so static_mutex_lock will reinitialize it
226// On Windows, the InterlockedCompareExchange logic handles this automatically
227#ifndef _WIN32
228 g_options_init_mutex.initialized = 0; // Force reinitialization on next lock
229#else
230 g_options_init_mutex.initialized = 0; // Same approach for Windows
231#endif
232 log_debug("Detected fork in options_state_init (parent PID %d, child PID %d) - resetting mutex", g_init_pid,
233 current_pid);
234 g_options_initialized = false;
235 g_init_pid = current_pid; // Set to current PID so we know we're initialized in this process
236 }
237
238 static_mutex_lock(&g_options_init_mutex);
239
240 // Check if already initialized in this process
241 if (g_options_initialized && g_init_pid == current_pid) {
242 // Already initialized in this process
243 static_mutex_unlock(&g_options_init_mutex);
244 log_warn("Options state already initialized");
245 return ASCIICHAT_OK;
246 }
247
248 // Initialize write mutex
249 if (mutex_init(&g_options_write_mutex) != 0) {
250 static_mutex_unlock(&g_options_init_mutex);
251 return SET_ERRNO(ERROR_THREAD, "Failed to initialize options write mutex");
252 }
253
254 // Initialize deferred free mutex
255 if (mutex_init(&g_deferred_free_mutex) != 0) {
256 mutex_destroy(&g_options_write_mutex);
257 static_mutex_unlock(&g_options_init_mutex);
258 return SET_ERRNO(ERROR_THREAD, "Failed to initialize deferred free mutex");
259 }
260
261 // Allocate initial options struct (will be populated later by options_state_populate_from_globals)
262 options_t *initial_opts = SAFE_MALLOC(sizeof(options_t), options_t *);
263 if (!initial_opts) {
264 mutex_destroy(&g_options_write_mutex);
265 mutex_destroy(&g_deferred_free_mutex);
266 static_mutex_unlock(&g_options_init_mutex);
267 return SET_ERRNO(ERROR_MEMORY, "Failed to allocate initial options struct");
268 }
269
270 // Initialize all defaults using options_t_new()
271 *initial_opts = options_t_new();
272
273 // Publish initial struct (release semantics - make all fields visible to readers)
274 atomic_store_explicit(&g_options, initial_opts, memory_order_release);
275
276 g_options_initialized = true;
277 g_init_pid = current_pid; // Record PID to detect fork later
278 static_mutex_unlock(&g_options_init_mutex);
279 log_dev("Options state initialized with RCU pattern (PID %d)", current_pid);
280
281 return ASCIICHAT_OK;
282}
options_t options_t_new(void)
int mutex_init(mutex_t *mutex)
Definition threading.c:16
pid_t platform_get_pid(void)
Definition wasm/system.c:35

References mutex_destroy(), mutex_init(), options_t_new(), and platform_get_pid().

Referenced by __attribute__(), and options_init().

◆ options_state_set()

asciichat_error_t options_state_set ( const options_t *  opts)

Definition at line 284 of file rcu.c.

284 {
285 if (!opts) {
286 return SET_ERRNO(ERROR_INVALID_PARAM, "opts is NULL");
287 }
288
289 // Check initialization flag under lock to prevent TOCTOU race
290 static_mutex_lock(&g_options_init_mutex);
291 if (!g_options_initialized) {
292 static_mutex_unlock(&g_options_init_mutex);
293 return SET_ERRNO(ERROR_INVALID_STATE, "Options state not initialized (call options_state_init first)");
294 }
295 static_mutex_unlock(&g_options_init_mutex);
296
297 // Get current struct (should be the initial zero-initialized one during startup)
298 options_t *current = atomic_load_explicit(&g_options, memory_order_acquire);
299
300 // Copy provided options into current struct
301 // Note: No need for RCU update here since this is called during initialization
302 // before any reader threads exist
303 memcpy(current, opts, sizeof(options_t));
304
305 log_debug("Options state set from parsed struct");
306 return ASCIICHAT_OK;
307}

Referenced by config_load_and_apply(), and options_init().