From ae9a754173fe901136e7b176578c80281e6058a2 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 21 Apr 2026 13:22:33 -0700 Subject: [PATCH 01/70] Refactor: libcrmcommon: Use bool for crm_trigger_t:running Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 823c202f27c..3845243638b 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -25,7 +25,7 @@ struct trigger_s { GSource source; - gboolean running; + bool running; gboolean trigger; void *user_data; unsigned int id; @@ -118,7 +118,7 @@ crm_trigger_dispatch(GSource *source, GSourceFunc callback, void *userdata) if (callback_rc < 0) { pcmk__trace("Trigger handler %p not yet complete", trig); - trig->running = TRUE; + trig->running = true; } else if (callback_rc == 0) { rc = G_SOURCE_REMOVE; } @@ -166,7 +166,7 @@ void mainloop_trigger_complete(crm_trigger_t * trig) { pcmk__trace("Trigger handler %p complete", trig); - trig->running = FALSE; + trig->running = false; } /*! From e8809142e0d1b36f40d1432b47946d5f6ef71986 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 21 Apr 2026 13:24:31 -0700 Subject: [PATCH 02/70] Refactor: libcrmcommon: Use bool for crm_trigger_t:trigger Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 3845243638b..4693f648536 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -26,7 +26,7 @@ struct trigger_s { GSource source; bool running; - gboolean trigger; + bool trigger; void *user_data; unsigned int id; }; @@ -111,7 +111,7 @@ crm_trigger_dispatch(GSource *source, GSourceFunc callback, void *userdata) /* Wait until the existing job is complete before starting the next one */ return G_SOURCE_CONTINUE; } - trig->trigger = FALSE; + trig->trigger = false; if (callback) { int callback_rc = callback(trig->user_data); @@ -148,7 +148,7 @@ mainloop_setup_trigger(GSource * source, int priority, trigger = (crm_trigger_t *) source; trigger->id = 0; - trigger->trigger = FALSE; + trigger->trigger = false; trigger->user_data = userdata; if (dispatch) { @@ -197,7 +197,7 @@ void mainloop_set_trigger(crm_trigger_t * source) { if(source) { - source->trigger = TRUE; + source->trigger = true; } } @@ -258,7 +258,7 @@ crm_signal_dispatch(GSource *source, GSourceFunc callback, void *userdata) ((sig->handler != NULL)? "invoking" : "no")); } - sig->trigger.trigger = FALSE; + sig->trigger.trigger = false; if (sig->handler) { sig->handler(sig->signal); } From 624f79ba10fcdf89364d1ae50d2e62115019a798 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 02:49:58 -0700 Subject: [PATCH 03/70] Refactor: libcrmcommon: Best practices for child_free() Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 19 ++++++++++++++----- 1 file changed, 14 insertions(+), 5 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 4693f648536..fca3ae1a8e5 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -43,13 +43,22 @@ struct mainloop_timer_s { static GList *child_list = NULL; static qb_array_t *gio_map = NULL; +/*! + * \internal + * \brief Free a main loop child + * + * If the child has an associated timer, remove it. + * + * \param[in,out] child Main loop child + * + * \note This does not free \p child->privatedata. + */ static void -child_free(mainloop_child_t *child) +free_main_loop_child(mainloop_child_t *child) { if (child->timerid != 0) { pcmk__trace("Removing timer %d", child->timerid); g_source_remove(child->timerid); - child->timerid = 0; } free(child->desc); @@ -424,7 +433,7 @@ mainloop_destroy_signal(int sig) void mainloop_cleanup(void) { - g_list_free_full(child_list, (GDestroyNotify) child_free); + g_list_free_full(child_list, (GDestroyNotify) free_main_loop_child); child_list = NULL; g_clear_pointer(&gio_map, qb_array_free); @@ -1138,7 +1147,7 @@ child_death_dispatch(int signal) (long long) child->pid); child_list = g_list_remove_link(child_list, saved); g_list_free(saved); - child_free(child); + free_main_loop_child(child); } } } @@ -1202,7 +1211,7 @@ mainloop_child_kill(pid_t pid) } child_list = g_list_remove(child_list, match); - child_free(match); + free_main_loop_child(match); return TRUE; } From cf1b668725dc41a91539c6d32c78427c30100818 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 02:51:31 -0700 Subject: [PATCH 04/70] Refactor: libcrmcommon: Use bool for mainloop_child_t:timeout mainloop_child_timeout() is public API, so we have to convert to gboolean (int) there. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 5 ++--- lib/common/mainloop.c | 6 +++--- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 70cf3da79bb..a7c18c2b0c6 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -14,10 +14,9 @@ #ifndef PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H #define PCMK__CRM_COMMON_MAINLOOP_INTERNAL__H +#include // bool #include // pid_t -#include // gboolean - #include // crm_ipc_t #include // ipc_client_callbacks, mainloop_* @@ -29,7 +28,7 @@ struct mainloop_child_s { pid_t pid; char *desc; unsigned timerid; - gboolean timeout; + bool timeout; void *privatedata; enum mainloop_child_flags flags; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index fca3ae1a8e5..e5b5652f778 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -999,7 +999,7 @@ mainloop_child_name(mainloop_child_t * child) int mainloop_child_timeout(mainloop_child_t * child) { - return child->timeout; + return child->timeout? TRUE : FALSE; } void * @@ -1063,7 +1063,7 @@ child_timeout_callback(void *p) return FALSE; } - child->timeout = TRUE; + child->timeout = true; pcmk__debug("%s process (PID %lld) timed out", child->desc, (long long) child->pid); @@ -1233,7 +1233,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, child->pid = pid; child->timerid = 0; - child->timeout = FALSE; + child->timeout = false; child->privatedata = privatedata; child->exit_fn = exit_fn; child->flags = flags; From 67590c31c348bbff74dad66acc10a14a096b1c46 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 11:19:54 -0700 Subject: [PATCH 05/70] Refactor: libcrmcommon: Best practices for mainloop_child_add_with_flags ...and mainloop_child_add(). * Add Doxygen. * Improve variable names and comments. * Drop redundant assignments. All fields are zero-initialized. * Add TODO comment for flags argument. The comment mentions bool as a possibility, because currently there's only one enum value, so we may not need a flag group once this is internal. * Use bool instead of gboolean. * Be more assertive about passing a positive value for the child PID. Some functions like child_kill_helper() will definitely not work correctly for a nonpositive PID. * Fix a minor overflow bug. Note that pcmk__create_timer() takes an unsigned int for its interval_ms argument. So we want to avoid passing a negative int to pcmk__create_timer() here, since the negative value would be cast to a very large positive unsigned value. Signed-off-by: Reid Wahl --- include/crm/common/mainloop.h | 12 ++---- lib/common/mainloop.c | 70 +++++++++++++++++++++++------------ 2 files changed, 51 insertions(+), 31 deletions(-) diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 17a0ea1cc12..51a8ff66a44 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -171,16 +171,12 @@ mainloop_io_t *mainloop_add_fd(const char *name, int priority, int fd, void *use void mainloop_del_fd(mainloop_io_t * client); -/* - * Create a new tracked process - * To track a process group, use -pid - */ -void mainloop_child_add(pid_t pid, int timeout, const char *desc, - void *userdata, +void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, + void *user_data, pcmk__mainloop_child_exit_fn_t exit_fn); -void mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, - void *userdata, enum mainloop_child_flags, +void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, + void *user_data, enum mainloop_child_flags, pcmk__mainloop_child_exit_fn_t exit_fn); void *mainloop_child_userdata(mainloop_child_t * child); diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index e5b5652f778..3b012f53aec 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1215,51 +1215,75 @@ mainloop_child_kill(pid_t pid) return TRUE; } -/* Create/Log a new tracked process - * To track a process group, use -pid +/*! + * \brief Create a new \c mainloop_child_t object and add it to the main loop + * + * If the child process has not exited within \p timeout_ms, send it a + * \c SIGKILL signal. * - * @TODO Using a non-positive pid (i.e. any child, or process group) would - * likely not be useful since we will free the child after the first - * completed process. + * \param[in] pid Child PID (must be positive for correct behavior) + * \param[in] timeout_ms Timeout in milliseconds + * \param[in] desc Description + * \param[in] user_data User data + * \param[in] flags Group of enum mainloop_child_flags + * \param[in] exit_fn Function to call when the child process exits */ void -mainloop_child_add_with_flags(pid_t pid, int timeout, const char *desc, - void *privatedata, +mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, + void *user_data, enum mainloop_child_flags flags, pcmk__mainloop_child_exit_fn_t exit_fn) { - static bool need_init = TRUE; + // @TODO Make flags argument uint32_t or bool when this is made internal + static bool need_init = true; + mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); child->pid = pid; - child->timerid = 0; - child->timeout = false; - child->privatedata = privatedata; - child->exit_fn = exit_fn; - child->flags = flags; child->desc = pcmk__str_copy(desc); + child->privatedata = user_data; + child->flags = flags; + child->exit_fn = exit_fn; - if (timeout) { - child->timerid = pcmk__create_timer(timeout, child_timeout_callback, child); + if (timeout_ms > 0) { + child->timerid = pcmk__create_timer(timeout_ms, child_timeout_callback, + child); } child_list = g_list_append(child_list, child); - if(need_init) { - need_init = FALSE; - /* SIGCHLD processing has to be invoked from mainloop. - * We do not want it to be possible to both add a child pid - * to mainloop, and have the pid's exit callback invoked within - * the same callstack. */ + if (need_init) { + /* Invoke SIGCHLD processing from the main loop. This ensures that we + * don't add a child to the main loop and have the exit callback invoked + * for the child PID within the same call stack. + * + * @TODO Understand and document why this matters. + */ + need_init = false; pcmk__create_timer(1, child_signal_init, NULL); } } +/*! + * \brief Create a new \c mainloop_child_t object and add it to the main loop + * + * If the child process has not exited within \p timeout_ms, send it a + * \c SIGKILL signal. + * + * \param[in] pid Child PID (must be positive for correct behavior) + * \param[in] timeout_ms Timeout in seconds + * \param[in] desc Description + * \param[in] user_data User data + * \param[in] exit_fn Function to call when the child process exits + * + * \note This is a convenience wrapper for \c mainloop_child_add_with_flags() + * that doesn't set any flags. + */ void -mainloop_child_add(pid_t pid, int timeout, const char *desc, void *privatedata, +mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, pcmk__mainloop_child_exit_fn_t exit_fn) { - mainloop_child_add_with_flags(pid, timeout, desc, privatedata, 0, exit_fn); + mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, exit_fn); } static gboolean From ef61e5ce25b63cd6ee9ae5db36df67c3fccd57b9 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 11:33:32 -0700 Subject: [PATCH 06/70] Refactor: libcrmcommon: Best practices for child_signal_init() * Add Doxygen. * Improve log messages and comments. * Rename to install_sigchld_handler(). * Rename argument to user_data to align with GSourceFunc definition. * Return G_SOURCE_REMOVE instead of FALSE. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 25 +++++++++++++++++++------ 1 file changed, 19 insertions(+), 6 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 3b012f53aec..7e82c92538e 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1152,16 +1152,29 @@ child_death_dispatch(int signal) } } +/*! + * \internal + * \brief Install the main loop \c SIGCHLD handler + * + * Install \c child_death_dispatch as the \c SIGCHLD handler, and call it for + * any children that terminated before the handler was installed. + * + * \param[in] user_data Ignored + * + * \return \c G_SOURCE_REMOVE (to destroy the timeout that triggered this call) + * + * \note This is a \c GSourceFunc. + */ static gboolean -child_signal_init(void *p) +install_sigchld_handler(void *user_data) { - pcmk__trace("Installed SIGCHLD handler"); - /* Do NOT use g_child_watch_add() and friends, they rely on pthreads */ + pcmk__trace("Installing SIGCHLD handler"); + + // Do NOT use g_child_watch_add() and friends, since they rely on pthreads mainloop_add_signal(SIGCHLD, child_death_dispatch); - /* In case they terminated before the signal handler was installed */ child_death_dispatch(SIGCHLD); - return FALSE; + return G_SOURCE_REMOVE; } gboolean @@ -1260,7 +1273,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, * @TODO Understand and document why this matters. */ need_init = false; - pcmk__create_timer(1, child_signal_init, NULL); + pcmk__create_timer(1, install_sigchld_handler, NULL); } } From 8486d018e5b57ce984473a6b24b6a260f09ae10f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 14:22:52 -0700 Subject: [PATCH 07/70] Doc: libcrmcommon: Document struct mainloop_child_s Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 17 +++++++++++------ 1 file changed, 11 insertions(+), 6 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index a7c18c2b0c6..0f1f2c295ef 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -24,16 +24,21 @@ extern "C" { #endif +/*! + * \internal + * \brief Info about a child process tracked by a main event loop + */ struct mainloop_child_s { - pid_t pid; - char *desc; - unsigned timerid; - bool timeout; - void *privatedata; + pid_t pid; //!< Child PID + char *desc; //!< Description + unsigned int timerid; //!< ID of timer for child timeout + bool timeout; //!< Whether the child has timed out + void *privatedata; //!< User data + //! Group of enum mainloop_child_flags enum mainloop_child_flags flags; - /* Called when a process dies */ + //! Callback function called when the child terminates pcmk__mainloop_child_exit_fn_t exit_fn; }; From bbf73692920556759417db71622f41ee5f0af43e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 14:25:25 -0700 Subject: [PATCH 08/70] Refactor: libcrmcommon: Rename mainloop_child_t:timerid to timer_id Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 2 +- lib/common/mainloop.c | 14 +++++++------- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 0f1f2c295ef..cf3cdf45990 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -31,7 +31,7 @@ extern "C" { struct mainloop_child_s { pid_t pid; //!< Child PID char *desc; //!< Description - unsigned int timerid; //!< ID of timer for child timeout + unsigned int timer_id; //!< ID of timer for child timeout bool timeout; //!< Whether the child has timed out void *privatedata; //!< User data diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 7e82c92538e..921e011d7d5 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -56,9 +56,9 @@ static qb_array_t *gio_map = NULL; static void free_main_loop_child(mainloop_child_t *child) { - if (child->timerid != 0) { - pcmk__trace("Removing timer %d", child->timerid); - g_source_remove(child->timerid); + if (child->timer_id != 0) { + pcmk__trace("Removing timer %u", child->timer_id); + g_source_remove(child->timer_id); } free(child->desc); @@ -1050,7 +1050,7 @@ child_timeout_callback(void *p) mainloop_child_t *child = p; int rc = pcmk_rc_ok; - child->timerid = 0; + child->timer_id = 0; if (child->timeout) { pcmk__warn("%s process (PID %lld) will not die!", child->desc, (long long) child->pid); @@ -1067,7 +1067,7 @@ child_timeout_callback(void *p) pcmk__debug("%s process (PID %lld) timed out", child->desc, (long long) child->pid); - child->timerid = pcmk__create_timer(5000, child_timeout_callback, child); + child->timer_id = pcmk__create_timer(5000, child_timeout_callback, child); return FALSE; } @@ -1259,8 +1259,8 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, child->exit_fn = exit_fn; if (timeout_ms > 0) { - child->timerid = pcmk__create_timer(timeout_ms, child_timeout_callback, - child); + child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, + child); } child_list = g_list_append(child_list, child); From c64f17378bd0e181cc0485debb85aef3dcc21eb5 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 14:26:21 -0700 Subject: [PATCH 09/70] Refactor: libcrmcommon: Rename mainloop_child_t:timeout to timed_out The name "timeout" made it sounds as if it held the timeout duration in my opinion. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 2 +- lib/common/mainloop.c | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index cf3cdf45990..2b1adc550a2 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -32,7 +32,7 @@ struct mainloop_child_s { pid_t pid; //!< Child PID char *desc; //!< Description unsigned int timer_id; //!< ID of timer for child timeout - bool timeout; //!< Whether the child has timed out + bool timed_out; //!< Whether the child has timed out void *privatedata; //!< User data //! Group of enum mainloop_child_flags diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 921e011d7d5..6c1e5a456b6 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -999,7 +999,7 @@ mainloop_child_name(mainloop_child_t * child) int mainloop_child_timeout(mainloop_child_t * child) { - return child->timeout? TRUE : FALSE; + return child->timed_out? TRUE : FALSE; } void * @@ -1051,7 +1051,7 @@ child_timeout_callback(void *p) int rc = pcmk_rc_ok; child->timer_id = 0; - if (child->timeout) { + if (child->timed_out) { pcmk__warn("%s process (PID %lld) will not die!", child->desc, (long long) child->pid); return FALSE; @@ -1063,7 +1063,7 @@ child_timeout_callback(void *p) return FALSE; } - child->timeout = true; + child->timed_out = true; pcmk__debug("%s process (PID %lld) timed out", child->desc, (long long) child->pid); From be572ab023d7f37f656f1445ffef0bb2bcc5e111 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 14:29:13 -0700 Subject: [PATCH 10/70] Refactor: libcrmcommon: Rename mainloop_child_t:privatedata to user_data Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 2 +- lib/common/mainloop.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 2b1adc550a2..8f0ceb3026d 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -33,7 +33,7 @@ struct mainloop_child_s { char *desc; //!< Description unsigned int timer_id; //!< ID of timer for child timeout bool timed_out; //!< Whether the child has timed out - void *privatedata; //!< User data + void *user_data; //!< User data //! Group of enum mainloop_child_flags enum mainloop_child_flags flags; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 6c1e5a456b6..15bfaf54aec 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -51,7 +51,7 @@ static qb_array_t *gio_map = NULL; * * \param[in,out] child Main loop child * - * \note This does not free \p child->privatedata. + * \note This does not free \p child->user_data. */ static void free_main_loop_child(mainloop_child_t *child) @@ -1005,13 +1005,13 @@ mainloop_child_timeout(mainloop_child_t * child) void * mainloop_child_userdata(mainloop_child_t * child) { - return child->privatedata; + return child->user_data; } void mainloop_clear_child_userdata(mainloop_child_t * child) { - child->privatedata = NULL; + child->user_data = NULL; } static int @@ -1254,7 +1254,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, child->pid = pid; child->desc = pcmk__str_copy(desc); - child->privatedata = user_data; + child->user_data = user_data; child->flags = flags; child->exit_fn = exit_fn; From b5b3e49072b263bc5fdf8b7c987af227a85c7dba Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 14:32:22 -0700 Subject: [PATCH 11/70] Refactor: libcrmcommon: Replace mainloop_child_t:flags with kill_group There's only one enum value, so replace the flags field with a boolean. Also invert its meaning, since I think the inverted semantics are more clear. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 17 ++++++++++------- lib/common/mainloop.c | 12 ++++++------ 2 files changed, 16 insertions(+), 13 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 8f0ceb3026d..4d36c41a858 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -29,14 +29,17 @@ extern "C" { * \brief Info about a child process tracked by a main event loop */ struct mainloop_child_s { - pid_t pid; //!< Child PID - char *desc; //!< Description - unsigned int timer_id; //!< ID of timer for child timeout - bool timed_out; //!< Whether the child has timed out - void *user_data; //!< User data + pid_t pid; //!< Child PID + char *desc; //!< Description + unsigned int timer_id; //!< ID of timer for child timeout + bool timed_out; //!< Whether the child has timed out + void *user_data; //!< User data - //! Group of enum mainloop_child_flags - enum mainloop_child_flags flags; + /*! + * If \c true, kill the child's entire process group on timeout. + * If \c false, kill only the child process. + */ + bool kill_group; //! Callback function called when the child terminates pcmk__mainloop_child_exit_fn_t exit_fn; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 15bfaf54aec..2aaf2e826f0 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1019,15 +1019,15 @@ child_kill_helper(const mainloop_child_t *child) { int rc = 0; - if (pcmk__is_set(child->flags, mainloop_leave_pid_group)) { - pcmk__debug("Killing PID %lld only. Leaving its process group intact.", + if (child->kill_group) { + pcmk__debug("Killing PID %lld's entire process group", (long long) child->pid); - rc = kill(child->pid, SIGKILL); + rc = kill(-child->pid, SIGKILL); } else { - pcmk__debug("Killing PID %lld's entire process group", + pcmk__debug("Killing PID %lld only. Leaving its process group intact.", (long long) child->pid); - rc = kill(-child->pid, SIGKILL); + rc = kill(child->pid, SIGKILL); } if (rc == 0) { @@ -1255,7 +1255,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, child->pid = pid; child->desc = pcmk__str_copy(desc); child->user_data = user_data; - child->flags = flags; + child->kill_group = !pcmk__is_set(flags, mainloop_leave_pid_group); child->exit_fn = exit_fn; if (timeout_ms > 0) { From 09e3fc5b406e9373ef975e3e0436af1c8f795fd8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 15:08:47 -0700 Subject: [PATCH 12/70] Refactor: pacemakerd: Drop mainloop_child_name() internally Signed-off-by: Reid Wahl --- daemons/pacemakerd/pcmkd_subdaemons.c | 14 ++++++-------- 1 file changed, 6 insertions(+), 8 deletions(-) diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index 63152d29df0..332d1acc0d7 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -257,27 +257,25 @@ static void pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) { pcmkd_child_t *child = mainloop_child_userdata(p); - const char *name = mainloop_child_name(p); if (signo) { // cts-lab looks for this message do_crm_log(((signo == SIGKILL)? LOG_WARNING : LOG_ERR), - "%s[%d] terminated with signal %d (%s)%s", - name, p->pid, signo, strsignal(signo), - (core? " and dumped core" : "")); + "%s[%d] terminated with signal %d (%s)%s", p->desc, p->pid, + signo, strsignal(signo), (core? " and dumped core" : "")); pcmk_process_exit(child); return; } switch(exitcode) { case CRM_EX_OK: - pcmk__info("%s[%d] exited with status %d (%s)", name, p->pid, + pcmk__info("%s[%d] exited with status %d (%s)", p->desc, p->pid, exitcode, crm_exit_str(exitcode)); break; case CRM_EX_FATAL: pcmk__warn("Shutting cluster down because %s[%d] had fatal failure", - name, p->pid); + p->desc, p->pid); child->flags &= ~child_respawn; fatal_error = true; pcmk_shutdown(SIGTERM); @@ -290,7 +288,7 @@ pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) child->flags &= ~child_respawn; fatal_error = true; msg = pcmk__assert_asprintf("Subdaemon %s[%d] requested panic", - name, p->pid); + p->desc, p->pid); pcmk__panic(msg); // Should never get here @@ -301,7 +299,7 @@ pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) default: // cts-lab looks for this message - pcmk__err("%s[%d] exited with status %d (%s)", name, p->pid, + pcmk__err("%s[%d] exited with status %d (%s)", p->desc, p->pid, exitcode, crm_exit_str(exitcode)); break; } From 7ff8c3f67898bc7204b85143b6e59ba01bc95ade Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 15:09:37 -0700 Subject: [PATCH 13/70] Refactor: libcrmservice: Drop mainloop_child_timeout() internally Signed-off-by: Reid Wahl --- lib/services/services_linux.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 6ae1605ebbc..4ef484b3496 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -731,7 +731,7 @@ async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) log_op_output(op); parse_exit_reason_from_stderr(op); - } else if (mainloop_child_timeout(p)) { + } else if (p->timed_out) { const char *kind = services__action_kind(op); pcmk__info("%s %s[%d] timed out after %s", kind, op->id, op->pid, From 937f6e31c110167b11d8ee5993d38b9ed47985c6 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 15:10:47 -0700 Subject: [PATCH 14/70] Refactor: various: Drop mainloop_child_userdata() internally Signed-off-by: Reid Wahl --- daemons/pacemakerd/pcmkd_subdaemons.c | 2 +- lib/services/services_linux.c | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index 332d1acc0d7..b28219a7944 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -256,7 +256,7 @@ escalate_shutdown(void *data) static void pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) { - pcmkd_child_t *child = mainloop_child_userdata(p); + pcmkd_child_t *child = p->user_data; if (signo) { // cts-lab looks for this message diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 4ef484b3496..7bc45179bd6 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -708,7 +708,7 @@ parse_exit_reason_from_stderr(svc_action_t *op) static void async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) { - svc_action_t *op = mainloop_child_userdata(p); + svc_action_t *op = p->user_data; mainloop_clear_child_userdata(p); CRM_CHECK(op->pid == p->pid, From d7e043d4182c5f3c8a131baecfa61efcf8d30452 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 15:11:37 -0700 Subject: [PATCH 15/70] Refactor: libcrmservice: Drop mainloop_clear_child_userdata() internally Signed-off-by: Reid Wahl --- lib/services/services_linux.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 7bc45179bd6..1c0d5ed5634 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -710,7 +710,8 @@ async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) { svc_action_t *op = p->user_data; - mainloop_clear_child_userdata(p); + p->user_data = NULL; + CRM_CHECK(op->pid == p->pid, services__set_result(op, services__generic_error(op), PCMK_EXEC_ERROR, "Bug in mainloop handling"); From fb1e807c53e04e276de8a481a6f4a976baf46794 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 15:46:09 -0700 Subject: [PATCH 16/70] Refactor: libcrmcommon: New compare_children_by_pid() So that we can use g_list_find_custom(). Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 57 +++++++++++++++++++++++++++++++------------ 1 file changed, 42 insertions(+), 15 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 2aaf2e826f0..d0ce9432125 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1177,37 +1177,64 @@ install_sigchld_handler(void *user_data) return G_SOURCE_REMOVE; } +/*! + * \internal + * \brief Compare two mainloop child objects by PID + * + * \param[in] a First child to compare (const mainloop_child_t *) + * \param[in] b Second child to compare (const mainloop_child_t *) + * + * \retval -1 if \p a->pid is less than \p b->pid + * \retval 0 if \p a->pid is equal to \p b->pid + * \retval 1 if \p a->pid is greater than \p b->pid + * + * \note This is a \c GCompareFunc. + */ +static int +compare_children_by_pid(const void *a, const void *b) +{ + const mainloop_child_t *child1 = a; + const mainloop_child_t *child2 = b; + + if (child1->pid < child2->pid) { + return -1; + } + + if (child1->pid > child2->pid) { + return 1; + } + + return 0; +} + gboolean mainloop_child_kill(pid_t pid) { - GList *iter; + const mainloop_child_t cmp_data = { .pid = pid }; + GList *match = NULL; mainloop_child_t *child = NULL; - mainloop_child_t *match = NULL; + /* It is impossible to block SIGKILL, this allows us to * call waitpid without WNOHANG flag.*/ int waitflags = 0; int rc = pcmk_rc_ok; - for (iter = child_list; iter != NULL && match == NULL; iter = iter->next) { - child = iter->data; - if (pid == child->pid) { - match = child; - } - } - + match = g_list_find_custom(child_list, &cmp_data, compare_children_by_pid); if (match == NULL) { return FALSE; } - rc = child_kill_helper(match); + child = match->data; + + rc = child_kill_helper(child); if (rc == ESRCH) { /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get * SIGCHLD and let handler clean it up as normal (so we get the correct * return code/status). The blocking alternative would be to call - * child_waitpid(match, 0). + * child_waitpid(child, 0). */ pcmk__trace("Waiting for signal that child process %lld completed", - (long long) match->pid); + (long long) child->pid); return TRUE; } @@ -1218,13 +1245,13 @@ mainloop_child_kill(pid_t pid) waitflags = WNOHANG; } - if (!child_waitpid(match, waitflags)) { + if (!child_waitpid(child, waitflags)) { /* not much we can do if this occurs */ return FALSE; } - child_list = g_list_remove(child_list, match); - free_main_loop_child(match); + child_list = g_list_remove(child_list, child); + free_main_loop_child(child); return TRUE; } From f83391b8af32a66619342474c07b1a5fa5099749 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Thu, 13 Aug 2026 00:45:46 -0700 Subject: [PATCH 17/70] Refactor: libcrmcommon: Use g_list_delete_link() in mainloop_child_kill We've already got the link (as match). No need to have g_list_remove() iterate over the list again to find it. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d0ce9432125..8d91558d18a 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1250,7 +1250,7 @@ mainloop_child_kill(pid_t pid) return FALSE; } - child_list = g_list_remove(child_list, child); + child_list = g_list_delete_link(child_list, match); free_main_loop_child(child); return TRUE; } From 5a105f33451e15bd7bd0e6d3fa1f15435c8dd0e0 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 16:15:32 -0700 Subject: [PATCH 18/70] Doc: libcrmcommon: Add Doxygen for mainloop_child_kill() Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 8d91558d18a..d1ab359d062 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1207,6 +1207,25 @@ compare_children_by_pid(const void *a, const void *b) return 0; } +/*! + * \brief Kill a child process tracked by the main loop + * + * If a process with PID \p pid is being tracked, send it a \c SIGKILL. + * + * If this function kills the child process successfully, remove the child from + * the tracking data structure and free the child. + * + * If the process is being tracked but no longer exists, don't remove or free + * the child yet. We will do this later when we receive a \c SIGCHLD for the + * child process. + * + * \param[in] pid Child PID + * + * \return \c TRUE if the child with ID \p pid was being tracked and either this + * function killed the process successfully or the process has already + * terminated but we have not received a \c SIGCHLD for it; or \c FALSE + * otherwise + */ gboolean mainloop_child_kill(pid_t pid) { From 26393f76b6cbccd9c119f67fb613bd910d96ee8b Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 18:42:53 -0700 Subject: [PATCH 19/70] Refactor: libcrmcommon: Best practices in child_timeout_callback() * Add Doxygen. * Rename argument to user_data. * Return G_SOURCE_REMOVE instead of FALSE. * Improve log messages and comments. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 36 ++++++++++++++++++++++++++++-------- 1 file changed, 28 insertions(+), 8 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d1ab359d062..a6a846f98ab 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1044,31 +1044,51 @@ child_kill_helper(const mainloop_child_t *child) return rc; } +/*! + * \internal + * \brief Kill a child process after its timeout has expired + * + * \param[in,out] user_data Main loop child (mainloop_child_t *) + * + * \return \c G_SOURCE_REMOVE (to destroy the timeout that triggered this call) + * + * \note This is a \c GSourceFunc. + */ static gboolean -child_timeout_callback(void *p) +child_timeout_callback(void *user_data) { - mainloop_child_t *child = p; + mainloop_child_t *child = user_data; int rc = pcmk_rc_ok; child->timer_id = 0; if (child->timed_out) { + // @TODO Move this to a separate callback function or drop it pcmk__warn("%s process (PID %lld) will not die!", child->desc, (long long) child->pid); - return FALSE; + return G_SOURCE_REMOVE; } rc = child_kill_helper(child); if (rc == ESRCH) { - /* Nothing left to do. pid doesn't exist */ - return FALSE; + // PID no longer exists, so just destroy the timer + return G_SOURCE_REMOVE; } child->timed_out = true; - pcmk__debug("%s process (PID %lld) timed out", child->desc, - (long long) child->pid); + if (rc == pcmk_rc_ok) { + pcmk__debug("%s process (PID %lld) timed out and was killed " + "successfully", child->desc, (long long) child->pid); + return G_SOURCE_REMOVE; + } + + pcmk__debug("%s process (PID %lld) timed out and could not be killed", + child->desc, (long long) child->pid); + + // Warn if the child has not terminated after 5 more seconds child->timer_id = pcmk__create_timer(5000, child_timeout_callback, child); - return FALSE; + + return G_SOURCE_REMOVE; } static bool From 2c952daf92cb500e469f5a0ac36decad20daa310 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 18:49:19 -0700 Subject: [PATCH 20/70] Refactor: libcrmcommon: Drop five-second hung child check Previously, if we failed to kill a child process that timed out, we would log a warning if it hadn't terminated after five more seconds. This doesn't seem useful to me. If kill() failed, we already log an error for that. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index a6a846f98ab..aadd1c3b943 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1061,12 +1061,6 @@ child_timeout_callback(void *user_data) int rc = pcmk_rc_ok; child->timer_id = 0; - if (child->timed_out) { - // @TODO Move this to a separate callback function or drop it - pcmk__warn("%s process (PID %lld) will not die!", child->desc, - (long long) child->pid); - return G_SOURCE_REMOVE; - } rc = child_kill_helper(child); if (rc == ESRCH) { @@ -1079,14 +1073,11 @@ child_timeout_callback(void *user_data) if (rc == pcmk_rc_ok) { pcmk__debug("%s process (PID %lld) timed out and was killed " "successfully", child->desc, (long long) child->pid); - return G_SOURCE_REMOVE; - } - pcmk__debug("%s process (PID %lld) timed out and could not be killed", - child->desc, (long long) child->pid); - - // Warn if the child has not terminated after 5 more seconds - child->timer_id = pcmk__create_timer(5000, child_timeout_callback, child); + } else { + pcmk__debug("%s process (PID %lld) timed out and could not be killed", + child->desc, (long long) child->pid); + } return G_SOURCE_REMOVE; } From 04b1024327551d87286a1d78c0d4e56b3e67a36f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 19:40:11 -0700 Subject: [PATCH 21/70] Refactor: libcrmcommon: Always set timed_out in child_timeout_callback() Previously, ESRCH (child already terminated) was the only case where we weren't setting child->timed_out to true. However, if we reach child_timeout_callback() and then kill() returns ESRCH, that means the child process terminated after the timeout expired. If a child terminates before the timeout expires, the SIGCHLD handler (child_death_dispatch()) destroys the timeout source and frees the mainloop_child_t. Since the timeout source is destroyed in that case, child_timeout_callback() won't get called for that child. The SIGCHLD handler is implemented as a main loop trigger with priority (G_PRIORITY_HIGH - 1) -- that is, -100 - 1 == -101. The timeout source has priority G_PRIORITY_DEFAULT -- that is, 0. A more negative value indicates a higher priority. So if a child terminates before the timer expires, then the trigger for the SIGCHLD handler should be dispatched in a main loop iteration **before** the iteration that dispatches the timeout source. (On each main loop iteration, GLib finds the highest- priority source that's ready. It then dispatches all ready sources that have that same priority. Ready sources of a lower priority must wait until a later iteration to be dispatched.) Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 25 ++++++++++++++----------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index aadd1c3b943..2356b9a730c 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1059,26 +1059,29 @@ child_timeout_callback(void *user_data) { mainloop_child_t *child = user_data; int rc = pcmk_rc_ok; + const char *result_s = NULL; child->timer_id = 0; + child->timed_out = true; rc = child_kill_helper(child); - if (rc == ESRCH) { - // PID no longer exists, so just destroy the timer - return G_SOURCE_REMOVE; - } - child->timed_out = true; + switch (rc) { + case pcmk_rc_ok: + result_s = "was successfully killed"; + break; - if (rc == pcmk_rc_ok) { - pcmk__debug("%s process (PID %lld) timed out and was killed " - "successfully", child->desc, (long long) child->pid); + case ESRCH: + result_s = "has already terminated"; + break; - } else { - pcmk__debug("%s process (PID %lld) timed out and could not be killed", - child->desc, (long long) child->pid); + default: + result_s = "could not be killed"; + break; } + pcmk__debug("%s process (PID %lld) timed out and %s", child->desc, + (long long) child->pid, result_s); return G_SOURCE_REMOVE; } From 786738b5612bdf58e6407072583f3176f614dc5f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 22:30:39 -0700 Subject: [PATCH 22/70] Refactor: libcrmcommon: Clean up child_kill_helper() * Add Doxygen. * Rename to kill_child_pid(). * Simplify debug logging. It's known that a negative PID kills the process group. * Store the PID or its negation in a variable for convenience. * Log the child's description. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 2356b9a730c..97c5087b754 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1014,22 +1014,27 @@ mainloop_clear_child_userdata(mainloop_child_t * child) child->user_data = NULL; } +/*! + * \internal + * \brief Send \c SIGKILL to a main loop child process or its process group + * + * If \p child->kill_group is set, kill the child's entire process group. + * Otherwise, kill only the child process itself. + * + * \param[in] child Main loop child + * + * \return Standard Pacemaker return code (\c pcmk_rc_ok if \c kill() returns 0, + * or \c errno after calling \c kill() otherwise) + */ static int -child_kill_helper(const mainloop_child_t *child) +kill_child_pid(const mainloop_child_t *child) { + const pid_t pid = (child->kill_group? -child->pid : child->pid); int rc = 0; - if (child->kill_group) { - pcmk__debug("Killing PID %lld's entire process group", - (long long) child->pid); - rc = kill(-child->pid, SIGKILL); - - } else { - pcmk__debug("Killing PID %lld only. Leaving its process group intact.", - (long long) child->pid); - rc = kill(child->pid, SIGKILL); - } + pcmk__debug("Killing PID %lld", (long long) pid); + rc = kill(pid, SIGKILL); if (rc == 0) { return pcmk_rc_ok; } @@ -1039,8 +1044,8 @@ child_kill_helper(const mainloop_child_t *child) return rc; } - pcmk__err("kill(%lld, KILL) failed: %s", (long long) child->pid, - strerror(rc)); + pcmk__err("kill(%lld, KILL) failed for child '%s': %s", (long long) pid, + pcmk__s(child->desc, ""), strerror(rc)); return rc; } @@ -1064,7 +1069,7 @@ child_timeout_callback(void *user_data) child->timer_id = 0; child->timed_out = true; - rc = child_kill_helper(child); + rc = kill_child_pid(child); switch (rc) { case pcmk_rc_ok: @@ -1259,7 +1264,7 @@ mainloop_child_kill(pid_t pid) child = match->data; - rc = child_kill_helper(child); + rc = kill_child_pid(child); if (rc == ESRCH) { /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get * SIGCHLD and let handler clean it up as normal (so we get the correct From a5bbf754a1997fa6f6977992e296db23edcbab21 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Wed, 12 Aug 2026 23:17:31 -0700 Subject: [PATCH 23/70] Refactor: libcrmcommon: Clean up child_death_dispatch() * Add Doxygen. * Rename to free_terminated_children(). * Use a while loop, which feels more natural when the current element might get removed. Frustratingly, there is no g_list_foreach_remove() corresponding to g_hash_table_foreach_remove(). * Use g_list_delete_link() in place of g_list_remove_link() and g_list_free(). Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 33 ++++++++++++++++++++++----------- 1 file changed, 22 insertions(+), 11 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 97c5087b754..9d617984b4f 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1153,21 +1153,32 @@ child_waitpid(mainloop_child_t *child, int flags) return true; } +/*! + * \internal + * \brief Free all main loop children whose processes have terminated + * + * If a child object's process has terminated, remove the child from + * \c child_list and free it. + * + * \param[in] signal Ignored + */ static void -child_death_dispatch(int signal) +free_terminated_children(int signal) { - for (GList *iter = child_list; iter; ) { - GList *saved = iter; + GList *iter = child_list; + + while (iter != NULL) { mainloop_child_t *child = iter->data; + GList *next = iter->next; - iter = iter->next; if (child_waitpid(child, WNOHANG)) { - pcmk__trace("Removing completed process %lld from child list", + pcmk__trace("Removing terminated process %lld from child list", (long long) child->pid); - child_list = g_list_remove_link(child_list, saved); - g_list_free(saved); + child_list = g_list_delete_link(child_list, iter); free_main_loop_child(child); } + + iter = next; } } @@ -1175,8 +1186,8 @@ child_death_dispatch(int signal) * \internal * \brief Install the main loop \c SIGCHLD handler * - * Install \c child_death_dispatch as the \c SIGCHLD handler, and call it for - * any children that terminated before the handler was installed. + * Install \c free_terminated_children() as the \c SIGCHLD handler, and call it + * for any children that terminated before the handler was installed. * * \param[in] user_data Ignored * @@ -1190,9 +1201,9 @@ install_sigchld_handler(void *user_data) pcmk__trace("Installing SIGCHLD handler"); // Do NOT use g_child_watch_add() and friends, since they rely on pthreads - mainloop_add_signal(SIGCHLD, child_death_dispatch); + mainloop_add_signal(SIGCHLD, free_terminated_children); - child_death_dispatch(SIGCHLD); + free_terminated_children(SIGCHLD); return G_SOURCE_REMOVE; } From f9037f46a66ed16cdbbcdee489bbc032d73cdd49 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Thu, 13 Aug 2026 00:56:05 -0700 Subject: [PATCH 24/70] Refactor: libcrmcommon: Move duplicated functionality to child_waitpid() Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 31 +++++++++++++------------------ 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 9d617984b4f..58841d2e7a6 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1091,14 +1091,18 @@ child_timeout_callback(void *user_data) } static bool -child_waitpid(mainloop_child_t *child, int flags) +child_waitpid(GList *link, int flags) { + mainloop_child_t *child = NULL; int rc = 0; int core = 0; int signo = 0; int status = 0; int exitcode = 0; + pcmk__assert(link != NULL); + child = link->data; + rc = waitpid(child->pid, &status, flags); if (rc == 0) { // WNOHANG in flags, and child status is not available @@ -1150,6 +1154,11 @@ child_waitpid(mainloop_child_t *child, int flags) child->exit_fn(child, core, signo, exitcode); } + pcmk__trace("Removing terminated process %lld from child list", + (long long) child->pid); + child_list = g_list_delete_link(child_list, link); + free_main_loop_child(child); + return true; } @@ -1168,16 +1177,9 @@ free_terminated_children(int signal) GList *iter = child_list; while (iter != NULL) { - mainloop_child_t *child = iter->data; GList *next = iter->next; - if (child_waitpid(child, WNOHANG)) { - pcmk__trace("Removing terminated process %lld from child list", - (long long) child->pid); - child_list = g_list_delete_link(child_list, iter); - free_main_loop_child(child); - } - + child_waitpid(iter, WNOHANG); iter = next; } } @@ -1280,7 +1282,7 @@ mainloop_child_kill(pid_t pid) /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get * SIGCHLD and let handler clean it up as normal (so we get the correct * return code/status). The blocking alternative would be to call - * child_waitpid(child, 0). + * child_waitpid(iter, 0). */ pcmk__trace("Waiting for signal that child process %lld completed", (long long) child->pid); @@ -1294,14 +1296,7 @@ mainloop_child_kill(pid_t pid) waitflags = WNOHANG; } - if (!child_waitpid(child, waitflags)) { - /* not much we can do if this occurs */ - return FALSE; - } - - child_list = g_list_delete_link(child_list, match); - free_main_loop_child(child); - return TRUE; + return child_waitpid(match, waitflags)? TRUE : FALSE; } /*! From 589a2174d7de6a517b508a0d6a85467f4e3bf592 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Thu, 13 Aug 2026 01:02:29 -0700 Subject: [PATCH 25/70] Refactor: libcrmcommon: CRM_CHECK() else case in child_waitpid() The only callers pass either 0 or WNOHANG for the flags argument. Commit c2dbdfb, which added this case and the comment about WUNTRACED and WCONTINUED, acknowledges that "[t]his has no effect in practice since no callers use the flags necessary to reach that condition." Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 58841d2e7a6..7044148f225 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1144,10 +1144,12 @@ child_waitpid(GList *link, int flags) child->desc); #endif - } else { // flags must contain WUNTRACED and/or WCONTINUED to reach this - pcmk__trace("Child process %lld (%s) stopped or continued", - (long long) child->pid, child->desc); - return false; + } else { + /* We're not using the WUNTRACED or WCONTINUED options. If the process + * changed state, it should have either exited or been terminated by a + * signal. + */ + CRM_CHECK(false, return false); } if (child->exit_fn != NULL) { From 8f04db468562d4e45fbec283a1d3f801a7fd129f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Thu, 13 Aug 2026 01:09:25 -0700 Subject: [PATCH 26/70] Refactor: libcrmcommon: child_waitpid() takes a bool argument The only values we passed for the flags argument were 0 and WNOHANG. Also use pid_t for rc, since that's what waitpid() returns. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 7044148f225..4c7ff09a531 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1091,10 +1091,10 @@ child_timeout_callback(void *user_data) } static bool -child_waitpid(GList *link, int flags) +child_waitpid(GList *link, bool no_hang) { mainloop_child_t *child = NULL; - int rc = 0; + pid_t rc = 0; int core = 0; int signo = 0; int status = 0; @@ -1103,9 +1103,10 @@ child_waitpid(GList *link, int flags) pcmk__assert(link != NULL); child = link->data; - rc = waitpid(child->pid, &status, flags); + rc = waitpid(child->pid, &status, (no_hang? WNOHANG : 0)); - if (rc == 0) { // WNOHANG in flags, and child status is not available + if (rc == 0) { + // WNOHANG was specified and child->pid exists and has not changed state pcmk__trace("Child process %lld (%s) still active", (long long) child->pid, child->desc); return false; @@ -1181,7 +1182,7 @@ free_terminated_children(int signal) while (iter != NULL) { GList *next = iter->next; - child_waitpid(iter, WNOHANG); + child_waitpid(iter, true); iter = next; } } @@ -1266,11 +1267,8 @@ mainloop_child_kill(pid_t pid) const mainloop_child_t cmp_data = { .pid = pid }; GList *match = NULL; mainloop_child_t *child = NULL; - - /* It is impossible to block SIGKILL, this allows us to - * call waitpid without WNOHANG flag.*/ - int waitflags = 0; int rc = pcmk_rc_ok; + bool no_hang = false; match = g_list_find_custom(child_list, &cmp_data, compare_children_by_pid); if (match == NULL) { @@ -1284,7 +1282,7 @@ mainloop_child_kill(pid_t pid) /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get * SIGCHLD and let handler clean it up as normal (so we get the correct * return code/status). The blocking alternative would be to call - * child_waitpid(iter, 0). + * child_waitpid(iter, false). */ pcmk__trace("Waiting for signal that child process %lld completed", (long long) child->pid); @@ -1294,11 +1292,14 @@ mainloop_child_kill(pid_t pid) if (rc != pcmk_rc_ok) { /* If kill() failed for some other reason, set the WNOHANG flag, since * we can't be certain what happened. + * + * If kill() succeeded, we don't need the WNOHANG flag because SIGKILL + * can't be blocked. */ - waitflags = WNOHANG; + no_hang = true; } - return child_waitpid(match, waitflags)? TRUE : FALSE; + return child_waitpid(match, no_hang)? TRUE : FALSE; } /*! From 767f966d8467080887a141ccd9c54bea7631b098 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 00:02:13 -0700 Subject: [PATCH 27/70] Log: libcrmcommon: child_waitpid() logs more specific messages As noted in the now-deleted comment, we were previously capturing too many cases with the same test. This could create misleading log messages. This commit is not intended to change any behavior. Variables and return values are intended to keep their same values. This is intended to change only the log messages. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 82 +++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 4c7ff09a531..7d5214bfe50 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1093,17 +1093,20 @@ child_timeout_callback(void *user_data) static bool child_waitpid(GList *link, bool no_hang) { + const int options = no_hang? WNOHANG : 0; + mainloop_child_t *child = NULL; pid_t rc = 0; + int status = 0; + int core = 0; int signo = 0; - int status = 0; int exitcode = 0; pcmk__assert(link != NULL); child = link->data; - rc = waitpid(child->pid, &status, (no_hang? WNOHANG : 0)); + rc = waitpid(child->pid, &status, options); if (rc == 0) { // WNOHANG was specified and child->pid exists and has not changed state @@ -1112,47 +1115,74 @@ child_waitpid(GList *link, bool no_hang) return false; } + if (rc == -1) { + // @TODO Reevaluate signo and exitcode here + signo = SIGCHLD; + exitcode = 1; + + if (errno == EINTR) { + pcmk__notice("Wait for child process %lld (%s) was interrupted by " + "a signal", (long long) child->pid, child->desc); + + } else if (errno == ECHILD) { + pcmk__err("Wait for child process %lld (%s) failed because process " + "does not exist or is not our child", + (long long) child->pid, child->desc); + + } else { + pcmk__err("Bug: Wait for child process %lld (%s) failed: %s " + "(waitpid() options: %#x)", (long long) child->pid, + child->desc, strerror(errno), options); + } + + goto terminated; + } + + /* At this point, rc is the PID of a child whose state changed. If + * child->pid is positive, then rc == child->pid. Otherwise, rc is the PID + * of one of the child processes in the process group with ID -child->pid. + */ + if (rc != child->pid) { - /* According to POSIX, possible conditions: - * - child->pid was non-positive (process group or any child), - * and rc is specific child - * - errno ECHILD (pid does not exist or is not child) - * - errno EINVAL (invalid flags) - * - errno EINTR (caller interrupted by signal) - * - * @TODO Handle these cases more specifically. - */ + // @TODO Reevaluate signo and exitcode here signo = SIGCHLD; exitcode = 1; - pcmk__notice("Wait for child process %lld (%s) interrupted: %s", - (long long) child->pid, child->desc, strerror(errno)); + pcmk__trace("Child process %lld from group %lld (%s) terminated", + (long long) rc, (long long) -child->pid, child->desc); + goto terminated; + } - } else if (WIFEXITED(status)) { + if (WIFEXITED(status)) { exitcode = WEXITSTATUS(status); pcmk__trace("Child process %lld (%s) exited with status %d", (long long) child->pid, child->desc, exitcode); + goto terminated; + } - } else if (WIFSIGNALED(status)) { + if (WIFSIGNALED(status)) { signo = WTERMSIG(status); - pcmk__trace("Child process %lld (%s) exited with signal %d (%s)", + pcmk__trace("Child process %lld (%s) was terminated by signal %d (%s)", (long long) child->pid, child->desc, signo, strsignal(signo)); + goto terminated; + } -#ifdef WCOREDUMP // AIX, SunOS, maybe others - } else if (WCOREDUMP(status)) { +#ifdef WCOREDUMP + if (WCOREDUMP(status)) { core = 1; pcmk__err("Child process %lld (%s) dumped core", (long long) child->pid, child->desc); -#endif - - } else { - /* We're not using the WUNTRACED or WCONTINUED options. If the process - * changed state, it should have either exited or been terminated by a - * signal. - */ - CRM_CHECK(false, return false); + goto terminated; } +#endif // defined(WCOREDUMP) + + /* We're not using the WUNTRACED or WCONTINUED options. If the process + * changed state, it should have either exited or been terminated by a + * signal. + */ + CRM_CHECK(false, return false); +terminated: if (child->exit_fn != NULL) { child->exit_fn(child, core, signo, exitcode); } From b6221c6deef7b61befbec6fd9243c940d2bf3249 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 00:51:02 -0700 Subject: [PATCH 28/70] Low: libcrmcommon: Fix core dumped logic in child_waitpid() WCOREDUMP() is supposed to be called only if WIFSIGNALED() returns true. Previously, we called it only if WIFSIGNALED() returned false. This also meant that if a process was terminated by a signal, we would never mark it as having dumped core. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 7d5214bfe50..653ff1581c5 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1164,17 +1164,17 @@ child_waitpid(GList *link, bool no_hang) pcmk__trace("Child process %lld (%s) was terminated by signal %d (%s)", (long long) child->pid, child->desc, signo, strsignal(signo)); - goto terminated; - } #ifdef WCOREDUMP - if (WCOREDUMP(status)) { - core = 1; - pcmk__err("Child process %lld (%s) dumped core", (long long) child->pid, - child->desc); + if (WCOREDUMP(status)) { + core = 1; + pcmk__err("Child process %lld (%s) dumped core", + (long long) child->pid, child->desc); + } +#endif // defined(WCOREDUMP) + goto terminated; } -#endif // defined(WCOREDUMP) /* We're not using the WUNTRACED or WCONTINUED options. If the process * changed state, it should have either exited or been terminated by a From 898533380d7d231a26597ce816664e90f794f4e3 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 01:32:07 -0700 Subject: [PATCH 29/70] Low: libcrmcommon: Don't set signo/exitcode for nonpositive child->pid The (rc != child->pid) case means that child->pid is nonpositive and a process in the group with ID -child->pid has changed state. It doesn't make sense to set signo to SIGCHLD or to set exitcode to 1. SIGCHLD did not cause process with ID child->pid to terminate, nor do we know from this check that the child exited with nonzero exit status. Nothing internal creates a child with nonpositive PID, so in practice this change has no effect. It would only matter for a public API caller that creates a child with nonpositive PID and an exit callback. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 653ff1581c5..f6bbd00e1fa 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1144,9 +1144,10 @@ child_waitpid(GList *link, bool no_hang) */ if (rc != child->pid) { - // @TODO Reevaluate signo and exitcode here - signo = SIGCHLD; - exitcode = 1; + /* @COMPAT Nothing internal creates a nonpositive child->pid, and the + * public Doxygen for mainloop_child_add() now notes that nonpositive + * PIDs are not expected to work correctly. + */ pcmk__trace("Child process %lld from group %lld (%s) terminated", (long long) rc, (long long) -child->pid, child->desc); goto terminated; From 273ab0907fd0b569ef98eadf4ebbffd0627207ad Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 11:32:06 -0700 Subject: [PATCH 30/70] Doc: libcrmcommon: Add Doxygen to child_waitpid() Commits 348bb51 and c2dbdfb helped me to feel more confident about the intent behind the return code. I really hate that we have allowed nonpositive PIDs via the public API, as it makes the child tracking behavior messy, poorly specified, and/or probably incorrect in several places. It seems like the simplest approach in most places is to preserve that behavior until we get rid of the public API. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 31 +++++++++++++++++++++++++++++++ 1 file changed, 31 insertions(+) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index f6bbd00e1fa..1b64fa37958 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1090,6 +1090,35 @@ child_timeout_callback(void *user_data) return G_SOURCE_REMOVE; } +/*! + * \internal + * \brief Wait on a child process and free it if terminated + * + * If the child has terminated, call its exit callback if any, remove it from + * \c child_list, and free it. + * + * Likely bug: If the child object's \c pid field is nonpositive, then we wait + * on the corresponding process group as documented in the \c wait(2) man page. + * On success, call the exit callback using that PID (not the PID of the actual + * child process that changed state). Also remove the child object from + * \c child_list and free the child object, even though there may still be other + * child processes in the same process group that have not yet been waited on. + * This seems incorrect. However, nothing internal creates a child object with + * nonpositive PID, and the \c mainloop_child_add() documentation notes that + * nonpositive PIDs are not expected to work correctly. + * + * \param[in,out] link List element whose data is the child to wait for + * \param[in] no_hang If \c true, use the \c waitpid() \c WNOHANG option + * + * \return \c true if the child process (or a child process in the specified + * process group) has terminated, or \c false if the child process is + * still active or its state changed in an unexpected way + * + * \note Taking the list link rather than the child as an argument allows us to + * delete a terminated child from \c child_list in constant time. If we + * took the child, \c g_list_remove() would have to find the child in the + * list again before removing it. + */ static bool child_waitpid(GList *link, bool no_hang) { @@ -1121,6 +1150,7 @@ child_waitpid(GList *link, bool no_hang) exitcode = 1; if (errno == EINTR) { + // @TODO Returning true seems incorrect here pcmk__notice("Wait for child process %lld (%s) was interrupted by " "a signal", (long long) child->pid, child->desc); @@ -1130,6 +1160,7 @@ child_waitpid(GList *link, bool no_hang) (long long) child->pid, child->desc); } else { + // @TODO Returning true seems incorrect here pcmk__err("Bug: Wait for child process %lld (%s) failed: %s " "(waitpid() options: %#x)", (long long) child->pid, child->desc, strerror(errno), options); From 40fb285ea56e626670a69f4e69db72bb2e626561 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 13:53:15 -0700 Subject: [PATCH 31/70] Low: libcrmcommon: Fix child_waitpid() return values If waitpid() returns -1: * If errno is ECHILD, then the child process is already gone or doesn't belong to us. We'll never be able to wait on a child with this PID (unless we fork another one that happens to have the same PID). So treat it as terminated and return true. * If errno is EINTR, then our wait was interrupted and we don't know anything about the child's status. It may still need to be waited on. Consider the child still active and return false. * If errno is some other value, then we don't expect it and aren't prepared for it. Consider the child still active and return false. * EINVAL is the only other possibility that POSIX specifies. That should never happen, since child_waitpid() chooses its waitpid() arguments. * Linux can set errno to ESRCH if child->pid is INT_MIN. We never set nonpositive child PIDs internally. However, an external caller of mainloop_child_add() could do so for now. It might make more sense to return true in that case, since we'll never be able to wait on the child. But I'd rather not add a case to handle this errno value that may be impossible on some systems and would only be encountered by a pathological external caller. Just return false. * Linux can set EAGAIN if "[t]he PID file descriptor specified in id is nonblocking and the process that it refers to has not terminated." I'm not sure that the PID file descriptor can be set to nonblocking in our case. I'm okay with logging the "Bug" message if this happens. We should definitely treat the process as still active in that case. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 29 ++++++++++++++--------------- 1 file changed, 14 insertions(+), 15 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 1b64fa37958..d5d81672a86 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1145,28 +1145,27 @@ child_waitpid(GList *link, bool no_hang) } if (rc == -1) { - // @TODO Reevaluate signo and exitcode here - signo = SIGCHLD; - exitcode = 1; + if (errno == ECHILD) { + // @TODO Reevaluate signo and exitcode here + signo = SIGCHLD; + exitcode = 1; - if (errno == EINTR) { - // @TODO Returning true seems incorrect here - pcmk__notice("Wait for child process %lld (%s) was interrupted by " - "a signal", (long long) child->pid, child->desc); - - } else if (errno == ECHILD) { pcmk__err("Wait for child process %lld (%s) failed because process " "does not exist or is not our child", (long long) child->pid, child->desc); + goto terminated; + } - } else { - // @TODO Returning true seems incorrect here - pcmk__err("Bug: Wait for child process %lld (%s) failed: %s " - "(waitpid() options: %#x)", (long long) child->pid, - child->desc, strerror(errno), options); + if (errno == EINTR) { + pcmk__notice("Wait for child process %lld (%s) was interrupted by " + "a signal", (long long) child->pid, child->desc); + return false; } - goto terminated; + pcmk__err("Bug: Wait for child process %lld (%s) failed: %s (waitpid() " + "options: %#x)", (long long) child->pid, child->desc, + strerror(errno), options); + return false; } /* At this point, rc is the PID of a child whose state changed. If From 7657ae3eeed0ed8ce38187e65c14c0c9e64bbc04 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 14:39:16 -0700 Subject: [PATCH 32/70] Low: libcrmcommon: Don't set signo to SIGCHLD for waitpid() ECHILD This would cause child exit callbacks to log that the child exited due to signal SIGCHLD, which is not the case. Also add a comment about exit_code being technically incorrect, and rename exitcode to exit_code. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d5d81672a86..b9b2125ef71 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1130,7 +1130,7 @@ child_waitpid(GList *link, bool no_hang) int core = 0; int signo = 0; - int exitcode = 0; + int exit_code = 0; pcmk__assert(link != NULL); child = link->data; @@ -1146,9 +1146,13 @@ child_waitpid(GList *link, bool no_hang) if (rc == -1) { if (errno == ECHILD) { - // @TODO Reevaluate signo and exitcode here - signo = SIGCHLD; - exitcode = 1; + /* This situation should probably never happen in practice. Setting + * exit_code to 1 is misleading in that it indicates the child + * exited with code 1, and we don't know that to be true. We could + * add a mainloop_child_t flag to indicate this case, but it doesn't + * seem worth it. + */ + exit_code = 1; pcmk__err("Wait for child process %lld (%s) failed because process " "does not exist or is not our child", @@ -1184,9 +1188,9 @@ child_waitpid(GList *link, bool no_hang) } if (WIFEXITED(status)) { - exitcode = WEXITSTATUS(status); + exit_code = WEXITSTATUS(status); pcmk__trace("Child process %lld (%s) exited with status %d", - (long long) child->pid, child->desc, exitcode); + (long long) child->pid, child->desc, exit_code); goto terminated; } @@ -1215,7 +1219,7 @@ child_waitpid(GList *link, bool no_hang) terminated: if (child->exit_fn != NULL) { - child->exit_fn(child, core, signo, exitcode); + child->exit_fn(child, core, signo, exit_code); } pcmk__trace("Removing terminated process %lld from child list", From 437c07b757c2e5409f65b675c25f043481d874b5 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 15:21:54 -0700 Subject: [PATCH 33/70] Refactor: libcrmcommon: New pcmk__main_loop_child_create() This will replace both of the mainloop_child_add*() functions. Require the pid argument to be positive. Make the timeout_ms argument an unsigned int, which is what pcmk__create_timer() expects. Reverse the order of desc and timeout_ms to align with the struct definition. kill_group=false is equivalent to calling mainloop_child_add_with_flags() with the mainloop_leave_pid_group flag set. kill_group=true is equivalent to calling mainloop_child_add_with_flags() with the flags argument set to 0, or to calling mainloop_child_add(). Signed-off-by: Reid Wahl --- daemons/based/based_io.c | 3 +- daemons/execd/remoted_schemas.c | 7 ++-- daemons/pacemakerd/pcmkd_subdaemons.c | 3 +- include/crm/common/mainloop_internal.h | 5 +++ lib/common/mainloop.c | 51 +++++++++++++++++++++++++- lib/services/services_linux.c | 13 +++---- 6 files changed, 68 insertions(+), 14 deletions(-) diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 5e6a12153ab..561cf17d739 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -113,7 +113,8 @@ write_cib_async(void *user_data) if (pid > 0) { // Parent - mainloop_child_add(pid, 0, "disk-writer", NULL, write_cib_cb); + pcmk__main_loop_child_create(pid, "disk-writer", 0, NULL, true, + write_cib_cb); if (blackbox_state == QB_LOG_STATE_ENABLED) { qb_log_ctl(QB_LOG_BLACKBOX, QB_LOG_CONF_ENABLED, QB_TRUE); diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index da4fece9267..8590fa18ba0 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -287,9 +287,10 @@ remoted_request_cib_schema_files(void) default: /* parent */ schema_fetch_pid = pid; - mainloop_child_add_with_flags(pid, 5 * 60 * 1000, "schema-fetch", NULL, - mainloop_leave_pid_group, - get_schema_files_complete); + + // Five-minute timeout + pcmk__main_loop_child_create(pid, "schema-fetch", 300000, NULL, + false, get_schema_files_complete); break; } } diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index b28219a7944..681e89d9b86 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -477,7 +477,8 @@ start_child(pcmkd_child_t * child) valgrind_s = " (valgrind enabled: " PCMK__VALGRIND_EXEC ")"; } - mainloop_child_add(child->pid, 0, name, child, pcmk_child_exit); + pcmk__main_loop_child_create(child->pid, name, 0, child, true, + pcmk_child_exit); pcmk__info("Forked process %lld using user %lld (%s) and group %lld " "for subdaemon %s%s", diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 4d36c41a858..88d8e5269db 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -59,6 +59,11 @@ struct mainloop_io_s { void (*destroy_fn)(void *user_data); }; +void pcmk__main_loop_child_create(pid_t pid, const char *desc, + unsigned int timeout_ms, void *user_data, + bool kill_group, + pcmk__mainloop_child_exit_fn_t exit_fn); + int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index b9b2125ef71..ea3f4ab271c 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1367,6 +1367,56 @@ mainloop_child_kill(pid_t pid) return child_waitpid(match, no_hang)? TRUE : FALSE; } +/*! + * \internal + * \brief Create a new \c mainloop_child_t object and add it to the main loop + * + * If the child process has not exited within \p timeout_ms, send it a + * \c SIGKILL signal. + * + * \param[in] pid Child PID + * \param[in] desc Description + * \param[in] timeout_ms Timeout in milliseconds + * \param[in] user_data User data + * \param[in] kill group If \c true, kill the child's entire process group on + * timeout; otherwise, kill only the child process + * \param[in] exit_fn Function to call when the child process exits + */ +void +pcmk__main_loop_child_create(pid_t pid, const char *desc, + unsigned int timeout_ms, void *user_data, + bool kill_group, + pcmk__mainloop_child_exit_fn_t exit_fn) +{ + static bool need_init = true; + + mainloop_child_t *child = NULL; + + pcmk__assert(pid > 0); + + child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); + child->pid = pid; + child->desc = pcmk__str_copy(desc); + child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, + child); + child->user_data = user_data; + child->kill_group = kill_group; + child->exit_fn = exit_fn; + + child_list = g_list_append(child_list, child); + + if (need_init) { + /* Invoke SIGCHLD processing from the main loop. This ensures that we + * don't add a child to the main loop and have the exit callback invoked + * for the child PID within the same call stack. + * + * @TODO Understand and document why this matters. + */ + need_init = false; + pcmk__create_timer(1, install_sigchld_handler, NULL); + } +} + /*! * \brief Create a new \c mainloop_child_t object and add it to the main loop * @@ -1386,7 +1436,6 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, enum mainloop_child_flags flags, pcmk__mainloop_child_exit_fn_t exit_fn) { - // @TODO Make flags argument uint32_t or bool when this is made internal static bool need_init = true; mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 1c0d5ed5634..a1f23e43ce5 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -1394,14 +1394,11 @@ services__execute_file(svc_action_t *op) } pcmk__trace("Waiting async for '%s'[%d]", op->opaque->exec, op->pid); - if (pcmk__is_set(op->flags, SVC_ACTION_LEAVE_GROUP)) { - mainloop_child_add_with_flags(op->pid, op->timeout, op->id, op, - mainloop_leave_pid_group, - async_action_complete); - } else { - mainloop_child_add_with_flags(op->pid, op->timeout, op->id, op, 0, - async_action_complete); - } + + pcmk__main_loop_child_create(op->pid, op->id, op->timeout, op, + !pcmk__is_set(op->flags, + SVC_ACTION_LEAVE_GROUP), + async_action_complete); op->opaque->stdout_gsource = mainloop_add_fd(op->id, G_PRIORITY_LOW, From 6f38b70c562c9de8d3b5b1238a0f76b305eb1890 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 15:33:18 -0700 Subject: [PATCH 34/70] Refactor: libcrmservice: Use bool variable in services_action_cancel() Signed-off-by: Reid Wahl --- lib/services/services.c | 13 +++++++------ 1 file changed, 7 insertions(+), 6 deletions(-) diff --git a/lib/services/services.c b/lib/services/services.c index c6a3019452e..cc06adecd1d 100644 --- a/lib/services/services.c +++ b/lib/services/services.c @@ -649,7 +649,7 @@ gboolean services_action_cancel(const char *name, const char *action, unsigned int interval_ms) { - gboolean cancelled = FALSE; + bool cancelled = false; char *id = pcmk__op_key(name, action, interval_ms); svc_action_t *op = NULL; @@ -674,12 +674,13 @@ services_action_cancel(const char *name, const char *action, */ if (op->pid != 0) { pcmk__info("Terminating in-flight op %s[%d] early because it was " - "cancelled", - id, op->pid); + "cancelled", id, op->pid); + cancelled = mainloop_child_kill(op->pid); - if (cancelled == FALSE) { + if (!cancelled) { pcmk__err("Termination of %s[%d] failed", id, op->pid); } + goto done; } @@ -707,12 +708,12 @@ services_action_cancel(const char *name, const char *action, blocked_ops = g_list_remove(blocked_ops, op); services_action_free(op); - cancelled = TRUE; + cancelled = true; // @TODO Initiate handle_blocked_ops() asynchronously done: free(id); - return cancelled; + return cancelled? TRUE : FALSE; } gboolean From bed681b7993b078fb48788f5081fa5d5193e1217 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 15:45:52 -0700 Subject: [PATCH 35/70] Refactor: libcrmcommon: New pcmk__main_loop_child_kill() To replace mainloop_child_kill(). Note that the new function asserts if the PID is not positive, so we can't replace the mainloop_child_kill() body with a call to the new function. Signed-off-by: Reid Wahl --- daemons/execd/remoted_schemas.c | 2 +- include/crm/common/mainloop_internal.h | 1 + lib/common/mainloop.c | 63 ++++++++++++++++++++++++++ lib/services/services.c | 2 +- 4 files changed, 66 insertions(+), 2 deletions(-) diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index 8590fa18ba0..ec20eb8acdb 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -248,7 +248,7 @@ remoted_request_cib_schema_files(void) * directory. */ if (schema_fetch_pid != 0) { - if (mainloop_child_kill(schema_fetch_pid) == FALSE) { + if (!pcmk__main_loop_child_kill(schema_fetch_pid)) { pcmk__warn("Unable to kill pre-existing schema-fetch process"); return; } diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 88d8e5269db..81dccdf3152 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -63,6 +63,7 @@ void pcmk__main_loop_child_create(pid_t pid, const char *desc, unsigned int timeout_ms, void *user_data, bool kill_group, pcmk__mainloop_child_exit_fn_t exit_fn); +bool pcmk__main_loop_child_kill(pid_t pid); int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index ea3f4ab271c..4ed7b025cce 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1417,6 +1417,69 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, } } +/*! + * \internal + * \brief Kill a child process tracked by the main loop + * + * If a process with PID \p pid is being tracked, send it a \c SIGKILL. + * + * If this function kills the child process successfully, remove the child from + * the tracking data structure and free the child. + * + * If the process is being tracked but no longer exists, don't remove or free + * the child yet. We will do this later when we receive a \c SIGCHLD for the + * child process. + * + * \param[in] pid Child PID + * + * \return \c true if the child with ID \p pid was being tracked and either this + * function killed the process successfully or the process has already + * terminated but we have not received a \c SIGCHLD for it; or \c false + * otherwise + */ +bool +pcmk__main_loop_child_kill(pid_t pid) +{ + const mainloop_child_t cmp_data = { .pid = pid }; + GList *match = NULL; + mainloop_child_t *child = NULL; + int rc = pcmk_rc_ok; + bool no_hang = false; + + pcmk__assert(pid > 0); + + match = g_list_find_custom(child_list, &cmp_data, compare_children_by_pid); + if (match == NULL) { + return false; + } + + child = match->data; + + rc = kill_child_pid(child); + if (rc == ESRCH) { + /* It's gone but hasn't shown up in waitpid() yet. Wait until we get + * SIGCHLD and let handler clean it up as normal (so we get the correct + * return code/status). The blocking alternative would be to call + * child_waitpid(iter, false). + */ + pcmk__trace("Waiting for signal that child process %lld completed", + (long long) child->pid); + return true; + } + + if (rc != pcmk_rc_ok) { + /* If kill() failed for some other reason, set the WNOHANG flag, since + * we can't be certain what happened. + * + * If kill() succeeded, we don't need the WNOHANG flag because SIGKILL + * can't be blocked. + */ + no_hang = true; + } + + return child_waitpid(match, no_hang); +} + /*! * \brief Create a new \c mainloop_child_t object and add it to the main loop * diff --git a/lib/services/services.c b/lib/services/services.c index cc06adecd1d..06abcd76d2f 100644 --- a/lib/services/services.c +++ b/lib/services/services.c @@ -676,7 +676,7 @@ services_action_cancel(const char *name, const char *action, pcmk__info("Terminating in-flight op %s[%d] early because it was " "cancelled", id, op->pid); - cancelled = mainloop_child_kill(op->pid); + cancelled = pcmk__main_loop_child_kill(op->pid); if (!cancelled) { pcmk__err("Termination of %s[%d] failed", id, op->pid); } From 1d0b82c7312eca96b23ec2b3ef890965274a1643 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 17:10:35 -0700 Subject: [PATCH 36/70] API: libcrmcommon: Deprecate mainloop_child_t API Deprecate the mainloop_child_t type itself and all functions that accept or manipulate mainloop_child_t objects. External programs should not use Pacemaker for general-purpose asynchronous programming. Signed-off-by: Reid Wahl --- include/crm/common/Makefile.am | 1 + include/crm/common/mainloop.h | 27 +-- include/crm/common/mainloop_compat.h | 69 +++++++ lib/common/mainloop.c | 264 +++++++++++---------------- 4 files changed, 180 insertions(+), 181 deletions(-) create mode 100644 include/crm/common/mainloop_compat.h diff --git a/include/crm/common/Makefile.am b/include/crm/common/Makefile.am index cf66a79f240..708190bb2d7 100644 --- a/include/crm/common/Makefile.am +++ b/include/crm/common/Makefile.am @@ -27,6 +27,7 @@ header_HEADERS += iso8601_compat.h header_HEADERS += logging.h header_HEADERS += logging_compat.h header_HEADERS += mainloop.h +header_HEADERS += mainloop_compat.h header_HEADERS += nodes.h header_HEADERS += nvpair.h header_HEADERS += nvpair_compat.h diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 51a8ff66a44..2120ad70680 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -30,15 +30,12 @@ extern "C" { * \ingroup core */ -enum mainloop_child_flags { - /* don't kill pid group on timeout, only kill the pid */ - mainloop_leave_pid_group = 0x01, -}; - // NOTE: sbd (as of at least 1.5.2) uses this typedef struct trigger_s crm_trigger_t; typedef struct mainloop_io_s mainloop_io_t; + +//! \deprecated Do not use typedef struct mainloop_child_s mainloop_child_t; // NOTE: sbd (as of at least 1.5.2) uses this @@ -171,22 +168,6 @@ mainloop_io_t *mainloop_add_fd(const char *name, int priority, int fd, void *use void mainloop_del_fd(mainloop_io_t * client); -void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, - void *user_data, - pcmk__mainloop_child_exit_fn_t exit_fn); - -void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, - void *user_data, enum mainloop_child_flags, - pcmk__mainloop_child_exit_fn_t exit_fn); - -void *mainloop_child_userdata(mainloop_child_t * child); -int mainloop_child_timeout(mainloop_child_t * child); -const char *mainloop_child_name(mainloop_child_t * child); - -pid_t mainloop_child_pid(mainloop_child_t * child); -void mainloop_clear_child_userdata(mainloop_child_t * child); -gboolean mainloop_child_kill(pid_t pid); - void pcmk_quit_main_loop(GMainLoop *mloop, unsigned int n); void pcmk_drain_main_loop(GMainLoop *mloop, unsigned int timer_ms, bool (*check)(unsigned int)); @@ -197,4 +178,8 @@ void pcmk_drain_main_loop(GMainLoop *mloop, unsigned int timer_ms, } #endif +#if !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) +#include +#endif // !defined(PCMK_ALLOW_DEPRECATED) || (PCMK_ALLOW_DEPRECATED == 1) + #endif diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h new file mode 100644 index 00000000000..2863fa200b1 --- /dev/null +++ b/include/crm/common/mainloop_compat.h @@ -0,0 +1,69 @@ +/* + * Copyright 2009-2026 the Pacemaker project contributors + * + * The version control history for this file may have further details. + * + * This source code is licensed under the GNU Lesser General Public License + * version 2.1 or later (LGPLv2.1+) WITHOUT ANY WARRANTY. + */ + +#ifndef PCMK__CRM_COMMON_MAINLOOP_COMPAT__H +#define PCMK__CRM_COMMON_MAINLOOP_COMPAT__H + +#include // pid_t + +#include // gboolean + +#include // mainloop_* + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * \file + * \brief Deprecated Pacemaker main event loop API + * \ingroup core + * \deprecated Do not include this header directly. The time APIs in this + * header, and the header itself, will be removed in a future + * release. + */ + +//! \deprecated Do not use +enum mainloop_child_flags { + mainloop_leave_pid_group = 0x01, +}; + +//! \deprecated Do not use +void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, + void *user_data, enum mainloop_child_flags, + pcmk__mainloop_child_exit_fn_t exit_fn); + +//! \deprecated Do not use +void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, + void *user_data, + pcmk__mainloop_child_exit_fn_t exit_fn); + +//! \deprecated Do not use +gboolean mainloop_child_kill(pid_t pid); + +//! \deprecated Do not use +pid_t mainloop_child_pid(mainloop_child_t *child); + +//! \deprecated Do not use +const char *mainloop_child_name(mainloop_child_t *child); + +//! \deprecated Do not use +int mainloop_child_timeout(mainloop_child_t *child); + +//! \deprecated Do not use +void *mainloop_child_userdata(mainloop_child_t *child); + +//! \deprecated Do not use +void mainloop_clear_child_userdata(mainloop_child_t *child); + +#ifdef __cplusplus +} +#endif + +#endif // PCMK__CRM_COMMON_MAINLOOP_COMPAT__H diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 4ed7b025cce..d1214ebcaa2 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -984,36 +984,6 @@ mainloop_del_fd(mainloop_io_t *client) g_source_remove(client->source); } -pid_t -mainloop_child_pid(mainloop_child_t * child) -{ - return child->pid; -} - -const char * -mainloop_child_name(mainloop_child_t * child) -{ - return child->desc; -} - -int -mainloop_child_timeout(mainloop_child_t * child) -{ - return child->timed_out? TRUE : FALSE; -} - -void * -mainloop_child_userdata(mainloop_child_t * child) -{ - return child->user_data; -} - -void -mainloop_clear_child_userdata(mainloop_child_t * child) -{ - child->user_data = NULL; -} - /*! * \internal * \brief Send \c SIGKILL to a main loop child process or its process group @@ -1307,66 +1277,6 @@ compare_children_by_pid(const void *a, const void *b) return 0; } -/*! - * \brief Kill a child process tracked by the main loop - * - * If a process with PID \p pid is being tracked, send it a \c SIGKILL. - * - * If this function kills the child process successfully, remove the child from - * the tracking data structure and free the child. - * - * If the process is being tracked but no longer exists, don't remove or free - * the child yet. We will do this later when we receive a \c SIGCHLD for the - * child process. - * - * \param[in] pid Child PID - * - * \return \c TRUE if the child with ID \p pid was being tracked and either this - * function killed the process successfully or the process has already - * terminated but we have not received a \c SIGCHLD for it; or \c FALSE - * otherwise - */ -gboolean -mainloop_child_kill(pid_t pid) -{ - const mainloop_child_t cmp_data = { .pid = pid }; - GList *match = NULL; - mainloop_child_t *child = NULL; - int rc = pcmk_rc_ok; - bool no_hang = false; - - match = g_list_find_custom(child_list, &cmp_data, compare_children_by_pid); - if (match == NULL) { - return FALSE; - } - - child = match->data; - - rc = kill_child_pid(child); - if (rc == ESRCH) { - /* It's gone, but hasn't shown up in waitpid() yet. Wait until we get - * SIGCHLD and let handler clean it up as normal (so we get the correct - * return code/status). The blocking alternative would be to call - * child_waitpid(iter, false). - */ - pcmk__trace("Waiting for signal that child process %lld completed", - (long long) child->pid); - return TRUE; - } - - if (rc != pcmk_rc_ok) { - /* If kill() failed for some other reason, set the WNOHANG flag, since - * we can't be certain what happened. - * - * If kill() succeeded, we don't need the WNOHANG flag because SIGKILL - * can't be blocked. - */ - no_hang = true; - } - - return child_waitpid(match, no_hang)? TRUE : FALSE; -} - /*! * \internal * \brief Create a new \c mainloop_child_t object and add it to the main loop @@ -1480,76 +1390,6 @@ pcmk__main_loop_child_kill(pid_t pid) return child_waitpid(match, no_hang); } -/*! - * \brief Create a new \c mainloop_child_t object and add it to the main loop - * - * If the child process has not exited within \p timeout_ms, send it a - * \c SIGKILL signal. - * - * \param[in] pid Child PID (must be positive for correct behavior) - * \param[in] timeout_ms Timeout in milliseconds - * \param[in] desc Description - * \param[in] user_data User data - * \param[in] flags Group of enum mainloop_child_flags - * \param[in] exit_fn Function to call when the child process exits - */ -void -mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, - void *user_data, - enum mainloop_child_flags flags, - pcmk__mainloop_child_exit_fn_t exit_fn) -{ - static bool need_init = true; - - mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); - - child->pid = pid; - child->desc = pcmk__str_copy(desc); - child->user_data = user_data; - child->kill_group = !pcmk__is_set(flags, mainloop_leave_pid_group); - child->exit_fn = exit_fn; - - if (timeout_ms > 0) { - child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, - child); - } - - child_list = g_list_append(child_list, child); - - if (need_init) { - /* Invoke SIGCHLD processing from the main loop. This ensures that we - * don't add a child to the main loop and have the exit callback invoked - * for the child PID within the same call stack. - * - * @TODO Understand and document why this matters. - */ - need_init = false; - pcmk__create_timer(1, install_sigchld_handler, NULL); - } -} - -/*! - * \brief Create a new \c mainloop_child_t object and add it to the main loop - * - * If the child process has not exited within \p timeout_ms, send it a - * \c SIGKILL signal. - * - * \param[in] pid Child PID (must be positive for correct behavior) - * \param[in] timeout_ms Timeout in seconds - * \param[in] desc Description - * \param[in] user_data User data - * \param[in] exit_fn Function to call when the child process exits - * - * \note This is a convenience wrapper for \c mainloop_child_add_with_flags() - * that doesn't set any flags. - */ -void -mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, - pcmk__mainloop_child_exit_fn_t exit_fn) -{ - mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, exit_fn); -} - static gboolean mainloop_timer_cb(void *user_data) { @@ -1729,3 +1569,107 @@ pcmk_drain_main_loop(GMainLoop *mloop, unsigned int timer_ms, g_source_remove(timer); } } + +// Deprecated functions kept only for backward API compatibility +// LCOV_EXCL_START + +#include + +void +mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, + void *user_data, + enum mainloop_child_flags flags, + pcmk__mainloop_child_exit_fn_t exit_fn) +{ + static bool need_init = true; + + mainloop_child_t *child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); + + child->pid = pid; + child->desc = pcmk__str_copy(desc); + child->user_data = user_data; + child->kill_group = !pcmk__is_set(flags, mainloop_leave_pid_group); + child->exit_fn = exit_fn; + + if (timeout_ms > 0) { + child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, + child); + } + + child_list = g_list_append(child_list, child); + + if (need_init) { + need_init = false; + pcmk__create_timer(1, install_sigchld_handler, NULL); + } +} + +void +mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, + pcmk__mainloop_child_exit_fn_t exit_fn) +{ + mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, exit_fn); +} + +gboolean +mainloop_child_kill(pid_t pid) +{ + const mainloop_child_t cmp_data = { .pid = pid }; + GList *match = NULL; + mainloop_child_t *child = NULL; + int rc = pcmk_rc_ok; + bool no_hang = false; + + match = g_list_find_custom(child_list, &cmp_data, compare_children_by_pid); + if (match == NULL) { + return FALSE; + } + + child = match->data; + + rc = kill_child_pid(child); + if (rc == ESRCH) { + pcmk__trace("Waiting for signal that child process %lld completed", + (long long) child->pid); + return TRUE; + } + + if (rc != pcmk_rc_ok) { + no_hang = true; + } + + return child_waitpid(match, no_hang)? TRUE : FALSE; +} + +pid_t +mainloop_child_pid(mainloop_child_t *child) +{ + return child->pid; +} + +const char * +mainloop_child_name(mainloop_child_t *child) +{ + return child->desc; +} + +int +mainloop_child_timeout(mainloop_child_t *child) +{ + return child->timed_out? TRUE : FALSE; +} + +void * +mainloop_child_userdata(mainloop_child_t *child) +{ + return child->user_data; +} + +void +mainloop_clear_child_userdata(mainloop_child_t *child) +{ + child->user_data = NULL; +} + +// LCOV_EXCL_STOP +// End deprecated API From 840bbec6f630eedc56134d3d047f3b6efbc81d1d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 17:16:06 -0700 Subject: [PATCH 37/70] Refactor: libcrmcommon: Make free_main_loop_child() a GDestroyNotify Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d1214ebcaa2..bd35f223832 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -49,13 +49,20 @@ static qb_array_t *gio_map = NULL; * * If the child has an associated timer, remove it. * - * \param[in,out] child Main loop child + * \param[in,out] data Main loop child (mainloop_child_t *) * - * \note This does not free \p child->user_data. + * \note This does not free the child's \c user_data field. + * \note This is a \c GDestroyNotify. */ static void -free_main_loop_child(mainloop_child_t *child) +free_main_loop_child(void *data) { + mainloop_child_t *child = data; + + if (child == NULL) { + return; + } + if (child->timer_id != 0) { pcmk__trace("Removing timer %u", child->timer_id); g_source_remove(child->timer_id); @@ -433,7 +440,7 @@ mainloop_destroy_signal(int sig) void mainloop_cleanup(void) { - g_list_free_full(child_list, (GDestroyNotify) free_main_loop_child); + g_list_free_full(child_list, free_main_loop_child); child_list = NULL; g_clear_pointer(&gio_map, qb_array_free); From 051ca958b826913e6f47776fd9db17291305306c Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 21:11:49 -0700 Subject: [PATCH 38/70] Refactor: libcrmcommon: Reorder some mainloop.c functions Put all the child-related functions together. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 156 +++++++++++++++++++++--------------------- 1 file changed, 78 insertions(+), 78 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index bd35f223832..59b7b9443d8 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -43,35 +43,6 @@ struct mainloop_timer_s { static GList *child_list = NULL; static qb_array_t *gio_map = NULL; -/*! - * \internal - * \brief Free a main loop child - * - * If the child has an associated timer, remove it. - * - * \param[in,out] data Main loop child (mainloop_child_t *) - * - * \note This does not free the child's \c user_data field. - * \note This is a \c GDestroyNotify. - */ -static void -free_main_loop_child(void *data) -{ - mainloop_child_t *child = data; - - if (child == NULL) { - return; - } - - if (child->timer_id != 0) { - pcmk__trace("Removing timer %u", child->timer_id); - g_source_remove(child->timer_id); - } - - free(child->desc); - free(child); -} - static gboolean crm_trigger_prepare(GSource *source, int *timeout) { @@ -431,25 +402,6 @@ mainloop_destroy_signal(int sig) return TRUE; } -/*! - * \internal - * \brief Free data structures used for the mainloop - * - * \todo This is incomplete. Free other data structures created in this file. - */ -void -mainloop_cleanup(void) -{ - g_list_free_full(child_list, free_main_loop_child); - child_list = NULL; - - g_clear_pointer(&gio_map, qb_array_free); - - for (int sig = 0; sig < NSIG; ++sig) { - mainloop_destroy_signal_entry(sig); - } -} - /* * libqb... */ @@ -1067,6 +1019,35 @@ child_timeout_callback(void *user_data) return G_SOURCE_REMOVE; } +/*! + * \internal + * \brief Free a main loop child + * + * If the child has an associated timer, remove it. + * + * \param[in,out] data Main loop child (mainloop_child_t *) + * + * \note This does not free the child's \c user_data field. + * \note This is a \c GDestroyNotify. + */ +static void +free_main_loop_child(void *data) +{ + mainloop_child_t *child = data; + + if (child == NULL) { + return; + } + + if (child->timer_id != 0) { + pcmk__trace("Removing timer %u", child->timer_id); + g_source_remove(child->timer_id); + } + + free(child->desc); + free(child); +} + /*! * \internal * \brief Wait on a child process and free it if terminated @@ -1254,36 +1235,6 @@ install_sigchld_handler(void *user_data) return G_SOURCE_REMOVE; } -/*! - * \internal - * \brief Compare two mainloop child objects by PID - * - * \param[in] a First child to compare (const mainloop_child_t *) - * \param[in] b Second child to compare (const mainloop_child_t *) - * - * \retval -1 if \p a->pid is less than \p b->pid - * \retval 0 if \p a->pid is equal to \p b->pid - * \retval 1 if \p a->pid is greater than \p b->pid - * - * \note This is a \c GCompareFunc. - */ -static int -compare_children_by_pid(const void *a, const void *b) -{ - const mainloop_child_t *child1 = a; - const mainloop_child_t *child2 = b; - - if (child1->pid < child2->pid) { - return -1; - } - - if (child1->pid > child2->pid) { - return 1; - } - - return 0; -} - /*! * \internal * \brief Create a new \c mainloop_child_t object and add it to the main loop @@ -1334,6 +1285,36 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, } } +/*! + * \internal + * \brief Compare two mainloop child objects by PID + * + * \param[in] a First child to compare (const mainloop_child_t *) + * \param[in] b Second child to compare (const mainloop_child_t *) + * + * \retval -1 if \p a->pid is less than \p b->pid + * \retval 0 if \p a->pid is equal to \p b->pid + * \retval 1 if \p a->pid is greater than \p b->pid + * + * \note This is a \c GCompareFunc. + */ +static int +compare_children_by_pid(const void *a, const void *b) +{ + const mainloop_child_t *child1 = a; + const mainloop_child_t *child2 = b; + + if (child1->pid < child2->pid) { + return -1; + } + + if (child1->pid > child2->pid) { + return 1; + } + + return 0; +} + /*! * \internal * \brief Kill a child process tracked by the main loop @@ -1577,6 +1558,25 @@ pcmk_drain_main_loop(GMainLoop *mloop, unsigned int timer_ms, } } +/*! + * \internal + * \brief Free data structures used for the mainloop + * + * \todo This is incomplete. Free other data structures created in this file. + */ +void +mainloop_cleanup(void) +{ + g_list_free_full(child_list, free_main_loop_child); + child_list = NULL; + + g_clear_pointer(&gio_map, qb_array_free); + + for (int sig = 0; sig < NSIG; ++sig) { + mainloop_destroy_signal_entry(sig); + } +} + // Deprecated functions kept only for backward API compatibility // LCOV_EXCL_START From 1df7e281b8c51c89be0958f40e1b02cbcb767c2d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 21:36:28 -0700 Subject: [PATCH 39/70] Refactor: libcrmcommon: Move pcmk__mainloop_child_exit_fn_t It was in the public header file for the sake of mainloop_add_child*(). Signed-off-by: Reid Wahl --- include/crm/common/mainloop.h | 4 ---- include/crm/common/mainloop_compat.h | 7 +++++-- include/crm/common/mainloop_internal.h | 4 ++++ lib/common/mainloop.c | 6 ++++-- 4 files changed, 13 insertions(+), 8 deletions(-) diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 2120ad70680..7c5f1fa98a3 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -41,10 +41,6 @@ typedef struct mainloop_child_s mainloop_child_t; // NOTE: sbd (as of at least 1.5.2) uses this typedef struct mainloop_timer_s mainloop_timer_t; -//! \deprecated This has been for internal use only since its creation. -typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *p, int core, - int signo, int exitcode); - void mainloop_cleanup(void); // NOTE: sbd (as of at least 1.5.2) uses this diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h index 2863fa200b1..70f8ff6ab5a 100644 --- a/include/crm/common/mainloop_compat.h +++ b/include/crm/common/mainloop_compat.h @@ -37,12 +37,15 @@ enum mainloop_child_flags { //! \deprecated Do not use void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void *user_data, enum mainloop_child_flags, - pcmk__mainloop_child_exit_fn_t exit_fn); + void (*exit_fn)(mainloop_child_t *child, + int core, int signo, + int exit_code)); //! \deprecated Do not use void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, - pcmk__mainloop_child_exit_fn_t exit_fn); + void (*exit_fn)(mainloop_child_t *child, int core, + int signo, int exit_code)); //! \deprecated Do not use gboolean mainloop_child_kill(pid_t pid); diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 81dccdf3152..0371b67b9d7 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -24,6 +24,10 @@ extern "C" { #endif +typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *child, + int core, int signo, + int exit_code); + /*! * \internal * \brief Info about a child process tracked by a main event loop diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 59b7b9443d8..fa2287389d0 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1586,7 +1586,8 @@ void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void *user_data, enum mainloop_child_flags flags, - pcmk__mainloop_child_exit_fn_t exit_fn) + void (*exit_fn)(mainloop_child_t *child, int core, + int signo, int exit_code)) { static bool need_init = true; @@ -1613,7 +1614,8 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, - pcmk__mainloop_child_exit_fn_t exit_fn) + void (*exit_fn)(mainloop_child_t *child, int core, int signo, + int exit_code)) { mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, exit_fn); } From 4de76fc47512c514f17975bfcd6d59dc2c61c155 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 21:25:17 -0700 Subject: [PATCH 40/70] Refactor: libcrmcommon: mainloop_child_t -> pcmk__main_loop_child_t We still have to keep the old name and keep the structs aligned (via a typedef, until the public API for mainloop_child_t is dropped. Signed-off-by: Reid Wahl --- daemons/based/based_io.c | 4 +-- daemons/execd/remoted_schemas.c | 2 +- daemons/pacemakerd/pcmkd_subdaemons.c | 4 +-- include/crm/common/mainloop.h | 3 --- include/crm/common/mainloop_compat.h | 3 +++ include/crm/common/mainloop_internal.h | 10 ++++++- lib/common/mainloop.c | 37 ++++++++++++++------------ lib/services/services_linux.c | 3 ++- 8 files changed, 39 insertions(+), 27 deletions(-) diff --git a/daemons/based/based_io.c b/daemons/based/based_io.c index 561cf17d739..60f541eb8bb 100644 --- a/daemons/based/based_io.c +++ b/daemons/based/based_io.c @@ -45,13 +45,13 @@ static crm_trigger_t *write_trigger = NULL; * \internal * \brief Process the exit status of a child forked from \c write_cib_async() * - * \param[in] child Mainloop child data + * \param[in] child Main loop child * \param[in] core If set to 1, the child process dumped core * \param[in] signo Signal that the child process exited with * \param[in] exit_code Child process's exit code */ static void -write_cib_cb(mainloop_child_t *child, int core, int signo, int exit_code) +write_cib_cb(pcmk__main_loop_child_t *child, int core, int signo, int exit_code) { const char *error = "Could not write CIB to disk"; diff --git a/daemons/execd/remoted_schemas.c b/daemons/execd/remoted_schemas.c index ec20eb8acdb..68baae6bfe8 100644 --- a/daemons/execd/remoted_schemas.c +++ b/daemons/execd/remoted_schemas.c @@ -201,7 +201,7 @@ get_schema_files(void) * saving them to disk. */ static void -get_schema_files_complete(mainloop_child_t *p, int core, int signo, +get_schema_files_complete(pcmk__main_loop_child_t *p, int core, int signo, int exitcode) { const char *errmsg = "Could not load additional schema files"; diff --git a/daemons/pacemakerd/pcmkd_subdaemons.c b/daemons/pacemakerd/pcmkd_subdaemons.c index 681e89d9b86..b29af46948e 100644 --- a/daemons/pacemakerd/pcmkd_subdaemons.c +++ b/daemons/pacemakerd/pcmkd_subdaemons.c @@ -104,7 +104,7 @@ static bool fatal_error = false; static int child_liveness(pcmkd_child_t *child); static gboolean escalate_shutdown(void *data); static int start_child(pcmkd_child_t *child); -static void pcmk_child_exit(mainloop_child_t *p, int core, int signo, +static void pcmk_child_exit(pcmk__main_loop_child_t *p, int core, int signo, int exitcode); static void pcmk_process_exit(pcmkd_child_t *child); static gboolean pcmk_shutdown_worker(void *user_data); @@ -254,7 +254,7 @@ escalate_shutdown(void *data) } static void -pcmk_child_exit(mainloop_child_t *p, int core, int signo, int exitcode) +pcmk_child_exit(pcmk__main_loop_child_t *p, int core, int signo, int exitcode) { pcmkd_child_t *child = p->user_data; diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 7c5f1fa98a3..d53afc374e0 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -35,9 +35,6 @@ typedef struct trigger_s crm_trigger_t; typedef struct mainloop_io_s mainloop_io_t; -//! \deprecated Do not use -typedef struct mainloop_child_s mainloop_child_t; - // NOTE: sbd (as of at least 1.5.2) uses this typedef struct mainloop_timer_s mainloop_timer_t; diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h index 70f8ff6ab5a..95c865e9064 100644 --- a/include/crm/common/mainloop_compat.h +++ b/include/crm/common/mainloop_compat.h @@ -29,6 +29,9 @@ extern "C" { * release. */ +//! \deprecated Do not use +typedef struct mainloop_child_s mainloop_child_t; + //! \deprecated Do not use enum mainloop_child_flags { mainloop_leave_pid_group = 0x01, diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 0371b67b9d7..4730ef97026 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -24,7 +24,12 @@ extern "C" { #endif -typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *child, +/* Forward-declare because pcmk__main_loop_child_exit_fn_t takes a + * (pcmk__main_loop_child_t *) argument + */ +typedef struct mainloop_child_s pcmk__main_loop_child_t; + +typedef void (*pcmk__mainloop_child_exit_fn_t)(pcmk__main_loop_child_t *child, int core, int signo, int exit_code); @@ -33,6 +38,9 @@ typedef void (*pcmk__mainloop_child_exit_fn_t)(mainloop_child_t *child, * \brief Info about a child process tracked by a main event loop */ struct mainloop_child_s { + /* @COMPAT Drop "struct mainloop_child_s" when we drop it from + * mainloop_compat.h + */ pid_t pid; //!< Child PID char *desc; //!< Description unsigned int timer_id; //!< ID of timer for child timeout diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index fa2287389d0..ec1e2d0458a 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -956,7 +956,7 @@ mainloop_del_fd(mainloop_io_t *client) * or \c errno after calling \c kill() otherwise) */ static int -kill_child_pid(const mainloop_child_t *child) +kill_child_pid(const pcmk__main_loop_child_t *child) { const pid_t pid = (child->kill_group? -child->pid : child->pid); int rc = 0; @@ -982,7 +982,8 @@ kill_child_pid(const mainloop_child_t *child) * \internal * \brief Kill a child process after its timeout has expired * - * \param[in,out] user_data Main loop child (mainloop_child_t *) + * \param[in,out] user_data Main loop child + * (pcmk__main_loop_child_t *) * * \return \c G_SOURCE_REMOVE (to destroy the timeout that triggered this call) * @@ -991,7 +992,7 @@ kill_child_pid(const mainloop_child_t *child) static gboolean child_timeout_callback(void *user_data) { - mainloop_child_t *child = user_data; + pcmk__main_loop_child_t *child = user_data; int rc = pcmk_rc_ok; const char *result_s = NULL; @@ -1025,7 +1026,7 @@ child_timeout_callback(void *user_data) * * If the child has an associated timer, remove it. * - * \param[in,out] data Main loop child (mainloop_child_t *) + * \param[in,out] data Main loop child (pcmk__main_loop_child_t *) * * \note This does not free the child's \c user_data field. * \note This is a \c GDestroyNotify. @@ -1033,7 +1034,7 @@ child_timeout_callback(void *user_data) static void free_main_loop_child(void *data) { - mainloop_child_t *child = data; + pcmk__main_loop_child_t *child = data; if (child == NULL) { return; @@ -1082,7 +1083,7 @@ child_waitpid(GList *link, bool no_hang) { const int options = no_hang? WNOHANG : 0; - mainloop_child_t *child = NULL; + pcmk__main_loop_child_t *child = NULL; pid_t rc = 0; int status = 0; @@ -1107,8 +1108,8 @@ child_waitpid(GList *link, bool no_hang) /* This situation should probably never happen in practice. Setting * exit_code to 1 is misleading in that it indicates the child * exited with code 1, and we don't know that to be true. We could - * add a mainloop_child_t flag to indicate this case, but it doesn't - * seem worth it. + * add a pcmk__main_loop_child_t flag to indicate this case, but it + * doesn't seem worth it. */ exit_code = 1; @@ -1237,7 +1238,7 @@ install_sigchld_handler(void *user_data) /*! * \internal - * \brief Create a new \c mainloop_child_t object and add it to the main loop + * \brief Create a \c pcmk__main_loop_child_t object and add it to the main loop * * If the child process has not exited within \p timeout_ms, send it a * \c SIGKILL signal. @@ -1258,11 +1259,11 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, { static bool need_init = true; - mainloop_child_t *child = NULL; + pcmk__main_loop_child_t *child = NULL; pcmk__assert(pid > 0); - child = pcmk__assert_alloc(1, sizeof(mainloop_child_t)); + child = pcmk__assert_alloc(1, sizeof(pcmk__main_loop_child_t)); child->pid = pid; child->desc = pcmk__str_copy(desc); child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, @@ -1289,8 +1290,10 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, * \internal * \brief Compare two mainloop child objects by PID * - * \param[in] a First child to compare (const mainloop_child_t *) - * \param[in] b Second child to compare (const mainloop_child_t *) + * \param[in] a First child to compare + * (const pcmk__main_loop_child_t *) + * \param[in] b Second child to compare + * (const pcmk__main_loop_child_t *) * * \retval -1 if \p a->pid is less than \p b->pid * \retval 0 if \p a->pid is equal to \p b->pid @@ -1301,8 +1304,8 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, static int compare_children_by_pid(const void *a, const void *b) { - const mainloop_child_t *child1 = a; - const mainloop_child_t *child2 = b; + const pcmk__main_loop_child_t *child1 = a; + const pcmk__main_loop_child_t *child2 = b; if (child1->pid < child2->pid) { return -1; @@ -1338,9 +1341,9 @@ compare_children_by_pid(const void *a, const void *b) bool pcmk__main_loop_child_kill(pid_t pid) { - const mainloop_child_t cmp_data = { .pid = pid }; + const pcmk__main_loop_child_t cmp_data = { .pid = pid }; GList *match = NULL; - mainloop_child_t *child = NULL; + pcmk__main_loop_child_t *child = NULL; int rc = pcmk_rc_ok; bool no_hang = false; diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index a1f23e43ce5..5c491cecdab 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -706,7 +706,8 @@ parse_exit_reason_from_stderr(svc_action_t *op) * \param[in] exitcode Exit status of child process */ static void -async_action_complete(mainloop_child_t *p, int core, int signo, int exitcode) +async_action_complete(pcmk__main_loop_child_t *p, int core, int signo, + int exitcode) { svc_action_t *op = p->user_data; From a96a19672f90f8d4b463d0cc0b54148853151ec0 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 21:54:04 -0700 Subject: [PATCH 41/70] Refactor: libcrmcommon: Rename pcmk__mainloop_child_exit_fn_t ...to pcmk__main_loop_child_cb_t, which is at least a little bit shorter. This typedef is used only in the pcmk__main_loop_child_t definition and in the pcmk__main_loop_child_create() signature. It feels almost gratuituous but maybe it helps with readability. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 15 +++++++++------ lib/common/mainloop.c | 2 +- 2 files changed, 10 insertions(+), 7 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 4730ef97026..023e48696f9 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -24,14 +24,17 @@ extern "C" { #endif -/* Forward-declare because pcmk__main_loop_child_exit_fn_t takes a +/* Forward-declare because pcmk__main_loop_child_cb_t takes a * (pcmk__main_loop_child_t *) argument */ typedef struct mainloop_child_s pcmk__main_loop_child_t; -typedef void (*pcmk__mainloop_child_exit_fn_t)(pcmk__main_loop_child_t *child, - int core, int signo, - int exit_code); +/*! + * \internal + * \brief Callback function called when a child process terminates + */ +typedef void (*pcmk__main_loop_child_cb_t)(pcmk__main_loop_child_t *child, + int core, int signo, int exit_code); /*! * \internal @@ -54,7 +57,7 @@ struct mainloop_child_s { bool kill_group; //! Callback function called when the child terminates - pcmk__mainloop_child_exit_fn_t exit_fn; + pcmk__main_loop_child_cb_t exit_fn; }; struct mainloop_io_s { @@ -74,7 +77,7 @@ struct mainloop_io_s { void pcmk__main_loop_child_create(pid_t pid, const char *desc, unsigned int timeout_ms, void *user_data, bool kill_group, - pcmk__mainloop_child_exit_fn_t exit_fn); + pcmk__main_loop_child_cb_t exit_fn); bool pcmk__main_loop_child_kill(pid_t pid); int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index ec1e2d0458a..59d5eeb8479 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1255,7 +1255,7 @@ void pcmk__main_loop_child_create(pid_t pid, const char *desc, unsigned int timeout_ms, void *user_data, bool kill_group, - pcmk__mainloop_child_exit_fn_t exit_fn) + pcmk__main_loop_child_cb_t exit_fn) { static bool need_init = true; From db80ae4d293ca2ba7f6994047ea856fb105a284b Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 21:59:45 -0700 Subject: [PATCH 42/70] Refactor: libcrmcommon: Rename pcmk__main_loop_child_t:exit_fn ...to callback. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_compat.h | 10 +++++----- include/crm/common/mainloop_internal.h | 4 ++-- lib/common/mainloop.c | 22 +++++++++++----------- 3 files changed, 18 insertions(+), 18 deletions(-) diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h index 95c865e9064..6cd18620cbf 100644 --- a/include/crm/common/mainloop_compat.h +++ b/include/crm/common/mainloop_compat.h @@ -40,15 +40,15 @@ enum mainloop_child_flags { //! \deprecated Do not use void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void *user_data, enum mainloop_child_flags, - void (*exit_fn)(mainloop_child_t *child, - int core, int signo, - int exit_code)); + void (*callback)(mainloop_child_t *child, + int core, int signo, + int exit_code)); //! \deprecated Do not use void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, - void (*exit_fn)(mainloop_child_t *child, int core, - int signo, int exit_code)); + void (*callback)(mainloop_child_t *child, int core, + int signo, int exit_code)); //! \deprecated Do not use gboolean mainloop_child_kill(pid_t pid); diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 023e48696f9..8afb69efc99 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -57,7 +57,7 @@ struct mainloop_child_s { bool kill_group; //! Callback function called when the child terminates - pcmk__main_loop_child_cb_t exit_fn; + pcmk__main_loop_child_cb_t callback; }; struct mainloop_io_s { @@ -77,7 +77,7 @@ struct mainloop_io_s { void pcmk__main_loop_child_create(pid_t pid, const char *desc, unsigned int timeout_ms, void *user_data, bool kill_group, - pcmk__main_loop_child_cb_t exit_fn); + pcmk__main_loop_child_cb_t callback); bool pcmk__main_loop_child_kill(pid_t pid); int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 59d5eeb8479..a1f83596b5f 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1177,8 +1177,8 @@ child_waitpid(GList *link, bool no_hang) CRM_CHECK(false, return false); terminated: - if (child->exit_fn != NULL) { - child->exit_fn(child, core, signo, exit_code); + if (child->callback != NULL) { + child->callback(child, core, signo, exit_code); } pcmk__trace("Removing terminated process %lld from child list", @@ -1249,13 +1249,13 @@ install_sigchld_handler(void *user_data) * \param[in] user_data User data * \param[in] kill group If \c true, kill the child's entire process group on * timeout; otherwise, kill only the child process - * \param[in] exit_fn Function to call when the child process exits + * \param[in] callback Function to call when the child process terminates */ void pcmk__main_loop_child_create(pid_t pid, const char *desc, unsigned int timeout_ms, void *user_data, bool kill_group, - pcmk__main_loop_child_cb_t exit_fn) + pcmk__main_loop_child_cb_t callback) { static bool need_init = true; @@ -1270,7 +1270,7 @@ pcmk__main_loop_child_create(pid_t pid, const char *desc, child); child->user_data = user_data; child->kill_group = kill_group; - child->exit_fn = exit_fn; + child->callback = callback; child_list = g_list_append(child_list, child); @@ -1589,8 +1589,8 @@ void mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void *user_data, enum mainloop_child_flags flags, - void (*exit_fn)(mainloop_child_t *child, int core, - int signo, int exit_code)) + void (*callback)(mainloop_child_t *child, int core, + int signo, int exit_code)) { static bool need_init = true; @@ -1600,7 +1600,7 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, child->desc = pcmk__str_copy(desc); child->user_data = user_data; child->kill_group = !pcmk__is_set(flags, mainloop_leave_pid_group); - child->exit_fn = exit_fn; + child->callback = callback; if (timeout_ms > 0) { child->timer_id = pcmk__create_timer(timeout_ms, child_timeout_callback, @@ -1617,10 +1617,10 @@ mainloop_child_add_with_flags(pid_t pid, int timeout_ms, const char *desc, void mainloop_child_add(pid_t pid, int timeout_ms, const char *desc, void *user_data, - void (*exit_fn)(mainloop_child_t *child, int core, int signo, - int exit_code)) + void (*callback)(mainloop_child_t *child, int core, + int signo, int exit_code)) { - mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, exit_fn); + mainloop_child_add_with_flags(pid, timeout_ms, desc, user_data, 0, callback); } gboolean From f692591352e131bbc04a15b37cb919410a695dc3 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 22:14:31 -0700 Subject: [PATCH 43/70] Refactor: libcrmcommon: Drop pcmk__mainloop_timer_get_period() Signed-off-by: Reid Wahl --- daemons/fenced/fenced_commands.c | 7 +++---- include/crm/common/mainloop_internal.h | 10 +++++++++- lib/common/mainloop.c | 25 ------------------------- 3 files changed, 12 insertions(+), 30 deletions(-) diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index b6dd543f5fc..65415180f8c 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -629,7 +629,6 @@ static int get_agent_metadata_cb(void *data) { fenced_device_t *device = data; - unsigned int period_ms = 0; int rc = get_agent_metadata(device->agent, &device->agent_metadata); if (rc == pcmk_rc_ok) { @@ -643,9 +642,9 @@ get_agent_metadata_cb(void *data) } if (rc == EAGAIN) { - period_ms = pcmk__mainloop_timer_get_period(device->timer); - if (period_ms < 160 * 1000) { - mainloop_timer_set_period(device->timer, 2 * period_ms); + if (device->timer->period_ms < (160 * 1000)) { + mainloop_timer_set_period(device->timer, + (2 * device->timer->period_ms)); } return G_SOURCE_CONTINUE; diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 8afb69efc99..b1922c67e62 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -60,6 +60,15 @@ struct mainloop_child_s { pcmk__main_loop_child_cb_t callback; }; +struct mainloop_timer_s { + unsigned int id; + unsigned int period_ms; + bool repeat; + char *name; + GSourceFunc cb; + void *userdata; +}; + struct mainloop_io_s { char *name; void *userdata; @@ -83,7 +92,6 @@ bool pcmk__main_loop_child_kill(pid_t pid); int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); -unsigned int pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer); #ifdef __cplusplus } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index a1f83596b5f..2b8da151058 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -31,15 +31,6 @@ struct trigger_s { unsigned int id; }; -struct mainloop_timer_s { - unsigned int id; - unsigned int period_ms; - bool repeat; - char *name; - GSourceFunc cb; - void *userdata; -}; - static GList *child_list = NULL; static qb_array_t *gio_map = NULL; @@ -837,22 +828,6 @@ pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, return pcmk_rc_ok; } -/*! - * \brief Get period for mainloop timer - * - * \param[in] timer Timer - * - * \return Period in ms - */ -unsigned int -pcmk__mainloop_timer_get_period(const mainloop_timer_t *timer) -{ - if (timer) { - return timer->period_ms; - } - return 0; -} - mainloop_io_t * mainloop_add_ipc_client(const char *name, int priority, size_t max_size, void *userdata, struct ipc_client_callbacks *callbacks) From f699c94cf749f90a5db3b930a276ca383f712649 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Fri, 14 Aug 2026 22:24:40 -0700 Subject: [PATCH 44/70] Refactor: libcrmcommon: Lightly reformat main loop timer functions Rename t to timer, add whitespace, unindent a bit, etc. These changes are simple enough that I elected to do them all in one commit. Signed-off-by: Reid Wahl --- include/crm/common/mainloop.h | 10 +-- lib/common/mainloop.c | 124 +++++++++++++++++++--------------- 2 files changed, 73 insertions(+), 61 deletions(-) diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index d53afc374e0..89d85d983f7 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -63,15 +63,15 @@ gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)); gboolean mainloop_destroy_signal(int sig); -bool mainloop_timer_running(mainloop_timer_t *t); +bool mainloop_timer_running(mainloop_timer_t *timer); // NOTE: sbd (as of at least 1.5.2) uses this -void mainloop_timer_start(mainloop_timer_t *t); +void mainloop_timer_start(mainloop_timer_t *timer); // NOTE: sbd (as of at least 1.5.2) uses this -void mainloop_timer_stop(mainloop_timer_t *t); +void mainloop_timer_stop(mainloop_timer_t *timer); -unsigned int mainloop_timer_set_period(mainloop_timer_t *t, +unsigned int mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int period_ms); // NOTE: sbd (as of at least 1.5.2) uses this @@ -79,7 +79,7 @@ mainloop_timer_t *mainloop_timer_add(const char *name, unsigned int period_ms, bool repeat, GSourceFunc cb, void *userdata); -void mainloop_timer_del(mainloop_timer_t *t); +void mainloop_timer_del(mainloop_timer_t *timer); struct ipc_client_callbacks { /*! diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 2b8da151058..8ff98bd4503 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1360,75 +1360,81 @@ static gboolean mainloop_timer_cb(void *user_data) { int id = 0; - bool repeat = FALSE; - struct mainloop_timer_s *t = user_data; - - pcmk__assert(t != NULL); - - id = t->id; - t->id = 0; /* Ensure it's unset during callbacks so that - * mainloop_timer_running() works as expected - */ - - if(t->cb) { - pcmk__trace("Invoking callbacks for timer %s", t->name); - repeat = t->repeat; - if(t->cb(t->userdata) == FALSE) { - pcmk__trace("Timer %s complete", t->name); - repeat = FALSE; + bool repeat = false; + mainloop_timer_t *timer = user_data; + + pcmk__assert(timer != NULL); + + id = timer->id; + timer->id = 0; /* Ensure it's unset during callbacks so that + * mainloop_timer_running() works as expected + */ + + if (timer->cb != NULL) { + pcmk__trace("Invoking callbacks for timer %s", timer->name); + repeat = timer->repeat; + + if (!timer->cb(timer->userdata)) { + pcmk__trace("Timer %s complete", timer->name); + repeat = false; } } - if(repeat) { + if (repeat) { /* Restore if repeating */ - t->id = id; + timer->id = id; } return repeat; } bool -mainloop_timer_running(mainloop_timer_t *t) +mainloop_timer_running(mainloop_timer_t *timer) { - if(t && t->id != 0) { - return TRUE; - } - return FALSE; + return (timer != NULL) && (timer->id != 0); } void -mainloop_timer_start(mainloop_timer_t *t) +mainloop_timer_start(mainloop_timer_t *timer) { - mainloop_timer_stop(t); - if(t && t->period_ms > 0) { - pcmk__trace("Starting timer %s", t->name); - t->id = pcmk__create_timer(t->period_ms, mainloop_timer_cb, t); + mainloop_timer_stop(timer); + + if ((timer == NULL) || (timer->period_ms == 0)) { + return; } + + pcmk__trace("Starting timer %s", timer->name); + timer->id = pcmk__create_timer(timer->period_ms, mainloop_timer_cb, timer); } void -mainloop_timer_stop(mainloop_timer_t *t) +mainloop_timer_stop(mainloop_timer_t *timer) { - if(t && t->id != 0) { - pcmk__trace("Stopping timer %s", t->name); - g_source_remove(t->id); - t->id = 0; + if ((timer == NULL) || (timer->id == 0)) { + return; } + + pcmk__trace("Stopping timer %s", timer->name); + g_source_remove(timer->id); + timer->id = 0; } unsigned int -mainloop_timer_set_period(mainloop_timer_t *t, unsigned int period_ms) +mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int period_ms) { unsigned int last = 0; - if(t) { - last = t->period_ms; - t->period_ms = period_ms; + if (timer == NULL) { + return 0; } - if(t && t->id != 0 && last != t->period_ms) { - mainloop_timer_start(t); + last = timer->period_ms; + timer->period_ms = period_ms; + + if ((timer->id != 0) && (timer->period_ms != last)) { + mainloop_timer_start(timer); } + return last; } @@ -1436,31 +1442,37 @@ mainloop_timer_t * mainloop_timer_add(const char *name, unsigned int period_ms, bool repeat, GSourceFunc cb, void *userdata) { - mainloop_timer_t *t = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); + mainloop_timer_t *timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); if (name != NULL) { - t->name = pcmk__assert_asprintf("%s-%u-%d", name, period_ms, repeat); + timer->name = pcmk__assert_asprintf("%s-%u-%d", name, period_ms, + repeat); + } else { - t->name = pcmk__assert_asprintf("%p-%u-%d", t, period_ms, repeat); - } - t->id = 0; - t->period_ms = period_ms; - t->repeat = repeat; - t->cb = cb; - t->userdata = userdata; - pcmk__trace("Created timer %s with %p %p", t->name, userdata, t->userdata); - return t; + timer->name = pcmk__assert_asprintf("%p-%u-%d", timer, period_ms, + repeat); + } + + timer->period_ms = period_ms; + timer->repeat = repeat; + timer->cb = cb; + timer->userdata = userdata; + + pcmk__trace("Created timer %s with %p", timer->name, userdata); + return timer; } void -mainloop_timer_del(mainloop_timer_t *t) +mainloop_timer_del(mainloop_timer_t *timer) { - if(t) { - pcmk__trace("Destroying timer %s", t->name); - mainloop_timer_stop(t); - free(t->name); - free(t); + if (timer == NULL) { + return; } + + pcmk__trace("Destroying timer %s", timer->name); + mainloop_timer_stop(timer); + free(timer->name); + free(timer); } /* From eb9c52261264855a629a2b5912ac301260b286b5 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 14:40:08 -0700 Subject: [PATCH 45/70] Refactor: libcrmcommon: Don't start main loop timer if callback is NULL If timer->cb were NULL, mainloop_timer_cb() would do nothing except set timer->id to 0 and remove the timeout source. Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 8ff98bd4503..12d01de931e 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1363,21 +1363,19 @@ mainloop_timer_cb(void *user_data) bool repeat = false; mainloop_timer_t *timer = user_data; - pcmk__assert(timer != NULL); + pcmk__assert((timer != NULL) && (timer->cb != NULL)); id = timer->id; timer->id = 0; /* Ensure it's unset during callbacks so that * mainloop_timer_running() works as expected */ - if (timer->cb != NULL) { - pcmk__trace("Invoking callbacks for timer %s", timer->name); - repeat = timer->repeat; + pcmk__trace("Invoking callbacks for timer %s", timer->name); + repeat = timer->repeat; - if (!timer->cb(timer->userdata)) { - pcmk__trace("Timer %s complete", timer->name); - repeat = false; - } + if (!timer->cb(timer->userdata)) { + pcmk__trace("Timer %s complete", timer->name); + repeat = false; } if (repeat) { @@ -1399,7 +1397,7 @@ mainloop_timer_start(mainloop_timer_t *timer) { mainloop_timer_stop(timer); - if ((timer == NULL) || (timer->period_ms == 0)) { + if ((timer == NULL) || (timer->period_ms == 0) || (timer->cb == NULL)) { return; } From 3991bf9d25ecff6b2afe9d73fc3be3201e5b195b Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 14:50:29 -0700 Subject: [PATCH 46/70] Refactor: libcrmcommon: Drop repeat variable from mainloop_timer_cb() Signed-off-by: Reid Wahl --- lib/common/mainloop.c | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 12d01de931e..d3ec75d43a5 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1360,7 +1360,6 @@ static gboolean mainloop_timer_cb(void *user_data) { int id = 0; - bool repeat = false; mainloop_timer_t *timer = user_data; pcmk__assert((timer != NULL) && (timer->cb != NULL)); @@ -1371,19 +1370,19 @@ mainloop_timer_cb(void *user_data) */ pcmk__trace("Invoking callbacks for timer %s", timer->name); - repeat = timer->repeat; + // G_SOURCE_REMOVE is false; G_SOURCE_CONTINUE is true if (!timer->cb(timer->userdata)) { pcmk__trace("Timer %s complete", timer->name); - repeat = false; + return G_SOURCE_REMOVE; } - if (repeat) { - /* Restore if repeating */ - timer->id = id; + if (!timer->repeat) { + return G_SOURCE_REMOVE; } - return repeat; + timer->id = id; + return G_SOURCE_CONTINUE; } bool From 2a47ae9ed00e4b5284c72039b154cf8422bdb3b8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 15:13:02 -0700 Subject: [PATCH 47/70] Refactor: various: Always pass repeat=true to mainloop_timer_add() The callers that passed false or FALSE for the repeat argument also passed a callback that always returned G_SOURCE_REMOVE (or equivalently, FALSE). mainloop_timer_t:repeat acts as an override switch for the callback's return value. If timer->repeat is true, mainloop_timer_cb() returns the return value from calling timer->cb. If timer->repeat is false, mainloop_timer_cb() acts as if timer->cb returned G_SOURCE_REMOVE. So for our internal callers of mainloop_timer_add(), the callbacks themselves give enough information via their return codes. We don't need to override them. Also replace TRUE with true in some other calls for consistency. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 2 +- daemons/attrd/attrd_sync.c | 3 ++- daemons/based/based_callbacks.c | 2 +- daemons/controld/controld_fencing.c | 16 +++++++--------- daemons/controld/controld_schedulerd.c | 6 +++--- daemons/controld/controld_throttle.c | 3 ++- daemons/fenced/fenced_commands.c | 2 +- daemons/pacemakerd/pcmkd_corosync.c | 3 ++- lib/cluster/election.c | 4 ++-- tools/crm_mon.c | 5 +++-- 10 files changed, 24 insertions(+), 22 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index e33d525fd41..2fb38e3d15b 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -492,7 +492,7 @@ set_alert_attribute_value(GHashTable *t, attribute_value_t *v) mainloop_timer_t * attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr) { - return mainloop_timer_add(id, timeout_ms, FALSE, attribute_timer_cb, attr); + return mainloop_timer_add(id, timeout_ms, true, attribute_timer_cb, attr); } /*! diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index f9c7f3f9bba..4b6c20f37dc 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -513,7 +513,8 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f action->ipc_id = request->ipc_id; action->flags = request->flags; - action->timer = mainloop_timer_add(NULL, 15000, FALSE, confirmation_timeout_cb, action); + action->timer = mainloop_timer_add(NULL, 15000, true, + confirmation_timeout_cb, action); mainloop_timer_start(action->timer); pcmk__intkey_table_insert(expected_confirmations, callid, action); diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index 71679d43275..25e5e1e5fd3 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -102,7 +102,7 @@ void based_callbacks_init(void) { if (digest_timer == NULL) { - digest_timer = mainloop_timer_add("based_digest_timer", 5000, false, + digest_timer = mainloop_timer_add("based_digest_timer", 5000, true, digest_timer_cb, NULL); } } diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index c3df6d5d2d4..f03c735d294 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -674,8 +674,8 @@ controld_timer_fencer_connect(void *user_data) if (controld_fencer_connect_timer == NULL) { controld_fencer_connect_timer = - mainloop_timer_add("controld_fencer_connect", 1000, - TRUE, controld_timer_fencer_connect, + mainloop_timer_add("controld_fencer_connect", 1000, true, + controld_timer_fencer_connect, GINT_TO_POINTER(TRUE)); } @@ -1042,7 +1042,7 @@ static gboolean fencing_history_sync_set_trigger(void *user_data) { mainloop_set_trigger(fencing_history_sync_trigger); - return FALSE; + return G_SOURCE_REMOVE; } void @@ -1074,9 +1074,8 @@ controld_trigger_fencing_history_sync(bool long_timeout) if (long_timeout) { if (fencing_history_sync_timer_long == NULL) { fencing_history_sync_timer_long = - mainloop_timer_add("history_sync_long", 30000, - FALSE, fencing_history_sync_set_trigger, - NULL); + mainloop_timer_add("history_sync_long", 30000, true, + fencing_history_sync_set_trigger, NULL); } pcmk__info("Fence history will be synchronized cluster-wide within 30 " "seconds"); @@ -1085,9 +1084,8 @@ controld_trigger_fencing_history_sync(bool long_timeout) } else { if (fencing_history_sync_timer_short == NULL) { fencing_history_sync_timer_short = - mainloop_timer_add("history_sync_short", 5000, - FALSE, fencing_history_sync_set_trigger, - NULL); + mainloop_timer_add("history_sync_short", 5000, true, + fencing_history_sync_set_trigger, NULL); } pcmk__info("Fence history will be synchronized cluster-wide within 5 " "seconds"); diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index 3f7d3301ba3..927142b3f8f 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -246,7 +246,7 @@ static mainloop_timer_t *controld_sched_timer = NULL; * * \param[in] user_data Ignored * - * \return FALSE (indicating that timer should not be restarted) + * \return \c G_SOURCE_REMOVE (indicating that timer should not be restarted) */ static gboolean controld_sched_timeout(void *user_data) @@ -292,7 +292,7 @@ controld_expect_sched_reply(char *ref) if (ref) { if (controld_sched_timer == NULL) { controld_sched_timer = mainloop_timer_add("scheduler_reply_timer", - SCHED_TIMEOUT_MS, FALSE, + SCHED_TIMEOUT_MS, true, controld_sched_timeout, NULL); } @@ -487,7 +487,7 @@ do_pe_invoke_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void pcmk__debug("Re-asking for the CIB: %d other peer updates still " "pending", (num_cib_op_callbacks() - 1)); - controld_cib_retry_timer = mainloop_timer_add("cib_retry", 1000, false, + controld_cib_retry_timer = mainloop_timer_add("cib_retry", 1000, true, sleep_timer, NULL); mainloop_timer_start(controld_cib_retry_timer); return; diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 05a67207009..344674c3bd3 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -262,7 +262,8 @@ throttle_init(void) { if(throttle_records == NULL) { throttle_records = pcmk__strkey_table(NULL, throttle_record_free); - throttle_timer = mainloop_timer_add("throttle", 30 * 1000, TRUE, throttle_timer_cb, NULL); + throttle_timer = mainloop_timer_add("throttle", (30 * 1000), true, + throttle_timer_cb, NULL); } throttle_update_job_max(NULL); diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 65415180f8c..82f40798228 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -1193,7 +1193,7 @@ build_device_from_xml(const xmlNode *dev) } else if (rc == EAGAIN) { if (device->timer == NULL) { device->timer = mainloop_timer_add("get_agent_metadata", 10 * 1000, - TRUE, get_agent_metadata_cb, + true, get_agent_metadata_cb, device); } diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index e085789d0cc..3301d8c3ac7 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -120,7 +120,8 @@ cfg_connection_destroy(void *user_data) "reattempted once per second)"); corosync_cfg_finalize(cfg_handle); cfg_handle = 0; - reconnect_timer = mainloop_timer_add("corosync reconnect", 1000, TRUE, cluster_reconnect_cb, NULL); + reconnect_timer = mainloop_timer_add("corosync reconnect", 1000, true, + cluster_reconnect_cb, NULL); mainloop_timer_start(reconnect_timer); } diff --git a/lib/cluster/election.c b/lib/cluster/election.c index db44bfdfccd..5f3f0ef6d92 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -53,7 +53,7 @@ election_timer_cb(void *user_data) pcmk__info("Declaring local node as winner after election timed out"); election_complete(cluster); - return FALSE; + return G_SOURCE_REMOVE; } /*! @@ -100,7 +100,7 @@ election_init(pcmk_cluster_t *cluster, void (*cb)(pcmk_cluster_t *)) cluster->priv->election->cb = cb; cluster->priv->election->timeout = mainloop_timer_add(name, ELECTION_TIMEOUT_MS, - FALSE, + true, election_timer_cb, cluster); } diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 3610870a9dd..9d817bdd2f3 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -1864,7 +1864,7 @@ static gboolean mon_trigger_refresh(void *user_data) { mainloop_set_trigger((crm_trigger_t *) refresh_trigger); - return FALSE; + return G_SOURCE_REMOVE; } static int @@ -2115,7 +2115,8 @@ refresh_after_event(gboolean data_updated, gboolean enforce) } if(refresh_timer == NULL) { - refresh_timer = mainloop_timer_add("refresh", 2000, FALSE, mon_trigger_refresh, NULL); + refresh_timer = mainloop_timer_add("refresh", 2000, true, + mon_trigger_refresh, NULL); } if (reconnect_timer > 0) { From cca1f567d34c828aa20b325f944711f7b8aa4dca Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Sat, 15 Aug 2026 02:33:56 -0700 Subject: [PATCH 48/70] Refactor: various: Use G_SOURCE_{CONTINUE,REMOVE} in GSourceFuncs Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 2 +- daemons/controld/controld_remote_ra.c | 16 +++++++++------- daemons/controld/controld_schedulerd.c | 3 ++- daemons/controld/controld_te_callbacks.c | 6 +++--- daemons/controld/controld_te_utils.c | 9 +++++---- daemons/controld/controld_throttle.c | 2 +- daemons/controld/controld_timers.c | 2 +- daemons/execd/cts-exec-helper.c | 2 +- daemons/execd/execd_commands.c | 6 +++--- daemons/execd/remoted_tls.c | 4 ++-- daemons/fenced/fenced_commands.c | 2 +- include/crm/common/mainloop_internal.h | 2 +- lib/cluster/cpg.c | 7 +++++-- lib/common/ipc_server.c | 2 +- lib/common/mainloop.c | 2 +- lib/common/remote.c | 10 ++++++---- lib/fencing/st_client.c | 6 +++--- lib/services/dbus.c | 2 +- lib/services/services_linux.c | 2 +- lib/services/systemd.c | 2 +- tools/crm_resource.c | 2 +- 21 files changed, 50 insertions(+), 41 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 2fb38e3d15b..640313cff24 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -240,7 +240,7 @@ attribute_timer_cb(void *data) attribute_t *a = data; pcmk__trace("Dampen interval expired for %s", a->id); attrd_write_or_elect_attribute(a); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/daemons/controld/controld_remote_ra.c b/daemons/controld/controld_remote_ra.c index 1c2f0a89106..36d071dbdad 100644 --- a/daemons/controld/controld_remote_ra.c +++ b/daemons/controld/controld_remote_ra.c @@ -139,7 +139,8 @@ recurring_helper(void *data) ra_data->cmds = g_list_append(ra_data->cmds, cmd); mainloop_set_trigger(ra_data->work); } - return FALSE; + + return G_SOURCE_REMOVE; } static gboolean @@ -155,7 +156,8 @@ start_delay_helper(void *data) mainloop_set_trigger(ra_data->work); } - return FALSE; + + return G_SOURCE_REMOVE; } static bool @@ -471,11 +473,11 @@ retry_start_cmd_cb(void *data) int remaining = 0; if (!ra_data || !ra_data->cur_cmd) { - return FALSE; + return G_SOURCE_REMOVE; } cmd = ra_data->cur_cmd; if (!pcmk__is_up_action(cmd->action)) { - return FALSE; + return G_SOURCE_REMOVE; } remaining = remaining_timeout_sec(cmd); @@ -499,7 +501,7 @@ retry_start_cmd_cb(void *data) /* wait for connection event */ } - return FALSE; + return G_SOURCE_REMOVE; } @@ -517,7 +519,7 @@ connection_takeover_timeout_cb(void *data) handle_remote_ra_stop(lrm_state, cmd); free_cmd(cmd); - return FALSE; + return G_SOURCE_REMOVE; } static gboolean @@ -552,7 +554,7 @@ monitor_timeout_cb(void *data) // @TODO Should we move this before reporting the result above? lrm_state_disconnect(lrm_state); } - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index 927142b3f8f..c70c7aea67e 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -264,7 +264,8 @@ controld_sched_timeout(void *user_data) */ crmd_exit(CRM_EX_FATAL); } - return FALSE; + + return G_SOURCE_REMOVE; } void diff --git a/daemons/controld/controld_te_callbacks.c b/daemons/controld/controld_te_callbacks.c index 3be6c120ce7..217183f6d68 100644 --- a/daemons/controld/controld_te_callbacks.c +++ b/daemons/controld/controld_te_callbacks.c @@ -480,7 +480,7 @@ cib_action_updated(xmlNode * msg, int call_id, int rc, xmlNode * output, void *u * * \param[in,out] data Pointer to graph action * - * \return FALSE (indicating that source should be not be re-added) + * \return \c G_SOURCE_REMOVE (indicating that source should be not be re-added) */ gboolean action_timer_callback(void *data) @@ -490,7 +490,7 @@ action_timer_callback(void *data) const char *on_node = NULL; const char *via_node = NULL; - CRM_CHECK(data != NULL, return FALSE); + CRM_CHECK(data != NULL, return G_SOURCE_REMOVE); stop_te_timer(action); @@ -527,5 +527,5 @@ action_timer_callback(void *data) } } - return FALSE; + return G_SOURCE_REMOVE; } diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c index 1e04bde99fa..d842ec6dc6e 100644 --- a/daemons/controld/controld_te_utils.c +++ b/daemons/controld/controld_te_utils.c @@ -139,8 +139,9 @@ abort_timer_popped(void *data) abort_transition(abort_timer->priority, abort_timer->action, abort_timer->text, NULL); } + abort_timer->id = 0; - return FALSE; // do not immediately reschedule timer + return G_SOURCE_REMOVE; // do not immediately reschedule timer } /*! @@ -183,12 +184,12 @@ node_pending_timer_popped(void *key) struct abort_timer_s *node_pending_timer = NULL; if (node_pending_timers == NULL) { - return FALSE; + return G_SOURCE_REMOVE; } node_pending_timer = g_hash_table_lookup(node_pending_timers, key); if (node_pending_timer == NULL) { - return FALSE; + return G_SOURCE_REMOVE; } pcmk__warn("Node with " PCMK_XA_ID " '%s' pending timed out (%us) on " @@ -201,7 +202,7 @@ node_pending_timer_popped(void *key) g_hash_table_remove(node_pending_timers, key); - return FALSE; // do not reschedule timer + return G_SOURCE_REMOVE; // do not reschedule timer } static void diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 344674c3bd3..788a8322dc1 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -202,7 +202,7 @@ static gboolean throttle_timer_cb(void *data) { throttle_send_command(throttle_mode()); - return TRUE; + return G_SOURCE_CONTINUE; } static void diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 90138ee716c..f853a35da32 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -248,7 +248,7 @@ crm_timer_popped(void *data) controld_trigger_fsa(); - return TRUE; + return G_SOURCE_CONTINUE; } bool diff --git a/daemons/execd/cts-exec-helper.c b/daemons/execd/cts-exec-helper.c index 8508da6ef51..d5a46fb257d 100644 --- a/daemons/execd/cts-exec-helper.c +++ b/daemons/execd/cts-exec-helper.c @@ -223,7 +223,7 @@ timeout_err(void *data) { print_result("LISTEN EVENT FAILURE - timeout occurred, never found"); test_exit(CRM_EX_TIMEOUT); - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/daemons/execd/execd_commands.c b/daemons/execd/execd_commands.c index 89d77d6ec08..67e8ab4057d 100644 --- a/daemons/execd/execd_commands.c +++ b/daemons/execd/execd_commands.c @@ -388,7 +388,7 @@ stonith_recurring_op_helper(void *data) cmd->stonith_recurring_id = 0; if (!cmd->rsc_id) { - return FALSE; + return G_SOURCE_REMOVE; } rsc = g_hash_table_lookup(rsc_list, cmd->rsc_id); @@ -403,7 +403,7 @@ stonith_recurring_op_helper(void *data) #endif mainloop_set_trigger(rsc->work); - return FALSE; + return G_SOURCE_REMOVE; } static inline void @@ -431,7 +431,7 @@ start_delay_helper(void *data) mainloop_set_trigger(rsc->work); } - return FALSE; + return G_SOURCE_REMOVE; } /*! diff --git a/daemons/execd/remoted_tls.c b/daemons/execd/remoted_tls.c index af28efc657c..98bd27cebb1 100644 --- a/daemons/execd/remoted_tls.c +++ b/daemons/execd/remoted_tls.c @@ -195,13 +195,13 @@ lrmd_auth_timeout_cb(void *data) client->remote->auth_timeout = 0; if (pcmk__is_set(client->flags, pcmk__client_tls_handshake_complete)) { - return FALSE; + return G_SOURCE_REMOVE; } g_clear_pointer(&client->remote->source, mainloop_del_fd); pcmk__err("Remote client authentication timed out"); - return FALSE; + return G_SOURCE_REMOVE; } // Dispatch callback for remote server socket diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 82f40798228..ee2cc89a03c 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -809,7 +809,7 @@ start_delay_helper(void *data) mainloop_set_trigger(device->work); } - return FALSE; + return G_SOURCE_REMOVE; } static void diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index b1922c67e62..c3d6b7e5d34 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -63,7 +63,7 @@ struct mainloop_child_s { struct mainloop_timer_s { unsigned int id; unsigned int period_ms; - bool repeat; + gboolean repeat; char *name; GSourceFunc cb; void *userdata; diff --git a/lib/cluster/cpg.c b/lib/cluster/cpg.c index ad314112ac6..58ae8978510 100644 --- a/lib/cluster/cpg.c +++ b/lib/cluster/cpg.c @@ -181,14 +181,17 @@ pcmk__cpg_local_nodeid(cpg_handle_t handle) * * \param[in] data CPG handle * - * \return FALSE (to indicate to glib that timer should not be removed) + * \return \c G_SOURCE_REMOVE (to indicate to glib that timer should not be + * removed) + * + * \note Return value description seems inverted */ static gboolean crm_cs_flush_cb(void *data) { cs_message_timer = 0; crm_cs_flush(data); - return FALSE; + return G_SOURCE_REMOVE; } // Send no more than this many CPG messages in one flush diff --git a/lib/common/ipc_server.c b/lib/common/ipc_server.c index ec92392918f..d3796edade3 100644 --- a/lib/common/ipc_server.c +++ b/lib/common/ipc_server.c @@ -482,7 +482,7 @@ crm_ipcs_flush_events_cb(void *data) c->event_timer = 0; crm_ipcs_flush_events(c); - return FALSE; + return G_SOURCE_REMOVE; } /*! diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d3ec75d43a5..5fad98a7234 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1482,7 +1482,7 @@ drain_timeout_cb(void *user_data) bool *timeout_popped = (bool*) user_data; *timeout_popped = TRUE; - return FALSE; + return G_SOURCE_REMOVE; } /*! diff --git a/lib/common/remote.c b/lib/common/remote.c index 1846b8fc775..c6af9c9f9ba 100644 --- a/lib/common/remote.c +++ b/lib/common/remote.c @@ -785,7 +785,9 @@ struct tcp_async_cb_data { void (*callback) (void *userdata, int rc, int sock); }; -// \return TRUE if timer should be rescheduled, FALSE otherwise +/* \return G_SOURCE_CONTINUE if timer should be rescheduled, G_SOURCE_REMOVED + * otherwise + */ static gboolean check_connect_finished(void *userdata) { @@ -811,7 +813,7 @@ check_connect_finished(void *userdata) rc = errno; if ((rc == EINTR) || (rc == EAGAIN)) { if ((time(NULL) - cb_data->start) < pcmk__timeout_ms2s(cb_data->timeout_ms)) { - return TRUE; // There is time left, so reschedule timer + return G_SOURCE_CONTINUE; // There is time left, so reschedule timer } else { rc = ETIMEDOUT; } @@ -821,7 +823,7 @@ check_connect_finished(void *userdata) } else if (rc == 0) { // select() timeout if ((time(NULL) - cb_data->start) < pcmk__timeout_ms2s(cb_data->timeout_ms)) { - return TRUE; // There is time left, so reschedule timer + return G_SOURCE_CONTINUE; // There is time left, so reschedule timer } pcmk__debug("Timed out while waiting for socket %d connection success", cb_data->sock); @@ -868,7 +870,7 @@ check_connect_finished(void *userdata) cb_data->callback(cb_data->userdata, rc, cb_data->sock); } free(cb_data); - return FALSE; // Do not reschedule timer + return G_SOURCE_REMOVE; // Do not reschedule timer } /*! diff --git a/lib/fencing/st_client.c b/lib/fencing/st_client.c index 691e353c16a..fae37900e64 100644 --- a/lib/fencing/st_client.c +++ b/lib/fencing/st_client.c @@ -1022,10 +1022,10 @@ stonith_async_timeout_handler(void *data) timer->timeout); invoke_registered_callbacks(timer->stonith, NULL, timer->call_id); - /* Always return TRUE, never remove the handler - * We do that in stonith_del_callback() + /* Always return G_SOURCE_CONTINUE, never remove the handler. + * We do that in stonith_del_callback(). */ - return TRUE; + return G_SOURCE_CONTINUE; } static void diff --git a/lib/services/dbus.c b/lib/services/dbus.c index a21da13e599..99d961d79a7 100644 --- a/lib/services/dbus.c +++ b/lib/services/dbus.c @@ -205,7 +205,7 @@ timer_popped(void *data) pcmk__debug("%dms DBus timer expired", dbus_timeout_get_interval((DBusTimeout *) data)); dbus_timeout_handle(data); - return FALSE; + return G_SOURCE_REMOVE; } static dbus_bool_t diff --git a/lib/services/services_linux.c b/lib/services/services_linux.c index 5c491cecdab..80fd416e1b5 100644 --- a/lib/services/services_linux.c +++ b/lib/services/services_linux.c @@ -560,7 +560,7 @@ recurring_action_timer(void *data) op->opaque->repeat_timer = 0; services_action_async(op, NULL); - return FALSE; + return G_SOURCE_REMOVE; } /*! diff --git a/lib/services/systemd.c b/lib/services/systemd.c index 8f20d08d4b9..93ffe306579 100644 --- a/lib/services/systemd.c +++ b/lib/services/systemd.c @@ -1363,7 +1363,7 @@ systemd_timeout_callback(void *p) } else { services__finalize_async_op(op); } - return FALSE; + return G_SOURCE_REMOVE; } /*! diff --git a/tools/crm_resource.c b/tools/crm_resource.c index 8c2d165b19d..e4f08f36f6d 100644 --- a/tools/crm_resource.c +++ b/tools/crm_resource.c @@ -212,7 +212,7 @@ resource_ipc_timeout(void *data) _("Aborting because no messages received in %d seconds"), MESSAGE_TIMEOUT_S); quit_main_loop(CRM_EX_TIMEOUT); - return FALSE; + return G_SOURCE_REMOVE; } static void From b0200f180f777ff98ac4f5f22e1711d84f7201ab Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 15:32:35 -0700 Subject: [PATCH 49/70] Refactor: controller: Drop fsa_timer_t:callback It's always set to crm_timer_popped() and it just made the pcmk__create_timer() call a little bit harder to trace. Move crm_timer_popped() just so that we can use it in the pcmk__create_timer() call. Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 139 ++++++++++++++--------------- 1 file changed, 66 insertions(+), 73 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index f853a35da32..32742bc202d 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -20,12 +20,11 @@ //! FSA mainloop timer type typedef struct { - unsigned int source_id; //!< Timer source ID - unsigned int period_ms; //!< Timer period - enum crmd_fsa_input fsa_input; //!< Input to register if timer pops - gboolean (*callback)(void *data); //!< What do if timer pops - bool log_error; //!< Timer popping indicates error - int counter; //!< For detecting loops + unsigned int source_id; //!< Timer source ID + unsigned int period_ms; //!< Timer period + enum crmd_fsa_input fsa_input; //!< Input to register if timer pops + bool log_error; //!< Timer popping indicates error + int counter; //!< For detecting loops } fsa_timer_t; //! Wait before retrying a failed cib or executor connection @@ -109,6 +108,65 @@ controld_stop_timer(fsa_timer_t *timer) return false; } +static gboolean +crm_timer_popped(void *data) +{ + fsa_timer_t *timer = (fsa_timer_t *) data; + + if (timer->log_error) { + pcmk__err("%s just popped in state %s! " QB_XS " input=%s time=%ums", + get_timer_desc(timer), + fsa_state2string(controld_globals.fsa_state), + fsa_input2string(timer->fsa_input), timer->period_ms); + } else { + pcmk__info("%s just popped " QB_XS " input=%s time=%ums", + get_timer_desc(timer), fsa_input2string(timer->fsa_input), + timer->period_ms); + timer->counter++; + } + + if ((timer == election_timer) && (election_timer->counter > 5)) { + pcmk__notice("We appear to be in an election loop, something may be " + "wrong"); + crm_write_blackbox(0, NULL); + election_timer->counter = 0; + } + + controld_stop_timer(timer); // Make timer _not_ go off again + + if (timer->fsa_input == I_INTEGRATED) { + pcmk__info("Welcomed: %d, Integrated: %d", + crmd_join_phase_count(controld_join_welcomed), + crmd_join_phase_count(controld_join_integrated)); + if (crmd_join_phase_count(controld_join_welcomed) == 0) { + // If we don't even have ourselves, start again + register_fsa_error(I_ELECTION, NULL); + + } else { + controld_fsa_prepend(C_TIMER_POPPED, timer->fsa_input, NULL); + } + + } else if ((timer == recheck_timer) + && (controld_globals.fsa_state != S_IDLE)) { + pcmk__debug("Discarding %s event in state: %s", + fsa_input2string(timer->fsa_input), + fsa_state2string(controld_globals.fsa_state)); + + } else if ((timer == finalization_timer) + && (controld_globals.fsa_state != S_FINALIZE_JOIN)) { + pcmk__debug("Discarding %s event in state: %s", + fsa_input2string(timer->fsa_input), + fsa_state2string(controld_globals.fsa_state)); + + } else if (timer->fsa_input != I_NULL) { + controld_fsa_append(C_TIMER_POPPED, timer->fsa_input, NULL); + } + + controld_trigger_fsa(); + + return G_SOURCE_CONTINUE; +} + /*! * \internal * \brief Start an FSA timer @@ -119,7 +177,8 @@ static void controld_start_timer(fsa_timer_t *timer) { if (timer->source_id == 0 && timer->period_ms > 0) { - timer->source_id = pcmk__create_timer(timer->period_ms, timer->callback, timer); + timer->source_id = pcmk__create_timer(timer->period_ms, + crm_timer_popped, timer); pcmk__assert(timer->source_id != 0); pcmk__debug("Started %s (inject %s if pops after %ums, source=%d)", get_timer_desc(timer), fsa_input2string(timer->fsa_input), @@ -192,65 +251,6 @@ do_timer_control(long long action, enum crmd_fsa_cause cause, } } -static gboolean -crm_timer_popped(void *data) -{ - fsa_timer_t *timer = (fsa_timer_t *) data; - - if (timer->log_error) { - pcmk__err("%s just popped in state %s! " QB_XS " input=%s time=%ums", - get_timer_desc(timer), - fsa_state2string(controld_globals.fsa_state), - fsa_input2string(timer->fsa_input), timer->period_ms); - } else { - pcmk__info("%s just popped " QB_XS " input=%s time=%ums", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), - timer->period_ms); - timer->counter++; - } - - if ((timer == election_timer) && (election_timer->counter > 5)) { - pcmk__notice("We appear to be in an election loop, something may be " - "wrong"); - crm_write_blackbox(0, NULL); - election_timer->counter = 0; - } - - controld_stop_timer(timer); // Make timer _not_ go off again - - if (timer->fsa_input == I_INTEGRATED) { - pcmk__info("Welcomed: %d, Integrated: %d", - crmd_join_phase_count(controld_join_welcomed), - crmd_join_phase_count(controld_join_integrated)); - if (crmd_join_phase_count(controld_join_welcomed) == 0) { - // If we don't even have ourselves, start again - register_fsa_error(I_ELECTION, NULL); - - } else { - controld_fsa_prepend(C_TIMER_POPPED, timer->fsa_input, NULL); - } - - } else if ((timer == recheck_timer) - && (controld_globals.fsa_state != S_IDLE)) { - pcmk__debug("Discarding %s event in state: %s", - fsa_input2string(timer->fsa_input), - fsa_state2string(controld_globals.fsa_state)); - - } else if ((timer == finalization_timer) - && (controld_globals.fsa_state != S_FINALIZE_JOIN)) { - pcmk__debug("Discarding %s event in state: %s", - fsa_input2string(timer->fsa_input), - fsa_state2string(controld_globals.fsa_state)); - - } else if (timer->fsa_input != I_NULL) { - controld_fsa_append(C_TIMER_POPPED, timer->fsa_input, NULL); - } - - controld_trigger_fsa(); - - return G_SOURCE_CONTINUE; -} - bool controld_init_fsa_timers(void) { @@ -265,25 +265,21 @@ controld_init_fsa_timers(void) election_timer->source_id = 0; election_timer->period_ms = 0; election_timer->fsa_input = I_DC_TIMEOUT; - election_timer->callback = crm_timer_popped; election_timer->log_error = FALSE; transition_timer->source_id = 0; transition_timer->period_ms = 0; transition_timer->fsa_input = I_PE_CALC; - transition_timer->callback = crm_timer_popped; transition_timer->log_error = FALSE; integration_timer->source_id = 0; integration_timer->period_ms = 0; integration_timer->fsa_input = I_INTEGRATED; - integration_timer->callback = crm_timer_popped; integration_timer->log_error = TRUE; finalization_timer->source_id = 0; finalization_timer->period_ms = 0; finalization_timer->fsa_input = I_FINALIZED; - finalization_timer->callback = crm_timer_popped; finalization_timer->log_error = FALSE; /* We can't use I_FINALIZED here, because that creates a bug in the join @@ -300,19 +296,16 @@ controld_init_fsa_timers(void) shutdown_escalation_timer->source_id = 0; shutdown_escalation_timer->period_ms = 0; shutdown_escalation_timer->fsa_input = I_STOP; - shutdown_escalation_timer->callback = crm_timer_popped; shutdown_escalation_timer->log_error = TRUE; wait_timer->source_id = 0; wait_timer->period_ms = 2000; wait_timer->fsa_input = I_NULL; - wait_timer->callback = crm_timer_popped; wait_timer->log_error = FALSE; recheck_timer->source_id = 0; recheck_timer->period_ms = 0; recheck_timer->fsa_input = I_PE_CALC; - recheck_timer->callback = crm_timer_popped; recheck_timer->log_error = FALSE; return TRUE; From abcdf2ea480e2ea1e7db56ea3170c574382976e1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 15:46:25 -0700 Subject: [PATCH 50/70] Refactor: controller: Drop redundant inits in controld_init_fsa_timers() We call pcmk__assert_alloc() to allocate these timers. That sets all the fields to zero. So drop assignments that set fields to 0 or FALSE. Keep the assignment of I_NULL to wait_timer->fsa_input, just because it's not as obvious that that value is equivalent to zero. Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 22 ++-------------------- 1 file changed, 2 insertions(+), 20 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 32742bc202d..7abe4c3429d 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -262,25 +262,14 @@ controld_init_fsa_timers(void) wait_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); recheck_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - election_timer->source_id = 0; - election_timer->period_ms = 0; election_timer->fsa_input = I_DC_TIMEOUT; - election_timer->log_error = FALSE; - transition_timer->source_id = 0; - transition_timer->period_ms = 0; transition_timer->fsa_input = I_PE_CALC; - transition_timer->log_error = FALSE; - integration_timer->source_id = 0; - integration_timer->period_ms = 0; integration_timer->fsa_input = I_INTEGRATED; - integration_timer->log_error = TRUE; + integration_timer->log_error = true; - finalization_timer->source_id = 0; - finalization_timer->period_ms = 0; finalization_timer->fsa_input = I_FINALIZED; - finalization_timer->log_error = FALSE; /* We can't use I_FINALIZED here, because that creates a bug in the join * process where a joining node can be stuck in S_PENDING while we think it @@ -293,20 +282,13 @@ controld_init_fsa_timers(void) */ finalization_timer->fsa_input = I_ELECTION; - shutdown_escalation_timer->source_id = 0; - shutdown_escalation_timer->period_ms = 0; shutdown_escalation_timer->fsa_input = I_STOP; - shutdown_escalation_timer->log_error = TRUE; + shutdown_escalation_timer->log_error = true; - wait_timer->source_id = 0; wait_timer->period_ms = 2000; wait_timer->fsa_input = I_NULL; - wait_timer->log_error = FALSE; - recheck_timer->source_id = 0; - recheck_timer->period_ms = 0; recheck_timer->fsa_input = I_PE_CALC; - recheck_timer->log_error = FALSE; return TRUE; } From 70dfac4f6982066ff603c91f618842e2ae1d38c7 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 15:48:35 -0700 Subject: [PATCH 51/70] Refactor: controller: controld_init_fsa_timers() returns void It always returned true previously. Signed-off-by: Reid Wahl --- daemons/controld/controld_control.c | 4 +--- daemons/controld/controld_timers.c | 4 +--- daemons/controld/controld_timers.h | 2 +- 3 files changed, 3 insertions(+), 7 deletions(-) diff --git a/daemons/controld/controld_control.c b/daemons/controld/controld_control.c index 5d876436276..1dcc4fe8323 100644 --- a/daemons/controld/controld_control.c +++ b/daemons/controld/controld_control.c @@ -295,9 +295,7 @@ do_startup(long long action, enum crmd_fsa_cause cause, controld_execd_state_table_init(); controld_remote_proxy_table_init(); - if (!controld_init_fsa_timers()) { - register_fsa_error(I_ERROR, msg_data); - } + controld_init_fsa_timers(); } // \return libqb error code (0 on success, -errno on error) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 7abe4c3429d..97d952a37f8 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -251,7 +251,7 @@ do_timer_control(long long action, enum crmd_fsa_cause cause, } } -bool +void controld_init_fsa_timers(void) { transition_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); @@ -289,8 +289,6 @@ controld_init_fsa_timers(void) wait_timer->fsa_input = I_NULL; recheck_timer->fsa_input = I_PE_CALC; - - return TRUE; } /*! diff --git a/daemons/controld/controld_timers.h b/daemons/controld/controld_timers.h index 0baf5311ee4..19412861d99 100644 --- a/daemons/controld/controld_timers.h +++ b/daemons/controld/controld_timers.h @@ -14,7 +14,7 @@ # include // GHashTable # include // crmd_fsa_input -bool controld_init_fsa_timers(void); +void controld_init_fsa_timers(void); void controld_free_fsa_timers(void); void controld_configure_fsa_timers(GHashTable *options); From 2c186de86543ab88588f18c7f0456d0e367a8be3 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 16:02:22 -0700 Subject: [PATCH 52/70] Refactor: controller: Drop overridden set of finalization timer input The override from I_FINALIZED to I_ELECTION has been present for over 20 years (since commit b6f3e494). There's no reason to do the initial set to I_FINALIZED. Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 2 -- 1 file changed, 2 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 97d952a37f8..203a135a468 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -269,8 +269,6 @@ controld_init_fsa_timers(void) integration_timer->fsa_input = I_INTEGRATED; integration_timer->log_error = true; - finalization_timer->fsa_input = I_FINALIZED; - /* We can't use I_FINALIZED here, because that creates a bug in the join * process where a joining node can be stuck in S_PENDING while we think it * is in S_NOT_DC. This created an infinite transition loop in which we From de2b2b6fd36c3b7219b90a559b93b063afebd1e0 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 16:08:11 -0700 Subject: [PATCH 53/70] Refactor: controller: Drop get_timer_desc() Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 57 +++++++++--------------------- 1 file changed, 17 insertions(+), 40 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 203a135a468..d281bc38fe9 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -20,6 +20,7 @@ //! FSA mainloop timer type typedef struct { + const char *desc; //!< Description unsigned int source_id; //!< Timer source ID unsigned int period_ms; //!< Timer period enum crmd_fsa_input fsa_input; //!< Input to register if timer pops @@ -51,34 +52,6 @@ fsa_timer_t *shutdown_escalation_timer = NULL; //! Cluster recheck interval (from configuration) static unsigned int recheck_interval_ms = 0; -static const char * -get_timer_desc(fsa_timer_t * timer) -{ - if (timer == election_timer) { - return "Election Trigger"; - - } else if (timer == shutdown_escalation_timer) { - return "Shutdown Escalation"; - - } else if (timer == integration_timer) { - return "Integration Timer"; - - } else if (timer == finalization_timer) { - return "Finalization Timer"; - - } else if (timer == transition_timer) { - return "New Transition Timer"; - - } else if (timer == wait_timer) { - return "Wait Timer"; - - } else if (timer == recheck_timer) { - return "Cluster Recheck Timer"; - - } - return "Unknown Timer"; -} - /*! * \internal * \brief Stop an FSA timer @@ -94,8 +67,7 @@ controld_stop_timer(fsa_timer_t *timer) if (timer->source_id != 0) { pcmk__trace("Stopping %s (would inject %s if popped after %ums, " - "src=%d)", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), + "src=%d)", timer->desc, fsa_input2string(timer->fsa_input), timer->period_ms, timer->source_id); g_source_remove(timer->source_id); timer->source_id = 0; @@ -103,7 +75,7 @@ controld_stop_timer(fsa_timer_t *timer) } pcmk__trace("%s already stopped (would inject %s if popped after %ums)", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), + timer->desc, fsa_input2string(timer->fsa_input), timer->period_ms); return false; } @@ -115,13 +87,11 @@ crm_timer_popped(void *data) if (timer->log_error) { pcmk__err("%s just popped in state %s! " QB_XS " input=%s time=%ums", - get_timer_desc(timer), - fsa_state2string(controld_globals.fsa_state), + timer->desc, fsa_state2string(controld_globals.fsa_state), fsa_input2string(timer->fsa_input), timer->period_ms); } else { - pcmk__info("%s just popped " QB_XS " input=%s time=%ums", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), - timer->period_ms); + pcmk__info("%s just popped " QB_XS " input=%s time=%ums", timer->desc, + fsa_input2string(timer->fsa_input), timer->period_ms); timer->counter++; } @@ -181,13 +151,13 @@ controld_start_timer(fsa_timer_t *timer) crm_timer_popped, timer); pcmk__assert(timer->source_id != 0); pcmk__debug("Started %s (inject %s if pops after %ums, source=%d)", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), + timer->desc, fsa_input2string(timer->fsa_input), timer->period_ms, timer->source_id); } else { pcmk__debug("%s already running (inject %s if pops after %ums, " - "source=%d)", - get_timer_desc(timer), fsa_input2string(timer->fsa_input), - timer->period_ms, timer->source_id); + "source=%d)", timer->desc, + fsa_input2string(timer->fsa_input), timer->period_ms, + timer->source_id); } } @@ -262,10 +232,13 @@ controld_init_fsa_timers(void) wait_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); recheck_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); + election_timer->desc = "Election Trigger"; election_timer->fsa_input = I_DC_TIMEOUT; + transition_timer->desc = "New Transition Timer"; transition_timer->fsa_input = I_PE_CALC; + integration_timer->desc = "Integration Timer"; integration_timer->fsa_input = I_INTEGRATED; integration_timer->log_error = true; @@ -278,14 +251,18 @@ controld_init_fsa_timers(void) * not, we can avoid this causing an election/join loop, in the integration * phase. */ + finalization_timer->desc = "Finalization Timer"; finalization_timer->fsa_input = I_ELECTION; + shutdown_escalation_timer->desc = "Shutdown Escalation"; shutdown_escalation_timer->fsa_input = I_STOP; shutdown_escalation_timer->log_error = true; + wait_timer->desc = "Wait Timer"; wait_timer->period_ms = 2000; wait_timer->fsa_input = I_NULL; + recheck_timer->desc = "Cluster Recheck Timer"; recheck_timer->fsa_input = I_PE_CALC; } From 8fd6ebe177ca6d43ae37136c6387865af79f8f50 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 16:19:35 -0700 Subject: [PATCH 54/70] Refactor: controller: New new_fsa_timer() Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 68 ++++++++++++++++++------------ 1 file changed, 42 insertions(+), 26 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index d281bc38fe9..a8b9c326f3e 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -221,26 +221,40 @@ do_timer_control(long long action, enum crmd_fsa_cause cause, } } -void -controld_init_fsa_timers(void) +/*! + * \internal + * \brief Create an FSA timer + * + * \param[in] desc Description + * \param[in] interval_ms Timer interval in milliseconds + * \param[in] fsa_input FSA input to register if the timer pops + * \param[in] log_error If \c true, log an error if the timer pops + * + * \return Newly allocated FSA timer (guaranteed not to be \c NULL) + * + * \note The caller is responsible for freeing the return value using + * \c controld_stop_timer() and \c free(). + * + * \todo Functionize stopping and freeing an \c fsa_timer_t. + */ +static fsa_timer_t * +new_fsa_timer(const char *desc, unsigned int interval_ms, + enum crmd_fsa_input fsa_input, bool log_error) { - transition_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - integration_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - finalization_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - election_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - shutdown_escalation_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - wait_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - recheck_timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); + fsa_timer_t *timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); - election_timer->desc = "Election Trigger"; - election_timer->fsa_input = I_DC_TIMEOUT; + timer->desc = desc; + timer->period_ms = interval_ms; + timer->fsa_input = fsa_input; + timer->log_error = log_error; - transition_timer->desc = "New Transition Timer"; - transition_timer->fsa_input = I_PE_CALC; + return timer; +} - integration_timer->desc = "Integration Timer"; - integration_timer->fsa_input = I_INTEGRATED; - integration_timer->log_error = true; +void +controld_init_fsa_timers(void) +{ + election_timer = new_fsa_timer("Election Trigger", 0, I_DC_TIMEOUT, false); /* We can't use I_FINALIZED here, because that creates a bug in the join * process where a joining node can be stuck in S_PENDING while we think it @@ -251,19 +265,21 @@ controld_init_fsa_timers(void) * not, we can avoid this causing an election/join loop, in the integration * phase. */ - finalization_timer->desc = "Finalization Timer"; - finalization_timer->fsa_input = I_ELECTION; + finalization_timer = new_fsa_timer("Finalization Timer", 0, I_ELECTION, + false); + + integration_timer = new_fsa_timer("Integration Timer", 0, I_INTEGRATED, + true); + + recheck_timer = new_fsa_timer("Cluster Recheck Timer", 0, I_PE_CALC, false); - shutdown_escalation_timer->desc = "Shutdown Escalation"; - shutdown_escalation_timer->fsa_input = I_STOP; - shutdown_escalation_timer->log_error = true; + shutdown_escalation_timer = new_fsa_timer("Shutdown Escalation", 0, I_STOP, + true); - wait_timer->desc = "Wait Timer"; - wait_timer->period_ms = 2000; - wait_timer->fsa_input = I_NULL; + transition_timer = new_fsa_timer("New Transition Timer", 0, I_PE_CALC, + false); - recheck_timer->desc = "Cluster Recheck Timer"; - recheck_timer->fsa_input = I_PE_CALC; + wait_timer = new_fsa_timer("Wait Timer", 2000, I_NULL, false); } /*! From 8876cdcc616c67437e6261d4981575fbfecda5f3 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 16:25:42 -0700 Subject: [PATCH 55/70] Refactor: controller: New free_fsa_timer() Signed-off-by: Reid Wahl --- daemons/controld/controld_timers.c | 43 +++++++++++++++++------------- 1 file changed, 25 insertions(+), 18 deletions(-) diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index a8b9c326f3e..0431bba3870 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -233,9 +233,7 @@ do_timer_control(long long action, enum crmd_fsa_cause cause, * \return Newly allocated FSA timer (guaranteed not to be \c NULL) * * \note The caller is responsible for freeing the return value using - * \c controld_stop_timer() and \c free(). - * - * \todo Functionize stopping and freeing an \c fsa_timer_t. + * \c free_fsa_timer(). */ static fsa_timer_t * new_fsa_timer(const char *desc, unsigned int interval_ms, @@ -251,6 +249,23 @@ new_fsa_timer(const char *desc, unsigned int interval_ms, return timer; } +/*! + * \internal + * \return Free an FSA timer + * + * \param[in,out] timer FSA timer + */ +static void +free_fsa_timer(fsa_timer_t *timer) +{ + if (timer == NULL) { + return; + } + + controld_stop_timer(timer); + free(timer); +} + void controld_init_fsa_timers(void) { @@ -326,21 +341,13 @@ controld_configure_fsa_timers(GHashTable *options) void controld_free_fsa_timers(void) { - controld_stop_timer(transition_timer); - controld_stop_timer(integration_timer); - controld_stop_timer(finalization_timer); - controld_stop_timer(election_timer); - controld_stop_timer(shutdown_escalation_timer); - controld_stop_timer(wait_timer); - controld_stop_timer(recheck_timer); - - g_clear_pointer(&transition_timer, free); - g_clear_pointer(&integration_timer, free); - g_clear_pointer(&finalization_timer, free); - g_clear_pointer(&election_timer, free); - g_clear_pointer(&shutdown_escalation_timer, free); - g_clear_pointer(&wait_timer, free); - g_clear_pointer(&recheck_timer, free); + g_clear_pointer(&election_timer, free_fsa_timer); + g_clear_pointer(&finalization_timer, free_fsa_timer); + g_clear_pointer(&integration_timer, free_fsa_timer); + g_clear_pointer(&recheck_timer, free_fsa_timer); + g_clear_pointer(&shutdown_escalation_timer, free_fsa_timer); + g_clear_pointer(&transition_timer, free_fsa_timer); + g_clear_pointer(&wait_timer, free_fsa_timer); } /*! From 245d5d3497bcbc5d85fc2891b9e06b2d141dcdba Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 23:12:49 -0700 Subject: [PATCH 56/70] Refactor: attrd, pacemakerd: Rename two main loop timers Name the one in attrd explicitly, and remove whitespace from the one in pacemakerd. This will allow us to assert name is not NULL in an internal constructor for a main loop timer. Also assert that name is not NULL in attrd_create_attribute(). This is currently enforced by attrd_peer_update_one(). Signed-off-by: Reid Wahl --- daemons/attrd/attrd_attributes.c | 2 ++ daemons/attrd/attrd_sync.c | 2 +- daemons/pacemakerd/pcmkd_corosync.c | 2 +- 3 files changed, 4 insertions(+), 2 deletions(-) diff --git a/daemons/attrd/attrd_attributes.c b/daemons/attrd/attrd_attributes.c index 34440904af5..f2fc74a9d55 100644 --- a/daemons/attrd/attrd_attributes.c +++ b/daemons/attrd/attrd_attributes.c @@ -30,6 +30,8 @@ attrd_create_attribute(xmlNode *xml) const char *dampen_s = pcmk__xe_get(xml, PCMK__XA_ATTR_DAMPENING); attribute_t *a = NULL; + pcmk__assert(name != NULL); + if (set_type == NULL) { set_type = PCMK_XE_INSTANCE_ATTRIBUTES; } diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index 4b6c20f37dc..d9a2c05b5c4 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -513,7 +513,7 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f action->ipc_id = request->ipc_id; action->flags = request->flags; - action->timer = mainloop_timer_add(NULL, 15000, true, + action->timer = mainloop_timer_add("attrd_confirmation", 15000, true, confirmation_timeout_cb, action); mainloop_timer_start(action->timer); diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index 3301d8c3ac7..53fb6784528 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -120,7 +120,7 @@ cfg_connection_destroy(void *user_data) "reattempted once per second)"); corosync_cfg_finalize(cfg_handle); cfg_handle = 0; - reconnect_timer = mainloop_timer_add("corosync reconnect", 1000, true, + reconnect_timer = mainloop_timer_add("pcmkd_corosync_reconnect", 1000, true, cluster_reconnect_cb, NULL); mainloop_timer_start(reconnect_timer); } From aa00961b77f291328da260d9e1855ff1efcf616a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 23:35:02 -0700 Subject: [PATCH 57/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_new() To replace mainloop_timer_add() internally. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 2 +- daemons/attrd/attrd_sync.c | 4 +-- daemons/based/based_callbacks.c | 4 +-- daemons/controld/controld_fencing.c | 16 ++++++------ daemons/controld/controld_schedulerd.c | 12 ++++----- daemons/controld/controld_throttle.c | 4 +-- daemons/fenced/fenced_commands.c | 7 +++--- daemons/pacemakerd/pcmkd_corosync.c | 5 ++-- include/crm/common/mainloop_internal.h | 5 ++++ lib/cluster/election.c | 8 +++--- lib/common/mainloop.c | 34 ++++++++++++++++++++++++++ tools/crm_mon.c | 4 +-- 12 files changed, 73 insertions(+), 32 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 640313cff24..94a123cc3e9 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -492,7 +492,7 @@ set_alert_attribute_value(GHashTable *t, attribute_value_t *v) mainloop_timer_t * attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr) { - return mainloop_timer_add(id, timeout_ms, true, attribute_timer_cb, attr); + return pcmk__main_loop_timer_new(id, timeout_ms, attribute_timer_cb, attr); } /*! diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index d9a2c05b5c4..1a7ef24de51 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -513,8 +513,8 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f action->ipc_id = request->ipc_id; action->flags = request->flags; - action->timer = mainloop_timer_add("attrd_confirmation", 15000, true, - confirmation_timeout_cb, action); + action->timer = pcmk__main_loop_timer_new("attrd_confirmation", 15000, + confirmation_timeout_cb, action); mainloop_timer_start(action->timer); pcmk__intkey_table_insert(expected_confirmations, callid, action); diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index 25e5e1e5fd3..c20f6ead52e 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -102,8 +102,8 @@ void based_callbacks_init(void) { if (digest_timer == NULL) { - digest_timer = mainloop_timer_add("based_digest_timer", 5000, true, - digest_timer_cb, NULL); + digest_timer = pcmk__main_loop_timer_new("based_digest_timer", 5000, + digest_timer_cb, NULL); } } diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index f03c735d294..fdc5761364f 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -674,9 +674,9 @@ controld_timer_fencer_connect(void *user_data) if (controld_fencer_connect_timer == NULL) { controld_fencer_connect_timer = - mainloop_timer_add("controld_fencer_connect", 1000, true, - controld_timer_fencer_connect, - GINT_TO_POINTER(TRUE)); + pcmk__main_loop_timer_new("controld_fencer_connect", 1000, + controld_timer_fencer_connect, + GINT_TO_POINTER(TRUE)); } if (rc != pcmk_ok) { @@ -1074,8 +1074,9 @@ controld_trigger_fencing_history_sync(bool long_timeout) if (long_timeout) { if (fencing_history_sync_timer_long == NULL) { fencing_history_sync_timer_long = - mainloop_timer_add("history_sync_long", 30000, true, - fencing_history_sync_set_trigger, NULL); + pcmk__main_loop_timer_new("history_sync_long", 30000, + fencing_history_sync_set_trigger, + NULL); } pcmk__info("Fence history will be synchronized cluster-wide within 30 " "seconds"); @@ -1084,8 +1085,9 @@ controld_trigger_fencing_history_sync(bool long_timeout) } else { if (fencing_history_sync_timer_short == NULL) { fencing_history_sync_timer_short = - mainloop_timer_add("history_sync_short", 5000, true, - fencing_history_sync_set_trigger, NULL); + pcmk__main_loop_timer_new("history_sync_short", 5000, + fencing_history_sync_set_trigger, + NULL); } pcmk__info("Fence history will be synchronized cluster-wide within 5 " "seconds"); diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index c70c7aea67e..0c28526c85c 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -292,10 +292,10 @@ controld_expect_sched_reply(char *ref) { if (ref) { if (controld_sched_timer == NULL) { - controld_sched_timer = mainloop_timer_add("scheduler_reply_timer", - SCHED_TIMEOUT_MS, true, - controld_sched_timeout, - NULL); + controld_sched_timer = + pcmk__main_loop_timer_new("scheduler_reply_timer", + SCHED_TIMEOUT_MS, + controld_sched_timeout, NULL); } mainloop_timer_start(controld_sched_timer); } else { @@ -488,8 +488,8 @@ do_pe_invoke_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void pcmk__debug("Re-asking for the CIB: %d other peer updates still " "pending", (num_cib_op_callbacks() - 1)); - controld_cib_retry_timer = mainloop_timer_add("cib_retry", 1000, true, - sleep_timer, NULL); + controld_cib_retry_timer = pcmk__main_loop_timer_new("cib_retry", 1000, + sleep_timer, NULL); mainloop_timer_start(controld_cib_retry_timer); return; } diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 788a8322dc1..64b7f46b088 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -262,8 +262,8 @@ throttle_init(void) { if(throttle_records == NULL) { throttle_records = pcmk__strkey_table(NULL, throttle_record_free); - throttle_timer = mainloop_timer_add("throttle", (30 * 1000), true, - throttle_timer_cb, NULL); + throttle_timer = pcmk__main_loop_timer_new("throttle", (30 * 1000), + throttle_timer_cb, NULL); } throttle_update_job_max(NULL); diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index ee2cc89a03c..dd1a2080629 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -1192,9 +1192,10 @@ build_device_from_xml(const xmlNode *dev) } else if (rc == EAGAIN) { if (device->timer == NULL) { - device->timer = mainloop_timer_add("get_agent_metadata", 10 * 1000, - true, get_agent_metadata_cb, - device); + device->timer = pcmk__main_loop_timer_new("get_agent_metadata", + (10 * 1000), + get_agent_metadata_cb, + device); } if (!mainloop_timer_running(device->timer)) { diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index 53fb6784528..4230420af2f 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -120,8 +120,9 @@ cfg_connection_destroy(void *user_data) "reattempted once per second)"); corosync_cfg_finalize(cfg_handle); cfg_handle = 0; - reconnect_timer = mainloop_timer_add("pcmkd_corosync_reconnect", 1000, true, - cluster_reconnect_cb, NULL); + reconnect_timer = pcmk__main_loop_timer_new("pcmkd_corosync_reconnect", + 1000, cluster_reconnect_cb, + NULL); mainloop_timer_start(reconnect_timer); } diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index c3d6b7e5d34..96fb9110ca3 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -93,6 +93,11 @@ int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); +mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, + unsigned int interval_ms, + GSourceFunc callback, + void *user_data); + #ifdef __cplusplus } #endif diff --git a/lib/cluster/election.c b/lib/cluster/election.c index 5f3f0ef6d92..8c5e5d0386d 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -98,11 +98,9 @@ election_init(pcmk_cluster_t *cluster, void (*cb)(pcmk_cluster_t *)) cluster->priv->election = pcmk__assert_alloc(1, sizeof(pcmk__election_t)); cluster->priv->election->cb = cb; - cluster->priv->election->timeout = mainloop_timer_add(name, - ELECTION_TIMEOUT_MS, - true, - election_timer_cb, - cluster); + cluster->priv->election->timeout = + pcmk__main_loop_timer_new(name, ELECTION_TIMEOUT_MS, election_timer_cb, + cluster); } /*! diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 5fad98a7234..e3347e60cfa 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1356,6 +1356,40 @@ pcmk__main_loop_child_kill(pid_t pid) return child_waitpid(match, no_hang); } +/*! + * \internal + * \brief Create a main loop timer + * + * \param[in] name Timer name prefix (for logging only) + * \param[in] interval_ms Timer interval + * \param[in] callback Function to call after \p interval_ms expires + * \param[in] user_data User data for \p callback + * + * \return Newly allocated main loop timer (guaranteed not to be \c NULL) + * + * \note The new timer's \c name string starts with the \p name argument and + * includes the timer's interval and address. + * \note The caller is responsible for freeing the return value using + * \c mainloop_timer_del(). + */ +mainloop_timer_t * +pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, + GSourceFunc callback, void *user_data) +{ + mainloop_timer_t *timer = NULL; + pcmk__assert((name != NULL) && (callback != NULL)); + + timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); + timer->name = pcmk__assert_asprintf("%s-%u-%p", name, interval_ms, timer); + timer->period_ms = interval_ms; + timer->repeat = true; + timer->cb = callback; + timer->userdata = user_data; + + pcmk__trace("Created timer %s with data %p", timer->name, user_data); + return timer; +} + static gboolean mainloop_timer_cb(void *user_data) { diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 9d817bdd2f3..e58278bc6a3 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -2115,8 +2115,8 @@ refresh_after_event(gboolean data_updated, gboolean enforce) } if(refresh_timer == NULL) { - refresh_timer = mainloop_timer_add("refresh", 2000, true, - mon_trigger_refresh, NULL); + refresh_timer = pcmk__main_loop_timer_new("refresh", 2000, + mon_trigger_refresh, NULL); } if (reconnect_timer > 0) { From c0226e3f9998e0f44a3a88ba4a7efac1d509a2d1 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 23:43:21 -0700 Subject: [PATCH 58/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_running() To replace mainloop_timer_running() internally. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 4 ++-- daemons/controld/controld_fencing.c | 4 ++-- daemons/fenced/fenced_commands.c | 2 +- include/crm/common/mainloop_internal.h | 1 + lib/common/mainloop.c | 26 +++++++++++++++++++++++--- 5 files changed, 29 insertions(+), 8 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 94a123cc3e9..46b7a79cd37 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -319,7 +319,7 @@ attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *use */ } else if (a->timer) { // Attribute has a dampening value, so use that as delay - if (!mainloop_timer_running(a->timer)) { + if (!pcmk__main_loop_timer_running(a->timer)) { pcmk__trace("Delayed re-attempted write for %s by %s", name, pcmk__readable_interval(a->timeout_ms)); mainloop_timer_start(a->timer); @@ -536,7 +536,7 @@ write_attribute(attribute_t *a, bool ignore_delay) a->id, a->update); goto done; - } else if (mainloop_timer_running(a->timer)) { + } else if (pcmk__main_loop_timer_running(a->timer)) { if (ignore_delay) { mainloop_timer_stop(a->timer); pcmk__debug("Overriding '%s' write delay", a->id); diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index fdc5761364f..f81703ded65 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -443,7 +443,7 @@ destroy_fencer_connection(stonith_t *st, stonith_event_t *e) if (pcmk__is_set(controld_globals.fsa_input_register, R_ST_REQUIRED)) { pcmk__err("Lost fencer connection (will attempt to reconnect)"); - if (!mainloop_timer_running(controld_fencer_connect_timer)) { + if (!pcmk__main_loop_timer_running(controld_fencer_connect_timer)) { mainloop_timer_start(controld_fencer_connect_timer); } } else { @@ -686,7 +686,7 @@ controld_timer_fencer_connect(void *user_data) QB_XS " rc=%d", pcmk_strerror(rc), rc); - if (!mainloop_timer_running(controld_fencer_connect_timer)) { + if (!pcmk__main_loop_timer_running(controld_fencer_connect_timer)) { mainloop_timer_start(controld_fencer_connect_timer); } diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index dd1a2080629..025b11f578f 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -1198,7 +1198,7 @@ build_device_from_xml(const xmlNode *dev) device); } - if (!mainloop_timer_running(device->timer)) { + if (!pcmk__main_loop_timer_running(device->timer)) { mainloop_timer_start(device->timer); } } diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 96fb9110ca3..2adb7728fd4 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -97,6 +97,7 @@ mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, GSourceFunc callback, void *user_data); +bool pcmk__main_loop_timer_running(const mainloop_timer_t *timer); #ifdef __cplusplus } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index e3347e60cfa..d4eef4bef03 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1390,6 +1390,25 @@ pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, return timer; } +/*! + * \internal + * \brief Check whether a main loop timer is running + * + * A timer is running if its \c id field is nonzero, meaning that it has an + * active \c GSource with that ID associated with it. + * + * \param[in] timer Main loop timer + * + * \return \c true if the timer is running, or \c false otherwise + */ +bool +pcmk__main_loop_timer_running(const mainloop_timer_t *timer) +{ + CRM_CHECK(timer != NULL, return false); + + return (timer->id != 0); +} + static gboolean mainloop_timer_cb(void *user_data) { @@ -1398,10 +1417,11 @@ mainloop_timer_cb(void *user_data) pcmk__assert((timer != NULL) && (timer->cb != NULL)); + /* Ensure id is unset during callbacks so that + * pcmk__main_loop_timer_running() works as expected + */ id = timer->id; - timer->id = 0; /* Ensure it's unset during callbacks so that - * mainloop_timer_running() works as expected - */ + timer->id = 0; pcmk__trace("Invoking callbacks for timer %s", timer->name); From 2c3f5e1e630991df27fe525056c3509b5d1fb191 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Mon, 17 Aug 2026 23:47:41 -0700 Subject: [PATCH 59/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_free() To replace mainloop_timer_del() internally. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_attributes.c | 2 +- daemons/attrd/attrd_cib.c | 2 +- daemons/attrd/attrd_sync.c | 2 +- daemons/attrd/attrd_utils.c | 2 +- daemons/based/based_callbacks.c | 2 +- daemons/controld/controld_fencing.c | 9 ++++++--- daemons/controld/controld_schedulerd.c | 4 ++-- daemons/controld/controld_throttle.c | 2 +- daemons/fenced/fenced_commands.c | 2 +- daemons/pacemakerd/pcmkd_corosync.c | 4 ++-- include/crm/common/mainloop_internal.h | 1 + lib/cluster/election.c | 2 +- lib/common/mainloop.c | 21 ++++++++++++++++++++- 13 files changed, 39 insertions(+), 16 deletions(-) diff --git a/daemons/attrd/attrd_attributes.c b/daemons/attrd/attrd_attributes.c index f2fc74a9d55..318b4d67d43 100644 --- a/daemons/attrd/attrd_attributes.c +++ b/daemons/attrd/attrd_attributes.c @@ -99,7 +99,7 @@ attrd_update_dampening(attribute_t *a, xmlNode *xml, const char *attr) } if (a->timeout_ms != dampen) { - mainloop_timer_del(a->timer); + pcmk__main_loop_timer_free(a->timer); a->timeout_ms = (int) QB_MIN(dampen, INT_MAX); if (dampen > 0) { a->timer = attrd_add_timer(attr, a->timeout_ms, a); diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 46b7a79cd37..5406ee762d1 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -270,7 +270,7 @@ attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *use last_cib_op_done = call_id; if (a->timer && !a->timeout_ms) { // Remove temporary dampening for failed writes - g_clear_pointer(&a->timer, mainloop_timer_del); + g_clear_pointer(&a->timer, pcmk__main_loop_timer_free); } break; diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index 1a7ef24de51..6e90291c4bb 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -341,7 +341,7 @@ free_action(void *data) { struct confirmation_action *action = (struct confirmation_action *) data; g_list_free_full(action->respondents, free); - mainloop_timer_del(action->timer); + pcmk__main_loop_timer_free(action->timer); pcmk__xml_free(action->xml); free(action->client_id); free(action); diff --git a/daemons/attrd/attrd_utils.c b/daemons/attrd/attrd_utils.c index 3025f197e76..b63d2b29a01 100644 --- a/daemons/attrd/attrd_utils.c +++ b/daemons/attrd/attrd_utils.c @@ -213,7 +213,7 @@ attrd_free_attribute(void *data) free(a->set_type); free(a->user); - mainloop_timer_del(a->timer); + pcmk__main_loop_timer_free(a->timer); g_hash_table_destroy(a->values); free(a); diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index c20f6ead52e..da9a143e505 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -114,7 +114,7 @@ based_callbacks_init(void) void based_callbacks_cleanup(void) { - g_clear_pointer(&digest_timer, mainloop_timer_del); + g_clear_pointer(&digest_timer, pcmk__main_loop_timer_free); g_clear_pointer(&ping_digest, free); } diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index f81703ded65..e886001d805 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -735,7 +735,8 @@ controld_disconnect_fencer(bool destroy) g_clear_pointer(&fencer_api, fencer_api->cmds->free); } - g_clear_pointer(&controld_fencer_connect_timer, mainloop_timer_del); + g_clear_pointer(&controld_fencer_connect_timer, + pcmk__main_loop_timer_free); g_clear_pointer(&te_client_id, free); } } @@ -1018,8 +1019,10 @@ void controld_cleanup_fencing_history_sync(stonith_t *st, bool free_timers) { if (free_timers) { - g_clear_pointer(&fencing_history_sync_timer_short, mainloop_timer_del); - g_clear_pointer(&fencing_history_sync_timer_long, mainloop_timer_del); + g_clear_pointer(&fencing_history_sync_timer_short, + pcmk__main_loop_timer_free); + g_clear_pointer(&fencing_history_sync_timer_long, + pcmk__main_loop_timer_free); } else { mainloop_timer_stop(fencing_history_sync_timer_short); diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index 0c28526c85c..d01930b0442 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -312,7 +312,7 @@ controld_expect_sched_reply(char *ref) void controld_free_sched_timer(void) { - g_clear_pointer(&controld_sched_timer, mainloop_timer_del); + g_clear_pointer(&controld_sched_timer, pcmk__main_loop_timer_free); } // A_PE_INVOKE @@ -451,7 +451,7 @@ sleep_timer(void *data) { controld_set_fsa_action_flags(A_PE_INVOKE); controld_trigger_fsa(); - g_clear_pointer(&controld_cib_retry_timer, mainloop_timer_del); + g_clear_pointer(&controld_cib_retry_timer, pcmk__main_loop_timer_free); return G_SOURCE_REMOVE; } diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 64b7f46b088..7bb8e01faf8 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -292,7 +292,7 @@ controld_configure_throttle(GHashTable *options) void throttle_fini(void) { - g_clear_pointer(&throttle_timer, mainloop_timer_del); + g_clear_pointer(&throttle_timer, pcmk__main_loop_timer_free); g_clear_pointer(&throttle_records, g_hash_table_destroy); } diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 025b11f578f..522ca95eded 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -909,7 +909,7 @@ free_device(void *data) if (device->timer != NULL) { mainloop_timer_stop(device->timer); - mainloop_timer_del(device->timer); + pcmk__main_loop_timer_free(device->timer); } mainloop_destroy_trigger(device->work); diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index 4230420af2f..afb200c151d 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -96,7 +96,7 @@ static gboolean cluster_reconnect_cb(void *data) { if (cluster_connect_cfg()) { - g_clear_pointer(&reconnect_timer, mainloop_timer_del); + g_clear_pointer(&reconnect_timer, pcmk__main_loop_timer_free); pcmk__notice("Cluster reconnect succeeded"); pacemakerd_read_config(); restart_cluster_subdaemons(); @@ -134,7 +134,7 @@ cluster_disconnect_cfg(void) /* The mainloop should be gone by this point, so this isn't necessary, but * cleaning up memory should make valgrind happier. */ - g_clear_pointer(&reconnect_timer, mainloop_timer_del); + g_clear_pointer(&reconnect_timer, pcmk__main_loop_timer_free); } #define cs_repeat(counter, max, code) do { \ diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 2adb7728fd4..d1af7e438dc 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -98,6 +98,7 @@ mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, GSourceFunc callback, void *user_data); bool pcmk__main_loop_timer_running(const mainloop_timer_t *timer); +void pcmk__main_loop_timer_free(mainloop_timer_t *timer); #ifdef __cplusplus } diff --git a/lib/cluster/election.c b/lib/cluster/election.c index 8c5e5d0386d..8066bd55c2e 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -154,7 +154,7 @@ election_fini(pcmk_cluster_t *cluster) if ((cluster != NULL) && (cluster->priv->election != NULL)) { election_reset(cluster); pcmk__trace("Destroying election"); - mainloop_timer_del(cluster->priv->election->timeout); + pcmk__main_loop_timer_free(cluster->priv->election->timeout); g_clear_pointer(&cluster->priv->election, free); } } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d4eef4bef03..121d3e2cde1 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1370,7 +1370,7 @@ pcmk__main_loop_child_kill(pid_t pid) * \note The new timer's \c name string starts with the \p name argument and * includes the timer's interval and address. * \note The caller is responsible for freeing the return value using - * \c mainloop_timer_del(). + * \c pcmk__main_loop_timer_free(). */ mainloop_timer_t * pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, @@ -1409,6 +1409,25 @@ pcmk__main_loop_timer_running(const mainloop_timer_t *timer) return (timer->id != 0); } +/*! + * \internal + * \brief Free a main loop timer + * + * \param[in,out] timer Main loop timer + */ +void +pcmk__main_loop_timer_free(mainloop_timer_t *timer) +{ + if (timer == NULL) { + return; + } + + pcmk__trace("Destroying timer %s", timer->name); + mainloop_timer_stop(timer); + free(timer->name); + free(timer); +} + static gboolean mainloop_timer_cb(void *user_data) { From 5d3aa11a0b3f8d5b5671ad1f2b02ce389f303810 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 02:06:04 -0700 Subject: [PATCH 60/70] Refactor: various: Drop mainloop_timer_set_period() internally There are only two callers, and right now I think I'd rather they be explicit. Signed-off-by: Reid Wahl --- daemons/fenced/fenced_commands.c | 8 ++++++-- lib/cluster/election.c | 21 +++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 522ca95eded..40f623df1b9 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -643,8 +643,12 @@ get_agent_metadata_cb(void *data) if (rc == EAGAIN) { if (device->timer->period_ms < (160 * 1000)) { - mainloop_timer_set_period(device->timer, - (2 * device->timer->period_ms)); + device->timer->period_ms *= 2; + + if (pcmk__main_loop_timer_running(device->timer)) { + // Restart the timer using the new period + mainloop_timer_start(device->timer); + } } return G_SOURCE_CONTINUE; diff --git a/lib/cluster/election.c b/lib/cluster/election.c index 8066bd55c2e..0a3f24be006 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -189,8 +189,25 @@ election_timeout_stop(pcmk_cluster_t *cluster) void election_timeout_set_period(pcmk_cluster_t *cluster, unsigned int period) { - CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL), return); - mainloop_timer_set_period(cluster->priv->election->timeout, period); + mainloop_timer_t *timer = NULL; + + CRM_CHECK((cluster != NULL) + && (cluster->priv->election != NULL) + && (cluster->priv->election->timeout != NULL), + return); + + timer = cluster->priv->election->timeout; + + if (timer->period_ms == period) { + return; + } + + timer->period_ms = period; + + if (pcmk__main_loop_timer_running(timer)) { + // Restart the timer using the new period if it changed + mainloop_timer_start(timer); + } } static int From 22e782b8e111c4cd6d0b7b11eda65de2278b191f Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 02:12:00 -0700 Subject: [PATCH 61/70] Refactor: fencer: Drop dead code from get_agent_metadata_cb() mainloop_timer_cb() sets timer->id to 0 before calling timer->cb. So pcmk__main_loop_timer_running() would return false when called as part of get_agent_metadata_cb(), which is timer->cb in this case. Thus mainloop_timer_start() would never get called. The code that we're removing here is dead, but the period increase seems broken as noted in the FIXME comment. Signed-off-by: Reid Wahl --- daemons/fenced/fenced_commands.c | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 40f623df1b9..1788b26bf38 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -644,13 +644,12 @@ get_agent_metadata_cb(void *data) if (rc == EAGAIN) { if (device->timer->period_ms < (160 * 1000)) { device->timer->period_ms *= 2; - - if (pcmk__main_loop_timer_running(device->timer)) { - // Restart the timer using the new period - mainloop_timer_start(device->timer); - } } + /* @FIXME Does the updated period even take effect? G_SOURCE_CONTINUE + * tells mainloop_timer_cb() to keep the existing GSource. It seems as + * if that GSource would still use the old period. + */ return G_SOURCE_CONTINUE; } From 454fef1ea456ae06e3fbd90f9f031721202b70f8 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 02:21:43 -0700 Subject: [PATCH 62/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_start() To replace mainloop_timer_start() internally. All internal main loop timers have nonzero period and non-NULL callback. Add a temporary forward declaration for mainloop_timer_cb(). It will go away in an upcoming commit. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 4 ++-- daemons/attrd/attrd_corosync.c | 2 +- daemons/attrd/attrd_sync.c | 2 +- daemons/based/based_callbacks.c | 2 +- daemons/controld/controld_fencing.c | 8 ++++---- daemons/controld/controld_schedulerd.c | 6 ++++-- daemons/controld/controld_throttle.c | 2 +- daemons/fenced/fenced_commands.c | 2 +- daemons/pacemakerd/pcmkd_corosync.c | 2 +- include/crm/common/mainloop_internal.h | 1 + lib/cluster/election.c | 4 ++-- lib/common/mainloop.c | 27 ++++++++++++++++++++++++++ tools/crm_mon.c | 2 +- 13 files changed, 47 insertions(+), 17 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 5406ee762d1..0e9577b9ab2 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -322,7 +322,7 @@ attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *use if (!pcmk__main_loop_timer_running(a->timer)) { pcmk__trace("Delayed re-attempted write for %s by %s", name, pcmk__readable_interval(a->timeout_ms)); - mainloop_timer_start(a->timer); + pcmk__main_loop_timer_start(a->timer); } } else { /* Set a temporary dampening of 2 seconds (timer will continue @@ -330,7 +330,7 @@ attrd_cib_callback(xmlNode *msg, int call_id, int rc, xmlNode *output, void *use * write succeeds). */ a->timer = attrd_add_timer(a->id, 2000, a); - mainloop_timer_start(a->timer); + pcmk__main_loop_timer_start(a->timer); } } } diff --git a/daemons/attrd/attrd_corosync.c b/daemons/attrd/attrd_corosync.c index 20519e0911d..e89b1fca5e1 100644 --- a/daemons/attrd/attrd_corosync.c +++ b/daemons/attrd/attrd_corosync.c @@ -361,7 +361,7 @@ update_attr_on_host(attribute_t *a, const pcmk__node_status_t *peer, if (a->timeout_ms && a->timer) { pcmk__trace("Delaying write of %s %s for dampening", attr, pcmk__readable_interval(a->timeout_ms)); - mainloop_timer_start(a->timer); + pcmk__main_loop_timer_start(a->timer); } else { attrd_write_or_elect_attribute(a); } diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index 6e90291c4bb..0edfb48fab6 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -515,7 +515,7 @@ attrd_expect_confirmations(pcmk__request_t *request, attrd_confirmation_action_f action->timer = pcmk__main_loop_timer_new("attrd_confirmation", 15000, confirmation_timeout_cb, action); - mainloop_timer_start(action->timer); + pcmk__main_loop_timer_start(action->timer); pcmk__intkey_table_insert(expected_confirmations, callid, action); pcmk__trace("Callid %d now waiting on %u confirmations", callid, diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index da9a143e505..38861d6584d 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -566,7 +566,7 @@ based_perform_op_rw(xmlNode *request, const cib__operation_t *operation, ping_modified_since = true; } - mainloop_timer_start(digest_timer); + pcmk__main_loop_timer_start(digest_timer); done: if (!pcmk__any_flags_set(call_options, diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index e886001d805..0194707b0ce 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -444,7 +444,7 @@ destroy_fencer_connection(stonith_t *st, stonith_event_t *e) if (pcmk__is_set(controld_globals.fsa_input_register, R_ST_REQUIRED)) { pcmk__err("Lost fencer connection (will attempt to reconnect)"); if (!pcmk__main_loop_timer_running(controld_fencer_connect_timer)) { - mainloop_timer_start(controld_fencer_connect_timer); + pcmk__main_loop_timer_start(controld_fencer_connect_timer); } } else { pcmk__info("Disconnected from fencer"); @@ -687,7 +687,7 @@ controld_timer_fencer_connect(void *user_data) pcmk_strerror(rc), rc); if (!pcmk__main_loop_timer_running(controld_fencer_connect_timer)) { - mainloop_timer_start(controld_fencer_connect_timer); + pcmk__main_loop_timer_start(controld_fencer_connect_timer); } return G_SOURCE_CONTINUE; @@ -1083,7 +1083,7 @@ controld_trigger_fencing_history_sync(bool long_timeout) } pcmk__info("Fence history will be synchronized cluster-wide within 30 " "seconds"); - mainloop_timer_start(fencing_history_sync_timer_long); + pcmk__main_loop_timer_start(fencing_history_sync_timer_long); } else { if (fencing_history_sync_timer_short == NULL) { @@ -1094,7 +1094,7 @@ controld_trigger_fencing_history_sync(bool long_timeout) } pcmk__info("Fence history will be synchronized cluster-wide within 5 " "seconds"); - mainloop_timer_start(fencing_history_sync_timer_short); + pcmk__main_loop_timer_start(fencing_history_sync_timer_short); } } diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index d01930b0442..3ff1502b090 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -297,7 +297,9 @@ controld_expect_sched_reply(char *ref) SCHED_TIMEOUT_MS, controld_sched_timeout, NULL); } - mainloop_timer_start(controld_sched_timer); + + pcmk__main_loop_timer_start(controld_sched_timer); + } else { controld_stop_sched_timer(); } @@ -490,7 +492,7 @@ do_pe_invoke_callback(xmlNode * msg, int call_id, int rc, xmlNode * output, void controld_cib_retry_timer = pcmk__main_loop_timer_new("cib_retry", 1000, sleep_timer, NULL); - mainloop_timer_start(controld_cib_retry_timer); + pcmk__main_loop_timer_start(controld_cib_retry_timer); return; } diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 7bb8e01faf8..8246ab55481 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -267,7 +267,7 @@ throttle_init(void) } throttle_update_job_max(NULL); - mainloop_timer_start(throttle_timer); + pcmk__main_loop_timer_start(throttle_timer); } /*! diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 1788b26bf38..0b47e4dc66d 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -1202,7 +1202,7 @@ build_device_from_xml(const xmlNode *dev) } if (!pcmk__main_loop_timer_running(device->timer)) { - mainloop_timer_start(device->timer); + pcmk__main_loop_timer_start(device->timer); } } diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index afb200c151d..fa01312ce21 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -123,7 +123,7 @@ cfg_connection_destroy(void *user_data) reconnect_timer = pcmk__main_loop_timer_new("pcmkd_corosync_reconnect", 1000, cluster_reconnect_cb, NULL); - mainloop_timer_start(reconnect_timer); + pcmk__main_loop_timer_start(reconnect_timer); } void diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index d1af7e438dc..593072ed4f4 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -98,6 +98,7 @@ mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, GSourceFunc callback, void *user_data); bool pcmk__main_loop_timer_running(const mainloop_timer_t *timer); +void pcmk__main_loop_timer_start(mainloop_timer_t *timer); void pcmk__main_loop_timer_free(mainloop_timer_t *timer); #ifdef __cplusplus diff --git a/lib/cluster/election.c b/lib/cluster/election.c index 0a3f24be006..ead028570ad 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -162,7 +162,7 @@ election_fini(pcmk_cluster_t *cluster) static void election_timeout_start(pcmk_cluster_t *cluster) { - mainloop_timer_start(cluster->priv->election->timeout); + pcmk__main_loop_timer_start(cluster->priv->election->timeout); } /*! @@ -206,7 +206,7 @@ election_timeout_set_period(pcmk_cluster_t *cluster, unsigned int period) if (pcmk__main_loop_timer_running(timer)) { // Restart the timer using the new period if it changed - mainloop_timer_start(timer); + pcmk__main_loop_timer_start(timer); } } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 121d3e2cde1..eab5fa4e37a 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -31,6 +31,8 @@ struct trigger_s { unsigned int id; }; +static gboolean mainloop_timer_cb(void *user_data); + static GList *child_list = NULL; static qb_array_t *gio_map = NULL; @@ -1409,6 +1411,31 @@ pcmk__main_loop_timer_running(const mainloop_timer_t *timer) return (timer->id != 0); } +/*! + * \internal + * \brief Start a main loop timer + * + * Starting a timer consists of: + * 1. stopping the timer if it's already running (by removing the associated + * \c GSource) + * 2. creating a new \c GSource using \p timer->period_ms as the timeout (see + * \c pcmk__create_timer()) + * 3. assigning the new \c GSource ID to \p timer->id + * + * \param[in,out] timer Main loop timer + */ +void +pcmk__main_loop_timer_start(mainloop_timer_t *timer) +{ + CRM_CHECK((timer != NULL) && (timer->period_ms > 0) && (timer->cb != NULL), + return); + + mainloop_timer_stop(timer); + + pcmk__trace("Starting timer %s", timer->name); + timer->id = pcmk__create_timer(timer->period_ms, mainloop_timer_cb, timer); +} + /*! * \internal * \brief Free a main loop timer diff --git a/tools/crm_mon.c b/tools/crm_mon.c index e58278bc6a3..47ea1b57bef 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -2139,7 +2139,7 @@ refresh_after_event(gboolean data_updated, gboolean enforce) updates = 0; } else { - mainloop_timer_start(refresh_timer); + pcmk__main_loop_timer_start(refresh_timer); } } From c2f5eaf896f840945b3f502dbab3cbb129f84e2e Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 02:30:00 -0700 Subject: [PATCH 63/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_stop() To replace mainloop_timer_stop() internally. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 2 +- daemons/controld/controld_fencing.c | 4 ++-- daemons/controld/controld_schedulerd.c | 3 ++- daemons/fenced/fenced_commands.c | 2 +- include/crm/common/mainloop_internal.h | 1 + lib/cluster/election.c | 4 ++-- lib/common/mainloop.c | 25 +++++++++++++++++++++++-- tools/crm_mon.c | 6 +++--- 8 files changed, 35 insertions(+), 12 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 0e9577b9ab2..67be1b444c2 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -538,7 +538,7 @@ write_attribute(attribute_t *a, bool ignore_delay) } else if (pcmk__main_loop_timer_running(a->timer)) { if (ignore_delay) { - mainloop_timer_stop(a->timer); + pcmk__main_loop_timer_stop(a->timer); pcmk__debug("Overriding '%s' write delay", a->id); } else { pcmk__info("Delaying write of '%s'", a->id); diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index 0194707b0ce..fc760ba6d41 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -1025,8 +1025,8 @@ controld_cleanup_fencing_history_sync(stonith_t *st, bool free_timers) pcmk__main_loop_timer_free); } else { - mainloop_timer_stop(fencing_history_sync_timer_short); - mainloop_timer_stop(fencing_history_sync_timer_long); + pcmk__main_loop_timer_stop(fencing_history_sync_timer_short); + pcmk__main_loop_timer_stop(fencing_history_sync_timer_long); } if (st) { diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index 3ff1502b090..f698d30a8d9 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -276,7 +276,8 @@ controld_stop_sched_timer(void) pcmk__trace("Stopping timer for scheduler reply %s", controld_globals.fsa_pe_ref); } - mainloop_timer_stop(controld_sched_timer); + + pcmk__main_loop_timer_stop(controld_sched_timer); } /*! diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 0b47e4dc66d..450493c789a 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -911,7 +911,7 @@ free_device(void *data) g_list_free_full(device->targets, free); if (device->timer != NULL) { - mainloop_timer_stop(device->timer); + pcmk__main_loop_timer_stop(device->timer); pcmk__main_loop_timer_free(device->timer); } diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 593072ed4f4..36a0127e6e5 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -98,6 +98,7 @@ mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, GSourceFunc callback, void *user_data); bool pcmk__main_loop_timer_running(const mainloop_timer_t *timer); +void pcmk__main_loop_timer_stop(mainloop_timer_t *timer); void pcmk__main_loop_timer_start(mainloop_timer_t *timer); void pcmk__main_loop_timer_free(mainloop_timer_t *timer); diff --git a/lib/cluster/election.c b/lib/cluster/election.c index ead028570ad..53153a898c1 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -134,7 +134,7 @@ election_reset(pcmk_cluster_t *cluster) { if ((cluster != NULL) && (cluster->priv->election != NULL)) { pcmk__trace("Resetting election"); - mainloop_timer_stop(cluster->priv->election->timeout); + pcmk__main_loop_timer_stop(cluster->priv->election->timeout); g_clear_pointer(&cluster->priv->election->voted, g_hash_table_destroy); } } @@ -175,7 +175,7 @@ void election_timeout_stop(pcmk_cluster_t *cluster) { if ((cluster != NULL) && (cluster->priv->election != NULL)) { - mainloop_timer_stop(cluster->priv->election->timeout); + pcmk__main_loop_timer_stop(cluster->priv->election->timeout); } } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index eab5fa4e37a..a0168f4435c 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1411,6 +1411,27 @@ pcmk__main_loop_timer_running(const mainloop_timer_t *timer) return (timer->id != 0); } +/*! + * \internal + * \brief Stop a main loop timer + * + * Stopping a timer consists of removing its \c GSource and setting its \c id + * field to 0 (to indicate that it has no associated \c GSource). + * + * \param[in,out] timer Main loop timer + */ +void +pcmk__main_loop_timer_stop(mainloop_timer_t *timer) +{ + if (!pcmk__main_loop_timer_running(timer)) { + return; + } + + pcmk__trace("Stopping timer %s", timer->name); + g_source_remove(timer->id); + timer->id = 0; +} + /*! * \internal * \brief Start a main loop timer @@ -1430,7 +1451,7 @@ pcmk__main_loop_timer_start(mainloop_timer_t *timer) CRM_CHECK((timer != NULL) && (timer->period_ms > 0) && (timer->cb != NULL), return); - mainloop_timer_stop(timer); + pcmk__main_loop_timer_stop(timer); pcmk__trace("Starting timer %s", timer->name); timer->id = pcmk__create_timer(timer->period_ms, mainloop_timer_cb, timer); @@ -1450,7 +1471,7 @@ pcmk__main_loop_timer_free(mainloop_timer_t *timer) } pcmk__trace("Destroying timer %s", timer->name); - mainloop_timer_stop(timer); + pcmk__main_loop_timer_stop(timer); free(timer->name); free(timer); } diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 47ea1b57bef..436f47ef565 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -831,7 +831,7 @@ mon_cib_connection_destroy(void *user_data) if (refresh_timer != NULL) { /* we'll trigger a refresh after reconnect */ - mainloop_timer_stop(refresh_timer); + pcmk__main_loop_timer_stop(refresh_timer); } if (reconnect_timer) { /* we'll trigger a new reconnect-timeout at the end */ @@ -2121,7 +2121,7 @@ refresh_after_event(gboolean data_updated, gboolean enforce) if (reconnect_timer > 0) { /* we will receive a refresh request after successful reconnect */ - mainloop_timer_stop(refresh_timer); + pcmk__main_loop_timer_stop(refresh_timer); return; } @@ -2135,7 +2135,7 @@ refresh_after_event(gboolean data_updated, gboolean enforce) ((now - last_refresh) > pcmk__timeout_ms2s(options.reconnect_ms)) || updates >= 10) { mainloop_set_trigger((crm_trigger_t *) refresh_trigger); - mainloop_timer_stop(refresh_timer); + pcmk__main_loop_timer_stop(refresh_timer); updates = 0; } else { From bf19a0ca8140ee53bee7a61efb30529f480e8b6d Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 14:32:37 -0700 Subject: [PATCH 64/70] Refactor: libcrmcommon: New main_loop_timer_cb() To replace mainloop_timer_cb() in pcmk__main_loop_timer_start(). The names are confusing, but the plan is for the old one to go away in the future. Note that we can ignore timer->repeat, because it's true for all the timers that will get passed to this callback. Signed-off-by: Reid Wahl --- daemons/fenced/fenced_commands.c | 2 +- lib/common/mainloop.c | 47 +++++++++++++++++++++++++++++--- 2 files changed, 44 insertions(+), 5 deletions(-) diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index 450493c789a..b627b74baf3 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -647,7 +647,7 @@ get_agent_metadata_cb(void *data) } /* @FIXME Does the updated period even take effect? G_SOURCE_CONTINUE - * tells mainloop_timer_cb() to keep the existing GSource. It seems as + * tells main_loop_timer_cb() to keep the existing GSource. It seems as * if that GSource would still use the old period. */ return G_SOURCE_CONTINUE; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index a0168f4435c..247fab64a59 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -31,8 +31,6 @@ struct trigger_s { unsigned int id; }; -static gboolean mainloop_timer_cb(void *user_data); - static GList *child_list = NULL; static qb_array_t *gio_map = NULL; @@ -1384,7 +1382,6 @@ pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); timer->name = pcmk__assert_asprintf("%s-%u-%p", name, interval_ms, timer); timer->period_ms = interval_ms; - timer->repeat = true; timer->cb = callback; timer->userdata = user_data; @@ -1432,6 +1429,48 @@ pcmk__main_loop_timer_stop(mainloop_timer_t *timer) timer->id = 0; } +/*! + * \internal + * \brief Run a main loop timer's callback + * + * If the callback returns \c G_SOURCE_REMOVE, set \p timer->id to 0 to indicate + * that the timer has no associated \c GSource. + * + * \param[in,out] user_data Main loop timer (mainloop_timer_t *) + * + * \return The return value from \p timer->cb (\c G_SOURCE_CONTINUE to keep the + * timeout source, or \c G_SOURCE_REMOVE to remove it) + * + * \note This is a \c GSourceFunc. + */ +static gboolean +main_loop_timer_cb(void *user_data) +{ + int id = 0; + mainloop_timer_t *timer = user_data; + + pcmk__assert((timer != NULL) && (timer->cb != NULL)); + + /* Ensure id is unset during callbacks so that + * pcmk__main_loop_timer_running() works as expected. + * + * @TODO Why is this necessary or desirable? + */ + id = timer->id; + timer->id = 0; + + pcmk__trace("Invoking callbacks for timer %s", timer->name); + + // G_SOURCE_REMOVE is false; G_SOURCE_CONTINUE is true + if (!timer->cb(timer->userdata)) { + pcmk__trace("Timer %s complete", timer->name); + return G_SOURCE_REMOVE; + } + + timer->id = id; + return G_SOURCE_CONTINUE; +} + /*! * \internal * \brief Start a main loop timer @@ -1454,7 +1493,7 @@ pcmk__main_loop_timer_start(mainloop_timer_t *timer) pcmk__main_loop_timer_stop(timer); pcmk__trace("Starting timer %s", timer->name); - timer->id = pcmk__create_timer(timer->period_ms, mainloop_timer_cb, timer); + timer->id = pcmk__create_timer(timer->period_ms, main_loop_timer_cb, timer); } /*! From 8280f33dd3b9180e5d9a28723e475fe2ae655f9a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 14:39:52 -0700 Subject: [PATCH 65/70] Refactor: controller: Use "interval" instead of "period" for timers To me this seems clearer and more consistent with other code (aside from mainloop_timer_t, which will change next). Signed-off-by: Reid Wahl --- daemons/controld/controld_te_actions.c | 2 +- daemons/controld/controld_te_utils.c | 2 +- daemons/controld/controld_timers.c | 72 +++++++++++++------------- daemons/controld/controld_timers.h | 2 +- 4 files changed, 40 insertions(+), 38 deletions(-) diff --git a/daemons/controld/controld_te_actions.c b/daemons/controld/controld_te_actions.c index befce6d4ff1..dabc6dd672b 100644 --- a/daemons/controld/controld_te_actions.c +++ b/daemons/controld/controld_te_actions.c @@ -728,7 +728,7 @@ notify_crmd(pcmk__graph_t *graph) case pcmk__graph_restart: type = "restart"; if (controld_globals.fsa_state == S_TRANSITION_ENGINE) { - if (controld_get_period_transition_timer() > 0) { + if (controld_get_interval_transition_timer() > 0) { controld_stop_transition_timer(); controld_start_transition_timer(); } else { diff --git a/daemons/controld/controld_te_utils.c b/daemons/controld/controld_te_utils.c index d842ec6dc6e..05558238ea9 100644 --- a/daemons/controld/controld_te_utils.c +++ b/daemons/controld/controld_te_utils.c @@ -491,7 +491,7 @@ abort_transition_graph(int abort_priority, enum pcmk__graph_next abort_action, done: if (controld_globals.transition_graph->complete) { - if (controld_get_period_transition_timer() > 0) { + if (controld_get_interval_transition_timer() > 0) { controld_stop_transition_timer(); controld_start_transition_timer(); } else { diff --git a/daemons/controld/controld_timers.c b/daemons/controld/controld_timers.c index 0431bba3870..099077d011d 100644 --- a/daemons/controld/controld_timers.c +++ b/daemons/controld/controld_timers.c @@ -22,7 +22,7 @@ typedef struct { const char *desc; //!< Description unsigned int source_id; //!< Timer source ID - unsigned int period_ms; //!< Timer period + unsigned int interval_ms; //!< Timer interval enum crmd_fsa_input fsa_input; //!< Input to register if timer pops bool log_error; //!< Timer popping indicates error int counter; //!< For detecting loops @@ -68,7 +68,7 @@ controld_stop_timer(fsa_timer_t *timer) if (timer->source_id != 0) { pcmk__trace("Stopping %s (would inject %s if popped after %ums, " "src=%d)", timer->desc, fsa_input2string(timer->fsa_input), - timer->period_ms, timer->source_id); + timer->interval_ms, timer->source_id); g_source_remove(timer->source_id); timer->source_id = 0; return true; @@ -76,7 +76,7 @@ controld_stop_timer(fsa_timer_t *timer) pcmk__trace("%s already stopped (would inject %s if popped after %ums)", timer->desc, fsa_input2string(timer->fsa_input), - timer->period_ms); + timer->interval_ms); return false; } @@ -88,10 +88,10 @@ crm_timer_popped(void *data) if (timer->log_error) { pcmk__err("%s just popped in state %s! " QB_XS " input=%s time=%ums", timer->desc, fsa_state2string(controld_globals.fsa_state), - fsa_input2string(timer->fsa_input), timer->period_ms); + fsa_input2string(timer->fsa_input), timer->interval_ms); } else { pcmk__info("%s just popped " QB_XS " input=%s time=%ums", timer->desc, - fsa_input2string(timer->fsa_input), timer->period_ms); + fsa_input2string(timer->fsa_input), timer->interval_ms); timer->counter++; } @@ -146,17 +146,17 @@ crm_timer_popped(void *data) static void controld_start_timer(fsa_timer_t *timer) { - if (timer->source_id == 0 && timer->period_ms > 0) { - timer->source_id = pcmk__create_timer(timer->period_ms, + if ((timer->source_id == 0) && (timer->interval_ms > 0)) { + timer->source_id = pcmk__create_timer(timer->interval_ms, crm_timer_popped, timer); pcmk__assert(timer->source_id != 0); pcmk__debug("Started %s (inject %s if pops after %ums, source=%d)", timer->desc, fsa_input2string(timer->fsa_input), - timer->period_ms, timer->source_id); + timer->interval_ms, timer->source_id); } else { pcmk__debug("%s already running (inject %s if pops after %ums, " "source=%d)", timer->desc, - fsa_input2string(timer->fsa_input), timer->period_ms, + fsa_input2string(timer->fsa_input), timer->interval_ms, timer->source_id); } } @@ -242,7 +242,7 @@ new_fsa_timer(const char *desc, unsigned int interval_ms, fsa_timer_t *timer = pcmk__assert_alloc(1, sizeof(fsa_timer_t)); timer->desc = desc; - timer->period_ms = interval_ms; + timer->interval_ms = interval_ms; timer->fsa_input = fsa_input; timer->log_error = log_error; @@ -310,26 +310,25 @@ controld_configure_fsa_timers(GHashTable *options) // Election timer value = g_hash_table_lookup(options, PCMK_OPT_DC_DEADTIME); - pcmk_parse_interval_spec(value, &election_timer->period_ms); + pcmk_parse_interval_spec(value, &election_timer->interval_ms); // Integration timer value = g_hash_table_lookup(options, PCMK_OPT_JOIN_INTEGRATION_TIMEOUT); - pcmk_parse_interval_spec(value, &integration_timer->period_ms); + pcmk_parse_interval_spec(value, &integration_timer->interval_ms); // Finalization timer value = g_hash_table_lookup(options, PCMK_OPT_JOIN_FINALIZATION_TIMEOUT); - pcmk_parse_interval_spec(value, &finalization_timer->period_ms); + pcmk_parse_interval_spec(value, &finalization_timer->interval_ms); // Shutdown escalation timer value = g_hash_table_lookup(options, PCMK_OPT_SHUTDOWN_ESCALATION); - pcmk_parse_interval_spec(value, &shutdown_escalation_timer->period_ms); + pcmk_parse_interval_spec(value, &shutdown_escalation_timer->interval_ms); pcmk__debug("Shutdown escalation occurs if DC has not responded to request " - "in %ums", - shutdown_escalation_timer->period_ms); + "in %ums", shutdown_escalation_timer->interval_ms); // Transition timer value = g_hash_table_lookup(options, PCMK_OPT_TRANSITION_DELAY); - pcmk_parse_interval_spec(value, &transition_timer->period_ms); + pcmk_parse_interval_spec(value, &transition_timer->interval_ms); // Recheck interval value = g_hash_table_lookup(options, PCMK_OPT_CLUSTER_RECHECK_INTERVAL); @@ -358,7 +357,7 @@ controld_free_fsa_timers(void) bool controld_is_started_transition_timer(void) { - return (transition_timer->period_ms > 0) + return (transition_timer->interval_ms > 0) && (transition_timer->source_id != 0); } @@ -370,7 +369,7 @@ void controld_start_recheck_timer(void) { // Default to recheck interval configured in CIB (if any) - unsigned int period_ms = recheck_interval_ms; + unsigned int interval_ms = recheck_interval_ms; // If scheduler supplied a "recheck by" time, check whether that's sooner if (controld_globals.transition_graph->recheck_by > 0) { @@ -379,19 +378,20 @@ controld_start_recheck_timer(void) if (diff_seconds < 1) { // We're already past the desired time - period_ms = 500; + interval_ms = 500; } else { - period_ms = (unsigned int) QB_MIN(UINT_MAX, diff_seconds * 1000LL); + interval_ms = (unsigned int) QB_MIN(UINT_MAX, + (diff_seconds * 1000LL)); } // Use "recheck by" only if it's sooner than interval from CIB - if (period_ms > recheck_interval_ms) { - period_ms = recheck_interval_ms; + if (interval_ms > recheck_interval_ms) { + interval_ms = recheck_interval_ms; } } - if (period_ms > 0) { - recheck_timer->period_ms = period_ms; + if (interval_ms > 0) { + recheck_timer->interval_ms = interval_ms; controld_start_timer(recheck_timer); } } @@ -419,13 +419,15 @@ controld_stop_recheck_timer(void) } /*! - * \brief Get the transition timer's configured period - * \return The transition_timer's period + * \internal + * \brief Get the transition timer's configured interval + * + * \return The transition_timer's interval */ unsigned int -controld_get_period_transition_timer(void) +controld_get_interval_transition_timer(void) { - return transition_timer->period_ms; + return transition_timer->interval_ms; } /*! @@ -464,17 +466,17 @@ controld_start_transition_timer(void) * \internal * \brief Start the countdown sequence for a shutdown * - * \param[in] default_period_ms Period to use if the shutdown escalation - * timer's period is 0 + * \param[in] default_interval_ms Interval to use if the shutdown escalation + * timer's interval is 0 */ void -controld_shutdown_start_countdown(unsigned int default_period_ms) +controld_shutdown_start_countdown(unsigned int default_interval_ms) { - if (shutdown_escalation_timer->period_ms == 0) { - shutdown_escalation_timer->period_ms = default_period_ms; + if (shutdown_escalation_timer->interval_ms == 0) { + shutdown_escalation_timer->interval_ms = default_interval_ms; } pcmk__notice("Initiating controller shutdown sequence " QB_XS " limit=%ums", - shutdown_escalation_timer->period_ms); + shutdown_escalation_timer->interval_ms); controld_start_timer(shutdown_escalation_timer); } diff --git a/daemons/controld/controld_timers.h b/daemons/controld/controld_timers.h index 19412861d99..e824f73cf70 100644 --- a/daemons/controld/controld_timers.h +++ b/daemons/controld/controld_timers.h @@ -27,7 +27,7 @@ void controld_start_wait_timer(void); bool controld_is_started_transition_timer(void); -unsigned int controld_get_period_transition_timer(void); +unsigned int controld_get_interval_transition_timer(void); void controld_reset_counter_election_timer(void); From 4541343a23528cb509008bc5a3a44fc2cfdf225a Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 14:46:47 -0700 Subject: [PATCH 66/70] Refactor: libcrmcommon: Use "interval" instead of "period" for timers To me this seems clearer and more consistent with other code. Signed-off-by: Reid Wahl --- daemons/controld/controld_election.c | 2 +- daemons/fenced/fenced_commands.c | 8 +++---- include/crm/cluster/election_internal.h | 4 ++-- include/crm/common/mainloop.h | 4 ++-- include/crm/common/mainloop_internal.h | 2 +- lib/cluster/election.c | 12 +++++----- lib/common/mainloop.c | 32 ++++++++++++++----------- 7 files changed, 34 insertions(+), 30 deletions(-) diff --git a/daemons/controld/controld_election.c b/daemons/controld/controld_election.c index 43d5f0f7e5f..57b4191f9e1 100644 --- a/daemons/controld/controld_election.c +++ b/daemons/controld/controld_election.c @@ -45,7 +45,7 @@ controld_configure_election(GHashTable *options) unsigned int interval_ms = 0; pcmk_parse_interval_spec(value, &interval_ms); - election_timeout_set_period(controld_globals.cluster, interval_ms); + election_timeout_set_interval(controld_globals.cluster, interval_ms); } void diff --git a/daemons/fenced/fenced_commands.c b/daemons/fenced/fenced_commands.c index b627b74baf3..82c30700cdd 100644 --- a/daemons/fenced/fenced_commands.c +++ b/daemons/fenced/fenced_commands.c @@ -642,13 +642,13 @@ get_agent_metadata_cb(void *data) } if (rc == EAGAIN) { - if (device->timer->period_ms < (160 * 1000)) { - device->timer->period_ms *= 2; + if (device->timer->interval_ms < (160 * 1000)) { + device->timer->interval_ms *= 2; } - /* @FIXME Does the updated period even take effect? G_SOURCE_CONTINUE + /* @FIXME Does the updated interval even take effect? G_SOURCE_CONTINUE * tells main_loop_timer_cb() to keep the existing GSource. It seems as - * if that GSource would still use the old period. + * if that GSource would still use the old interval. */ return G_SOURCE_CONTINUE; } diff --git a/include/crm/cluster/election_internal.h b/include/crm/cluster/election_internal.h index 4825f8e103e..8e60b62824b 100644 --- a/include/crm/cluster/election_internal.h +++ b/include/crm/cluster/election_internal.h @@ -73,8 +73,8 @@ enum election_result { void election_reset(pcmk_cluster_t *cluster); void election_init(pcmk_cluster_t *cluster, void (*cb)(pcmk_cluster_t *)); -void election_timeout_set_period(pcmk_cluster_t *cluster, - unsigned int period_ms); +void election_timeout_set_interval(pcmk_cluster_t *cluster, + unsigned int interval_ms); void election_timeout_stop(pcmk_cluster_t *cluster); void election_vote(pcmk_cluster_t *cluster); diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index 89d85d983f7..beedda1dd12 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -72,10 +72,10 @@ void mainloop_timer_start(mainloop_timer_t *timer); void mainloop_timer_stop(mainloop_timer_t *timer); unsigned int mainloop_timer_set_period(mainloop_timer_t *timer, - unsigned int period_ms); + unsigned int interval_ms); // NOTE: sbd (as of at least 1.5.2) uses this -mainloop_timer_t *mainloop_timer_add(const char *name, unsigned int period_ms, +mainloop_timer_t *mainloop_timer_add(const char *name, unsigned int interval_ms, bool repeat, GSourceFunc cb, void *userdata); diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 36a0127e6e5..5b567a1b58d 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -62,7 +62,7 @@ struct mainloop_child_s { struct mainloop_timer_s { unsigned int id; - unsigned int period_ms; + unsigned int interval_ms; gboolean repeat; char *name; GSourceFunc cb; diff --git a/lib/cluster/election.c b/lib/cluster/election.c index 53153a898c1..d2849f558f0 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -183,11 +183,11 @@ election_timeout_stop(pcmk_cluster_t *cluster) * \internal * \brief Change an election's timeout (restarting timer if running) * - * \param[in,out] cluster Cluster with election - * \param[in] period New timeout + * \param[in,out] cluster Cluster with election + * \param[in] interval_ms New timer interval in milliseconds */ void -election_timeout_set_period(pcmk_cluster_t *cluster, unsigned int period) +election_timeout_set_interval(pcmk_cluster_t *cluster, unsigned int interval_ms) { mainloop_timer_t *timer = NULL; @@ -198,14 +198,14 @@ election_timeout_set_period(pcmk_cluster_t *cluster, unsigned int period) timer = cluster->priv->election->timeout; - if (timer->period_ms == period) { + if (timer->interval_ms == interval_ms) { return; } - timer->period_ms = period; + timer->interval_ms = interval_ms; if (pcmk__main_loop_timer_running(timer)) { - // Restart the timer using the new period if it changed + // Restart the timer using the new interval if it changed pcmk__main_loop_timer_start(timer); } } diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 247fab64a59..894a96c0b8d 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1381,7 +1381,7 @@ pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); timer->name = pcmk__assert_asprintf("%s-%u-%p", name, interval_ms, timer); - timer->period_ms = interval_ms; + timer->interval_ms = interval_ms; timer->cb = callback; timer->userdata = user_data; @@ -1478,7 +1478,7 @@ main_loop_timer_cb(void *user_data) * Starting a timer consists of: * 1. stopping the timer if it's already running (by removing the associated * \c GSource) - * 2. creating a new \c GSource using \p timer->period_ms as the timeout (see + * 2. creating a new \c GSource using \p timer->interval_ms as the timeout (see * \c pcmk__create_timer()) * 3. assigning the new \c GSource ID to \p timer->id * @@ -1487,13 +1487,16 @@ main_loop_timer_cb(void *user_data) void pcmk__main_loop_timer_start(mainloop_timer_t *timer) { - CRM_CHECK((timer != NULL) && (timer->period_ms > 0) && (timer->cb != NULL), + CRM_CHECK((timer != NULL) + && (timer->interval_ms > 0) + && (timer->cb != NULL), return); pcmk__main_loop_timer_stop(timer); pcmk__trace("Starting timer %s", timer->name); - timer->id = pcmk__create_timer(timer->period_ms, main_loop_timer_cb, timer); + timer->id = pcmk__create_timer(timer->interval_ms, main_loop_timer_cb, + timer); } /*! @@ -1556,12 +1559,13 @@ mainloop_timer_start(mainloop_timer_t *timer) { mainloop_timer_stop(timer); - if ((timer == NULL) || (timer->period_ms == 0) || (timer->cb == NULL)) { + if ((timer == NULL) || (timer->interval_ms == 0) || (timer->cb == NULL)) { return; } pcmk__trace("Starting timer %s", timer->name); - timer->id = pcmk__create_timer(timer->period_ms, mainloop_timer_cb, timer); + timer->id = pcmk__create_timer(timer->interval_ms, mainloop_timer_cb, + timer); } void @@ -1577,7 +1581,7 @@ mainloop_timer_stop(mainloop_timer_t *timer) } unsigned int -mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int period_ms) +mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int interval_ms) { unsigned int last = 0; @@ -1585,10 +1589,10 @@ mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int period_ms) return 0; } - last = timer->period_ms; - timer->period_ms = period_ms; + last = timer->interval_ms; + timer->interval_ms = interval_ms; - if ((timer->id != 0) && (timer->period_ms != last)) { + if ((timer->id != 0) && (timer->interval_ms != last)) { mainloop_timer_start(timer); } @@ -1596,21 +1600,21 @@ mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int period_ms) } mainloop_timer_t * -mainloop_timer_add(const char *name, unsigned int period_ms, bool repeat, +mainloop_timer_add(const char *name, unsigned int interval_ms, bool repeat, GSourceFunc cb, void *userdata) { mainloop_timer_t *timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); if (name != NULL) { - timer->name = pcmk__assert_asprintf("%s-%u-%d", name, period_ms, + timer->name = pcmk__assert_asprintf("%s-%u-%d", name, interval_ms, repeat); } else { - timer->name = pcmk__assert_asprintf("%p-%u-%d", timer, period_ms, + timer->name = pcmk__assert_asprintf("%p-%u-%d", timer, interval_ms, repeat); } - timer->period_ms = period_ms; + timer->interval_ms = interval_ms; timer->repeat = repeat; timer->cb = cb; timer->userdata = userdata; From 2ed962f3f028918bc5fdfc9c46027b7b75180211 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 14:51:55 -0700 Subject: [PATCH 67/70] Refactor: libcrmcommon: Rename mainloop_timer_t:userdata to user_data Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 2 +- lib/common/mainloop.c | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 5b567a1b58d..32af6ba53d2 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -66,7 +66,7 @@ struct mainloop_timer_s { gboolean repeat; char *name; GSourceFunc cb; - void *userdata; + void *user_data; }; struct mainloop_io_s { diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 894a96c0b8d..9bd6fb05628 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1383,7 +1383,7 @@ pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, timer->name = pcmk__assert_asprintf("%s-%u-%p", name, interval_ms, timer); timer->interval_ms = interval_ms; timer->cb = callback; - timer->userdata = user_data; + timer->user_data = user_data; pcmk__trace("Created timer %s with data %p", timer->name, user_data); return timer; @@ -1462,7 +1462,7 @@ main_loop_timer_cb(void *user_data) pcmk__trace("Invoking callbacks for timer %s", timer->name); // G_SOURCE_REMOVE is false; G_SOURCE_CONTINUE is true - if (!timer->cb(timer->userdata)) { + if (!timer->cb(timer->user_data)) { pcmk__trace("Timer %s complete", timer->name); return G_SOURCE_REMOVE; } @@ -1535,7 +1535,7 @@ mainloop_timer_cb(void *user_data) pcmk__trace("Invoking callbacks for timer %s", timer->name); // G_SOURCE_REMOVE is false; G_SOURCE_CONTINUE is true - if (!timer->cb(timer->userdata)) { + if (!timer->cb(timer->user_data)) { pcmk__trace("Timer %s complete", timer->name); return G_SOURCE_REMOVE; } @@ -1617,7 +1617,7 @@ mainloop_timer_add(const char *name, unsigned int interval_ms, bool repeat, timer->interval_ms = interval_ms; timer->repeat = repeat; timer->cb = cb; - timer->userdata = userdata; + timer->user_data = userdata; pcmk__trace("Created timer %s with %p", timer->name, userdata); return timer; From cad6acb75d5b3f34487c94e0793a28bdb46daeec Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 14:56:22 -0700 Subject: [PATCH 68/70] Refactor: libcrmcommon: Rename mainloop_timer_t:id to source_id I want to make it more clear that this is not the ID of the mainloop_timer_t object. If the timer is running, this field is set to the ID of the GSource that we added with pcmk__create_timer(). Otherwise, it's set to 0 to indicate that there is no such GSource. Signed-off-by: Reid Wahl --- include/crm/common/mainloop_internal.h | 4 +-- lib/common/mainloop.c | 42 +++++++++++++------------- 2 files changed, 23 insertions(+), 23 deletions(-) diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index 32af6ba53d2..b4af7e68551 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -61,10 +61,10 @@ struct mainloop_child_s { }; struct mainloop_timer_s { - unsigned int id; + char *name; + unsigned int source_id; unsigned int interval_ms; gboolean repeat; - char *name; GSourceFunc cb; void *user_data; }; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 9bd6fb05628..9156f5e897a 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1405,7 +1405,7 @@ pcmk__main_loop_timer_running(const mainloop_timer_t *timer) { CRM_CHECK(timer != NULL, return false); - return (timer->id != 0); + return (timer->source_id != 0); } /*! @@ -1425,16 +1425,16 @@ pcmk__main_loop_timer_stop(mainloop_timer_t *timer) } pcmk__trace("Stopping timer %s", timer->name); - g_source_remove(timer->id); - timer->id = 0; + g_source_remove(timer->source_id); + timer->source_id = 0; } /*! * \internal * \brief Run a main loop timer's callback * - * If the callback returns \c G_SOURCE_REMOVE, set \p timer->id to 0 to indicate - * that the timer has no associated \c GSource. + * If the callback returns \c G_SOURCE_REMOVE, set \p timer->source_id to 0 to + * indicate that the timer has no associated \c GSource. * * \param[in,out] user_data Main loop timer (mainloop_timer_t *) * @@ -1456,8 +1456,8 @@ main_loop_timer_cb(void *user_data) * * @TODO Why is this necessary or desirable? */ - id = timer->id; - timer->id = 0; + id = timer->source_id; + timer->source_id = 0; pcmk__trace("Invoking callbacks for timer %s", timer->name); @@ -1467,7 +1467,7 @@ main_loop_timer_cb(void *user_data) return G_SOURCE_REMOVE; } - timer->id = id; + timer->source_id = id; return G_SOURCE_CONTINUE; } @@ -1480,7 +1480,7 @@ main_loop_timer_cb(void *user_data) * \c GSource) * 2. creating a new \c GSource using \p timer->interval_ms as the timeout (see * \c pcmk__create_timer()) - * 3. assigning the new \c GSource ID to \p timer->id + * 3. assigning the new \c GSource ID to \p timer->source_id * * \param[in,out] timer Main loop timer */ @@ -1495,8 +1495,8 @@ pcmk__main_loop_timer_start(mainloop_timer_t *timer) pcmk__main_loop_timer_stop(timer); pcmk__trace("Starting timer %s", timer->name); - timer->id = pcmk__create_timer(timer->interval_ms, main_loop_timer_cb, - timer); + timer->source_id = pcmk__create_timer(timer->interval_ms, + main_loop_timer_cb, timer); } /*! @@ -1529,8 +1529,8 @@ mainloop_timer_cb(void *user_data) /* Ensure id is unset during callbacks so that * pcmk__main_loop_timer_running() works as expected */ - id = timer->id; - timer->id = 0; + id = timer->source_id; + timer->source_id = 0; pcmk__trace("Invoking callbacks for timer %s", timer->name); @@ -1544,14 +1544,14 @@ mainloop_timer_cb(void *user_data) return G_SOURCE_REMOVE; } - timer->id = id; + timer->source_id = id; return G_SOURCE_CONTINUE; } bool mainloop_timer_running(mainloop_timer_t *timer) { - return (timer != NULL) && (timer->id != 0); + return (timer != NULL) && (timer->source_id != 0); } void @@ -1564,20 +1564,20 @@ mainloop_timer_start(mainloop_timer_t *timer) } pcmk__trace("Starting timer %s", timer->name); - timer->id = pcmk__create_timer(timer->interval_ms, mainloop_timer_cb, - timer); + timer->source_id = pcmk__create_timer(timer->interval_ms, mainloop_timer_cb, + timer); } void mainloop_timer_stop(mainloop_timer_t *timer) { - if ((timer == NULL) || (timer->id == 0)) { + if ((timer == NULL) || (timer->source_id == 0)) { return; } pcmk__trace("Stopping timer %s", timer->name); - g_source_remove(timer->id); - timer->id = 0; + g_source_remove(timer->source_id); + timer->source_id = 0; } unsigned int @@ -1592,7 +1592,7 @@ mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int interval_ms) last = timer->interval_ms; timer->interval_ms = interval_ms; - if ((timer->id != 0) && (timer->interval_ms != last)) { + if ((timer->source_id != 0) && (timer->interval_ms != last)) { mainloop_timer_start(timer); } From cafad306a3115bc71029e22dbd3461b2745d6778 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 15:20:02 -0700 Subject: [PATCH 69/70] Refactor: libcrmcommon: New pcmk__main_loop_timer_t To replace mainloop_timer_t internally. Signed-off-by: Reid Wahl --- daemons/attrd/attrd_cib.c | 2 +- daemons/attrd/attrd_sync.c | 2 +- daemons/attrd/pacemaker-attrd.h | 5 +-- daemons/based/based_callbacks.c | 2 +- daemons/controld/controld_fencing.c | 6 ++-- daemons/controld/controld_schedulerd.c | 4 +-- daemons/controld/controld_throttle.c | 2 +- daemons/fenced/pacemaker-fenced.h | 2 +- daemons/pacemakerd/pcmkd_corosync.c | 2 +- include/crm/common/mainloop_internal.h | 45 +++++++++++++++++++++----- lib/cluster/election.c | 4 +-- lib/common/mainloop.c | 19 +++++------ tools/crm_mon.c | 2 +- 13 files changed, 64 insertions(+), 33 deletions(-) diff --git a/daemons/attrd/attrd_cib.c b/daemons/attrd/attrd_cib.c index 67be1b444c2..31f86f68060 100644 --- a/daemons/attrd/attrd_cib.c +++ b/daemons/attrd/attrd_cib.c @@ -489,7 +489,7 @@ set_alert_attribute_value(GHashTable *t, attribute_value_t *v) g_hash_table_replace(t, a_v->nodename, a_v); } -mainloop_timer_t * +pcmk__main_loop_timer_t * attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr) { return pcmk__main_loop_timer_new(id, timeout_ms, attribute_timer_cb, attr); diff --git a/daemons/attrd/attrd_sync.c b/daemons/attrd/attrd_sync.c index 0edfb48fab6..2ae497c93d5 100644 --- a/daemons/attrd/attrd_sync.c +++ b/daemons/attrd/attrd_sync.c @@ -66,7 +66,7 @@ struct confirmation_action { * \brief A timer that will be used to remove the client should it time out * before receiving all confirmations */ - mainloop_timer_t *timer; + pcmk__main_loop_timer_t *timer; /*! * \brief A function to run when all confirmations have been received diff --git a/daemons/attrd/pacemaker-attrd.h b/daemons/attrd/pacemaker-attrd.h index 8d9b50ad408..1c6d2c5508b 100644 --- a/daemons/attrd/pacemaker-attrd.h +++ b/daemons/attrd/pacemaker-attrd.h @@ -141,7 +141,7 @@ typedef struct { int timeout_ms; // How long to wait for more changes before writing uint32_t flags; // Group of enum attrd_attr_flags GHashTable *values; // Key: node name, value: attribute_value_t - mainloop_timer_t *timer; // Timer to use for timeout_ms + pcmk__main_loop_timer_t *timer; // Timer to use for timeout_ms } attribute_t; #define attrd_set_attr_flags(attr, flags_to_set) do { \ @@ -236,7 +236,8 @@ extern int minimum_protocol_version; void attrd_remove_peer_protocol_ver(const char *host); void attrd_update_minimum_protocol_ver(const char *host, const char *value); -mainloop_timer_t *attrd_add_timer(const char *id, int timeout_ms, attribute_t *attr); +pcmk__main_loop_timer_t *attrd_add_timer(const char *id, int timeout_ms, + attribute_t *attr); void attrd_unregister_handlers(void); void attrd_handle_request(pcmk__request_t *request); diff --git a/daemons/based/based_callbacks.c b/daemons/based/based_callbacks.c index 38861d6584d..44f6ea2f1d8 100644 --- a/daemons/based/based_callbacks.c +++ b/daemons/based/based_callbacks.c @@ -34,7 +34,7 @@ #include "pacemaker-based.h" -static mainloop_timer_t *digest_timer = NULL; +static pcmk__main_loop_timer_t *digest_timer = NULL; static long long ping_seq = 0; static char *ping_digest = NULL; static bool ping_modified_since = false; diff --git a/daemons/controld/controld_fencing.c b/daemons/controld/controld_fencing.c index fc760ba6d41..41d8d068a1c 100644 --- a/daemons/controld/controld_fencing.c +++ b/daemons/controld/controld_fencing.c @@ -386,7 +386,7 @@ controld_execute_fencing_cleanup(void) */ static stonith_t *fencer_api = NULL; -static mainloop_timer_t *controld_fencer_connect_timer = NULL; +static pcmk__main_loop_timer_t *controld_fencer_connect_timer = NULL; static char *te_client_id = NULL; static bool @@ -1012,8 +1012,8 @@ controld_validate_fencing_watchdog_timeout(const char *value) */ static crm_trigger_t *fencing_history_sync_trigger = NULL; -static mainloop_timer_t *fencing_history_sync_timer_short = NULL; -static mainloop_timer_t *fencing_history_sync_timer_long = NULL; +static pcmk__main_loop_timer_t *fencing_history_sync_timer_short = NULL; +static pcmk__main_loop_timer_t *fencing_history_sync_timer_long = NULL; void controld_cleanup_fencing_history_sync(stonith_t *st, bool free_timers) diff --git a/daemons/controld/controld_schedulerd.c b/daemons/controld/controld_schedulerd.c index f698d30a8d9..7eba4cb4e90 100644 --- a/daemons/controld/controld_schedulerd.c +++ b/daemons/controld/controld_schedulerd.c @@ -25,7 +25,7 @@ static pcmk_ipc_api_t *schedulerd_api = NULL; -static mainloop_timer_t *controld_cib_retry_timer = NULL; +static pcmk__main_loop_timer_t *controld_cib_retry_timer = NULL; /*! * \internal @@ -235,7 +235,7 @@ do_pe_control(long long action, enum crmd_fsa_cause cause, } static int fsa_pe_query = 0; -static mainloop_timer_t *controld_sched_timer = NULL; +static pcmk__main_loop_timer_t *controld_sched_timer = NULL; // @TODO Make this a configurable cluster option if there's demand for it #define SCHED_TIMEOUT_MS (120000) diff --git a/daemons/controld/controld_throttle.c b/daemons/controld/controld_throttle.c index 8246ab55481..ad838aef84a 100644 --- a/daemons/controld/controld_throttle.c +++ b/daemons/controld/controld_throttle.c @@ -47,7 +47,7 @@ static float throttle_load_target = 0.0; #define THROTTLE_FACTOR_HIGH 2.0 static GHashTable *throttle_records = NULL; -static mainloop_timer_t *throttle_timer = NULL; +static pcmk__main_loop_timer_t *throttle_timer = NULL; static const char * load2str(enum throttle_state_e mode) diff --git a/daemons/fenced/pacemaker-fenced.h b/daemons/fenced/pacemaker-fenced.h index 6002c2f3601..810b3f9fc9c 100644 --- a/daemons/fenced/pacemaker-fenced.h +++ b/daemons/fenced/pacemaker-fenced.h @@ -143,7 +143,7 @@ typedef struct { GHashTable *params; GHashTable *aliases; GList *pending_ops; - mainloop_timer_t *timer; + pcmk__main_loop_timer_t *timer; crm_trigger_t *work; xmlNode *agent_metadata; const char *default_host_arg; diff --git a/daemons/pacemakerd/pcmkd_corosync.c b/daemons/pacemakerd/pcmkd_corosync.c index fa01312ce21..1eac33b0c22 100644 --- a/daemons/pacemakerd/pcmkd_corosync.c +++ b/daemons/pacemakerd/pcmkd_corosync.c @@ -34,7 +34,7 @@ #include "pcmkd_corosync.h" static corosync_cfg_handle_t cfg_handle = 0; -static mainloop_timer_t *reconnect_timer = NULL; +static pcmk__main_loop_timer_t *reconnect_timer = NULL; /* =::=::=::= CFG - Shutdown stuff =::=::=::= */ diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index b4af7e68551..f3f51acfdf0 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -60,6 +60,35 @@ struct mainloop_child_s { pcmk__main_loop_child_cb_t callback; }; +/*! + * \internal + * \brief Main loop timer + * + * This is an abstraction for a \c GSource that's added using \c g_timeout_add() + * or \c g_timeout_add_seconds(). + * + * A given source has an unsigned ID. This ID identifies the source to the main + * loop and allows it to be removed by \c g_source_remove(). + * + * To stop a timeout using GLib primitives, we remove the source. At that point, + * if we saved the source ID, it's no longer valid. If we want to start the + * timeout again using the same interval, callback function, and user data, we + * must pass all of those again to \c g_timeout_add() or similar. + * + * This structure facilitates timeout management and reuse. It encapsulates the + * timeout interval, callback function, and user data; a timer name for logging + * purposes; and the ID of the associated source if the timer is running. The ID + * is set to 0 if the timer is not running, indicating that there's no + * associated source. + */ +typedef struct { + char *name; //!< Timer name (for logging only) + unsigned int source_id; //!< Source ID (0 indicates timer not running) + unsigned int interval_ms; //!< Interval in milliseconds + GSourceFunc cb; //!< Callback called every \c interval_ms + void *user_data; //!< User data passed to \c cb +} pcmk__main_loop_timer_t; + struct mainloop_timer_s { char *name; unsigned int source_id; @@ -93,14 +122,14 @@ int pcmk__add_mainloop_ipc(crm_ipc_t *ipc, int priority, void *userdata, const struct ipc_client_callbacks *callbacks, mainloop_io_t **source); -mainloop_timer_t *pcmk__main_loop_timer_new(const char *name, - unsigned int interval_ms, - GSourceFunc callback, - void *user_data); -bool pcmk__main_loop_timer_running(const mainloop_timer_t *timer); -void pcmk__main_loop_timer_stop(mainloop_timer_t *timer); -void pcmk__main_loop_timer_start(mainloop_timer_t *timer); -void pcmk__main_loop_timer_free(mainloop_timer_t *timer); +pcmk__main_loop_timer_t *pcmk__main_loop_timer_new(const char *name, + unsigned int interval_ms, + GSourceFunc callback, + void *user_data); +bool pcmk__main_loop_timer_running(const pcmk__main_loop_timer_t *timer); +void pcmk__main_loop_timer_stop(pcmk__main_loop_timer_t *timer); +void pcmk__main_loop_timer_start(pcmk__main_loop_timer_t *timer); +void pcmk__main_loop_timer_free(pcmk__main_loop_timer_t *timer); #ifdef __cplusplus } diff --git a/lib/cluster/election.c b/lib/cluster/election.c index d2849f558f0..64ea1c38f08 100644 --- a/lib/cluster/election.c +++ b/lib/cluster/election.c @@ -28,7 +28,7 @@ struct pcmk__election { unsigned int count; // How many times local node has voted void (*cb)(pcmk_cluster_t *); // Function to call if election is won GHashTable *voted; // Key = node name, value = how node voted - mainloop_timer_t *timeout; // When to abort if all votes not received + pcmk__main_loop_timer_t *timeout; // When to abort if all votes not received int election_wins; // Track wins, for storm detection bool wrote_blackbox; // Write a storm blackbox at most once time_t expires; // When storm detection period ends @@ -189,7 +189,7 @@ election_timeout_stop(pcmk_cluster_t *cluster) void election_timeout_set_interval(pcmk_cluster_t *cluster, unsigned int interval_ms) { - mainloop_timer_t *timer = NULL; + pcmk__main_loop_timer_t *timer = NULL; CRM_CHECK((cluster != NULL) && (cluster->priv->election != NULL) diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index 9156f5e897a..d5e4841c3bf 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1372,14 +1372,14 @@ pcmk__main_loop_child_kill(pid_t pid) * \note The caller is responsible for freeing the return value using * \c pcmk__main_loop_timer_free(). */ -mainloop_timer_t * +pcmk__main_loop_timer_t * pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, GSourceFunc callback, void *user_data) { - mainloop_timer_t *timer = NULL; + pcmk__main_loop_timer_t *timer = NULL; pcmk__assert((name != NULL) && (callback != NULL)); - timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); + timer = pcmk__assert_alloc(1, sizeof(pcmk__main_loop_timer_t)); timer->name = pcmk__assert_asprintf("%s-%u-%p", name, interval_ms, timer); timer->interval_ms = interval_ms; timer->cb = callback; @@ -1401,7 +1401,7 @@ pcmk__main_loop_timer_new(const char *name, unsigned int interval_ms, * \return \c true if the timer is running, or \c false otherwise */ bool -pcmk__main_loop_timer_running(const mainloop_timer_t *timer) +pcmk__main_loop_timer_running(const pcmk__main_loop_timer_t *timer) { CRM_CHECK(timer != NULL, return false); @@ -1418,7 +1418,7 @@ pcmk__main_loop_timer_running(const mainloop_timer_t *timer) * \param[in,out] timer Main loop timer */ void -pcmk__main_loop_timer_stop(mainloop_timer_t *timer) +pcmk__main_loop_timer_stop(pcmk__main_loop_timer_t *timer) { if (!pcmk__main_loop_timer_running(timer)) { return; @@ -1436,7 +1436,8 @@ pcmk__main_loop_timer_stop(mainloop_timer_t *timer) * If the callback returns \c G_SOURCE_REMOVE, set \p timer->source_id to 0 to * indicate that the timer has no associated \c GSource. * - * \param[in,out] user_data Main loop timer (mainloop_timer_t *) + * \param[in,out] user_data Main loop timer + * (pcmk__main_loop_timer_t *) * * \return The return value from \p timer->cb (\c G_SOURCE_CONTINUE to keep the * timeout source, or \c G_SOURCE_REMOVE to remove it) @@ -1447,7 +1448,7 @@ static gboolean main_loop_timer_cb(void *user_data) { int id = 0; - mainloop_timer_t *timer = user_data; + pcmk__main_loop_timer_t *timer = user_data; pcmk__assert((timer != NULL) && (timer->cb != NULL)); @@ -1485,7 +1486,7 @@ main_loop_timer_cb(void *user_data) * \param[in,out] timer Main loop timer */ void -pcmk__main_loop_timer_start(mainloop_timer_t *timer) +pcmk__main_loop_timer_start(pcmk__main_loop_timer_t *timer) { CRM_CHECK((timer != NULL) && (timer->interval_ms > 0) @@ -1506,7 +1507,7 @@ pcmk__main_loop_timer_start(mainloop_timer_t *timer) * \param[in,out] timer Main loop timer */ void -pcmk__main_loop_timer_free(mainloop_timer_t *timer) +pcmk__main_loop_timer_free(pcmk__main_loop_timer_t *timer) { if (timer == NULL) { return; diff --git a/tools/crm_mon.c b/tools/crm_mon.c index 436f47ef565..b75b0329d86 100644 --- a/tools/crm_mon.c +++ b/tools/crm_mon.c @@ -67,7 +67,7 @@ static mon_output_format_t output_format = mon_output_unset; static GIOChannel *io_channel = NULL; static GMainLoop *mainloop = NULL; static unsigned int reconnect_timer = 0; -static mainloop_timer_t *refresh_timer = NULL; +static pcmk__main_loop_timer_t *refresh_timer = NULL; static enum pcmk_pacemakerd_state pcmkd_state = pcmk_pacemakerd_state_invalid; static cib_t *cib = NULL; From 85bd3eacb4641814f5288c0e60dbc799de4c5944 Mon Sep 17 00:00:00 2001 From: Reid Wahl Date: Tue, 18 Aug 2026 15:26:54 -0700 Subject: [PATCH 70/70] API: libcrmcommon: Deprecate mainloop_timer_t API Deprecate the mainloop_timer_t type itself and all functions that accept or manipulate mainloop_timer_t objects. External programs should not use Pacemaker for general-purpose asynchronous programming. Signed-off-by: Reid Wahl --- include/crm/common/mainloop.h | 25 +-- include/crm/common/mainloop_compat.h | 31 +++- include/crm/common/mainloop_internal.h | 9 - lib/common/mainloop.c | 241 +++++++++++++------------ 4 files changed, 155 insertions(+), 151 deletions(-) diff --git a/include/crm/common/mainloop.h b/include/crm/common/mainloop.h index beedda1dd12..c62dc61fd65 100644 --- a/include/crm/common/mainloop.h +++ b/include/crm/common/mainloop.h @@ -12,9 +12,9 @@ #include // bool #include // sighandler_t -#include // pid_t, ssize_t +#include // ssize_t -#include // gboolean, GSourceFunc, GMainLoop +#include // gboolean, GMainLoop #include // qb_ipcs_service_t, etc. #include @@ -35,9 +35,6 @@ typedef struct trigger_s crm_trigger_t; typedef struct mainloop_io_s mainloop_io_t; -// NOTE: sbd (as of at least 1.5.2) uses this -typedef struct mainloop_timer_s mainloop_timer_t; - void mainloop_cleanup(void); // NOTE: sbd (as of at least 1.5.2) uses this @@ -63,24 +60,6 @@ gboolean mainloop_add_signal(int sig, void (*dispatch) (int sig)); gboolean mainloop_destroy_signal(int sig); -bool mainloop_timer_running(mainloop_timer_t *timer); - -// NOTE: sbd (as of at least 1.5.2) uses this -void mainloop_timer_start(mainloop_timer_t *timer); - -// NOTE: sbd (as of at least 1.5.2) uses this -void mainloop_timer_stop(mainloop_timer_t *timer); - -unsigned int mainloop_timer_set_period(mainloop_timer_t *timer, - unsigned int interval_ms); - -// NOTE: sbd (as of at least 1.5.2) uses this -mainloop_timer_t *mainloop_timer_add(const char *name, unsigned int interval_ms, - bool repeat, GSourceFunc cb, - void *userdata); - -void mainloop_timer_del(mainloop_timer_t *timer); - struct ipc_client_callbacks { /*! * \brief Dispatch function for an IPC connection used as mainloop source diff --git a/include/crm/common/mainloop_compat.h b/include/crm/common/mainloop_compat.h index 6cd18620cbf..dd9232bf2d7 100644 --- a/include/crm/common/mainloop_compat.h +++ b/include/crm/common/mainloop_compat.h @@ -10,9 +10,10 @@ #ifndef PCMK__CRM_COMMON_MAINLOOP_COMPAT__H #define PCMK__CRM_COMMON_MAINLOOP_COMPAT__H +#include // bool #include // pid_t -#include // gboolean +#include // gboolean, GSourceFunc #include // mainloop_* @@ -68,6 +69,34 @@ void *mainloop_child_userdata(mainloop_child_t *child); //! \deprecated Do not use void mainloop_clear_child_userdata(mainloop_child_t *child); +// NOTE: sbd (as of at least 1.5.2) uses this +//! \deprecated Do not use +typedef struct mainloop_timer_s mainloop_timer_t; + +//! \deprecated Do not use +bool mainloop_timer_running(mainloop_timer_t *timer); + +// NOTE: sbd (as of at least 1.5.2) uses this +//! \deprecated Do not use +void mainloop_timer_start(mainloop_timer_t *timer); + +// NOTE: sbd (as of at least 1.5.2) uses this +//! \deprecated Do not use +void mainloop_timer_stop(mainloop_timer_t *timer); + +//! \deprecated Do not use +unsigned int mainloop_timer_set_period(mainloop_timer_t *timer, + unsigned int interval_ms); + +// NOTE: sbd (as of at least 1.5.2) uses this +//! \deprecated Do not use +mainloop_timer_t *mainloop_timer_add(const char *name, unsigned int interval_ms, + bool repeat, GSourceFunc cb, + void *userdata); + +//! \deprecated Do not use +void mainloop_timer_del(mainloop_timer_t *timer); + #ifdef __cplusplus } #endif diff --git a/include/crm/common/mainloop_internal.h b/include/crm/common/mainloop_internal.h index f3f51acfdf0..21e19bf9352 100644 --- a/include/crm/common/mainloop_internal.h +++ b/include/crm/common/mainloop_internal.h @@ -89,15 +89,6 @@ typedef struct { void *user_data; //!< User data passed to \c cb } pcmk__main_loop_timer_t; -struct mainloop_timer_s { - char *name; - unsigned int source_id; - unsigned int interval_ms; - gboolean repeat; - GSourceFunc cb; - void *user_data; -}; - struct mainloop_io_s { char *name; void *userdata; diff --git a/lib/common/mainloop.c b/lib/common/mainloop.c index d5e4841c3bf..f980c9193b1 100644 --- a/lib/common/mainloop.c +++ b/lib/common/mainloop.c @@ -1519,124 +1519,6 @@ pcmk__main_loop_timer_free(pcmk__main_loop_timer_t *timer) free(timer); } -static gboolean -mainloop_timer_cb(void *user_data) -{ - int id = 0; - mainloop_timer_t *timer = user_data; - - pcmk__assert((timer != NULL) && (timer->cb != NULL)); - - /* Ensure id is unset during callbacks so that - * pcmk__main_loop_timer_running() works as expected - */ - id = timer->source_id; - timer->source_id = 0; - - pcmk__trace("Invoking callbacks for timer %s", timer->name); - - // G_SOURCE_REMOVE is false; G_SOURCE_CONTINUE is true - if (!timer->cb(timer->user_data)) { - pcmk__trace("Timer %s complete", timer->name); - return G_SOURCE_REMOVE; - } - - if (!timer->repeat) { - return G_SOURCE_REMOVE; - } - - timer->source_id = id; - return G_SOURCE_CONTINUE; -} - -bool -mainloop_timer_running(mainloop_timer_t *timer) -{ - return (timer != NULL) && (timer->source_id != 0); -} - -void -mainloop_timer_start(mainloop_timer_t *timer) -{ - mainloop_timer_stop(timer); - - if ((timer == NULL) || (timer->interval_ms == 0) || (timer->cb == NULL)) { - return; - } - - pcmk__trace("Starting timer %s", timer->name); - timer->source_id = pcmk__create_timer(timer->interval_ms, mainloop_timer_cb, - timer); -} - -void -mainloop_timer_stop(mainloop_timer_t *timer) -{ - if ((timer == NULL) || (timer->source_id == 0)) { - return; - } - - pcmk__trace("Stopping timer %s", timer->name); - g_source_remove(timer->source_id); - timer->source_id = 0; -} - -unsigned int -mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int interval_ms) -{ - unsigned int last = 0; - - if (timer == NULL) { - return 0; - } - - last = timer->interval_ms; - timer->interval_ms = interval_ms; - - if ((timer->source_id != 0) && (timer->interval_ms != last)) { - mainloop_timer_start(timer); - } - - return last; -} - -mainloop_timer_t * -mainloop_timer_add(const char *name, unsigned int interval_ms, bool repeat, - GSourceFunc cb, void *userdata) -{ - mainloop_timer_t *timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); - - if (name != NULL) { - timer->name = pcmk__assert_asprintf("%s-%u-%d", name, interval_ms, - repeat); - - } else { - timer->name = pcmk__assert_asprintf("%p-%u-%d", timer, interval_ms, - repeat); - } - - timer->interval_ms = interval_ms; - timer->repeat = repeat; - timer->cb = cb; - timer->user_data = userdata; - - pcmk__trace("Created timer %s with %p", timer->name, userdata); - return timer; -} - -void -mainloop_timer_del(mainloop_timer_t *timer) -{ - if (timer == NULL) { - return; - } - - pcmk__trace("Destroying timer %s", timer->name); - mainloop_timer_stop(timer); - free(timer->name); - free(timer); -} - /* * Helpers to make sure certain events aren't lost at shutdown */ @@ -1832,5 +1714,128 @@ mainloop_clear_child_userdata(mainloop_child_t *child) child->user_data = NULL; } +struct mainloop_timer_s { + char *name; + unsigned int source_id; + unsigned int interval_ms; + gboolean repeat; + GSourceFunc cb; + void *user_data; +}; + +static gboolean +mainloop_timer_cb(void *user_data) +{ + int id = 0; + mainloop_timer_t *timer = user_data; + + pcmk__assert((timer != NULL) && (timer->cb != NULL)); + + id = timer->source_id; + timer->source_id = 0; + + pcmk__trace("Invoking callbacks for timer %s", timer->name); + + if (!timer->cb(timer->user_data)) { + pcmk__trace("Timer %s complete", timer->name); + return G_SOURCE_REMOVE; + } + + if (!timer->repeat) { + return G_SOURCE_REMOVE; + } + + timer->source_id = id; + return G_SOURCE_CONTINUE; +} + +bool +mainloop_timer_running(mainloop_timer_t *timer) +{ + return (timer != NULL) && (timer->source_id != 0); +} + +void +mainloop_timer_start(mainloop_timer_t *timer) +{ + mainloop_timer_stop(timer); + + if ((timer == NULL) || (timer->interval_ms == 0) || (timer->cb == NULL)) { + return; + } + + pcmk__trace("Starting timer %s", timer->name); + timer->source_id = pcmk__create_timer(timer->interval_ms, mainloop_timer_cb, + timer); +} + +void +mainloop_timer_stop(mainloop_timer_t *timer) +{ + if ((timer == NULL) || (timer->source_id == 0)) { + return; + } + + pcmk__trace("Stopping timer %s", timer->name); + g_source_remove(timer->source_id); + timer->source_id = 0; +} + +unsigned int +mainloop_timer_set_period(mainloop_timer_t *timer, unsigned int interval_ms) +{ + unsigned int last = 0; + + if (timer == NULL) { + return 0; + } + + last = timer->interval_ms; + timer->interval_ms = interval_ms; + + if ((timer->source_id != 0) && (timer->interval_ms != last)) { + mainloop_timer_start(timer); + } + + return last; +} + +mainloop_timer_t * +mainloop_timer_add(const char *name, unsigned int interval_ms, bool repeat, + GSourceFunc cb, void *userdata) +{ + mainloop_timer_t *timer = pcmk__assert_alloc(1, sizeof(mainloop_timer_t)); + + if (name != NULL) { + timer->name = pcmk__assert_asprintf("%s-%u-%d", name, interval_ms, + repeat); + + } else { + timer->name = pcmk__assert_asprintf("%p-%u-%d", timer, interval_ms, + repeat); + } + + timer->interval_ms = interval_ms; + timer->repeat = repeat; + timer->cb = cb; + timer->user_data = userdata; + + pcmk__trace("Created timer %s with %p", timer->name, userdata); + return timer; +} + +void +mainloop_timer_del(mainloop_timer_t *timer) +{ + if (timer == NULL) { + return; + } + + pcmk__trace("Destroying timer %s", timer->name); + mainloop_timer_stop(timer); + free(timer->name); + free(timer); +} + // LCOV_EXCL_STOP // End deprecated API