From df64e570925db02962e5246d99824e2de0b75049 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Wed, 8 Mar 2023 23:00:33 -0600 Subject: [PATCH] Feature: Add support for 'helper' mode to compliment the 'program' mode. This is a bug fix disguised as a new feature. When controller runs in program mode and is cancelled, all background (asynchronous) processes are cancelled. My original thoughts were that background processes should stay open in program mode. This is the bug being fixed. I believe that there are use cases to operate in "program" mode and to always terminate the background processes in this manner. Rather than fixing one case and breaking the other, this is a new feature that helps solve both cases. The "program" mode operates in the same manner unchanged. The new "helper" mode operates by detaching background (asynchronous) processes on exit. The foreground process still runs and blocks normally. A terminate signal might still terminate background processes. More work is likely needed in this regard. This stretches the original design to its limits. The 0.7.x versions and later will need re-design to better handle these cases. The original design I used was a learn as I go for thread design. This resulted in rather messy code. Now that I made it to this point, the controller program (0.6.x and earlier) can be used as a stepping stone for a better design. Some of the problems are worked-around. The program starts and exits too fast in controller mode. The child processes end up getting terminated before a complete process is started and then backgrounded. The work-around is to add a short sleep. This is not reliable but for most cases it should be fine. Additional work-arounds may be needed by the user such as executig their own sleep foreground process. --- level_3/controller/c/common/private-common.c | 1 + level_3/controller/c/common/private-common.h | 3 + level_3/controller/c/common/private-process.h | 2 +- level_3/controller/c/common/private-setting.h | 8 ++- level_3/controller/c/common/private-thread.h | 3 + level_3/controller/c/entry/private-entry.c | 6 +- level_3/controller/c/rule/private-rule.c | 1 - level_3/controller/c/thread/private-thread.c | 18 ++++++ level_3/controller/c/thread/private-thread.h | 23 +++++++ level_3/controller/c/thread/private-thread_entry.c | 11 ++++ .../controller/c/thread/private-thread_process.c | 73 +++++++++++++++------- level_3/controller/documents/entry.txt | 11 +++- level_3/controller/specifications/entry.txt | 2 +- 13 files changed, 130 insertions(+), 32 deletions(-) diff --git a/level_3/controller/c/common/private-common.c b/level_3/controller/c/common/private-common.c index 4bdee30..18aeb44 100644 --- a/level_3/controller/c/common/private-common.c +++ b/level_3/controller/c/common/private-common.c @@ -49,6 +49,7 @@ extern "C" { const f_string_static_t controller_full_path_s = macro_f_string_static_t_initialize(CONTROLLER_full_path_s, 0, CONTROLLER_full_path_s_length); const f_string_static_t controller_group_s = macro_f_string_static_t_initialize(CONTROLLER_group_s, 0, CONTROLLER_group_s_length); const f_string_static_t controller_groups_s = macro_f_string_static_t_initialize(CONTROLLER_groups_s, 0, CONTROLLER_groups_s_length); + const f_string_static_t controller_helper_s = macro_f_string_static_t_initialize(CONTROLLER_helper_s, 0, CONTROLLER_helper_s_length); const f_string_static_t controller_how_s = macro_f_string_static_t_initialize(CONTROLLER_how_s, 0, CONTROLLER_how_s_length); const f_string_static_t controller_idle_s = macro_f_string_static_t_initialize(CONTROLLER_idle_s, 0, CONTROLLER_idle_s_length); const f_string_static_t controller_iki_s = macro_f_string_static_t_initialize(CONTROLLER_iki_s, 0, CONTROLLER_iki_s_length); diff --git a/level_3/controller/c/common/private-common.h b/level_3/controller/c/common/private-common.h index 8b68e04..01a47df 100644 --- a/level_3/controller/c/common/private-common.h +++ b/level_3/controller/c/common/private-common.h @@ -73,6 +73,7 @@ extern "C" { #define CONTROLLER_full_path_s "full_path" #define CONTROLLER_group_s "group" #define CONTROLLER_groups_s "groups" + #define CONTROLLER_helper_s "helper" #define CONTROLLER_how_s "how" #define CONTROLLER_idle_s "idle" #define CONTROLLER_iki_s "iki" @@ -204,6 +205,7 @@ extern "C" { #define CONTROLLER_full_path_s_length 9 #define CONTROLLER_group_s_length 5 #define CONTROLLER_groups_s_length 6 + #define CONTROLLER_helper_s_length 6 #define CONTROLLER_how_s_length 3 #define CONTROLLER_idle_s_length 4 #define CONTROLLER_iki_s_length 3 @@ -335,6 +337,7 @@ extern "C" { extern const f_string_static_t controller_full_path_s; extern const f_string_static_t controller_group_s; extern const f_string_static_t controller_groups_s; + extern const f_string_static_t controller_helper_s; extern const f_string_static_t controller_how_s; extern const f_string_static_t controller_idle_s; extern const f_string_static_t controller_iki_s; diff --git a/level_3/controller/c/common/private-process.h b/level_3/controller/c/common/private-process.h index 7ccdf64..eb33b73 100644 --- a/level_3/controller/c/common/private-process.h +++ b/level_3/controller/c/common/private-process.h @@ -60,7 +60,7 @@ extern "C" { * cache: The cache used in this process. * child: The process id of a child process, if one is running (when forking to execute a child process). * lock: A read/write lock on the structure. - * options: Configuration options for this asynchronous thread. + * options: Configuration options for this thread. * result: The last return code from an execution of a process. * rule: A copy of the rule actively being executed. * stack: A stack used to represent dependencies as Rule ID's to avoid circular rule dependencies (If Rule A waits on Rule B, then Rule B must not wait on Rule A). diff --git a/level_3/controller/c/common/private-setting.h b/level_3/controller/c/common/private-setting.h index fdf3f56..8a50bd9 100644 --- a/level_3/controller/c/common/private-setting.h +++ b/level_3/controller/c/common/private-setting.h @@ -24,6 +24,7 @@ extern "C" { * - abort: Abort received before finished processing Entry/Exit. * * controller_setting_mode_*: + * - helper: Run as a helper, exiting when finished prrocess entry (and any respective exit). * - program: Run as a program, exiting when finished prrocess entry (and any respective exit). * - service: Run as a service, listening for requests after processing entry. * @@ -52,18 +53,19 @@ extern "C" { controller_setting_ready_done_e, controller_setting_ready_fail_e, controller_setting_ready_abort_e, - }; + }; // enum enum { controller_setting_mode_service_e = 0, + controller_setting_mode_helper_e, controller_setting_mode_program_e, - }; + }; // enum enum { controller_setting_flag_interruptible_e = 0x1, controller_setting_flag_pid_created_e = 0x2, controller_setting_flag_failsafe_e = 0x4, - }; + }; // enum typedef struct { uint8_t flag; diff --git a/level_3/controller/c/common/private-thread.h b/level_3/controller/c/common/private-thread.h index 6cb0fe2..6b48d1c 100644 --- a/level_3/controller/c/common/private-thread.h +++ b/level_3/controller/c/common/private-thread.h @@ -60,6 +60,9 @@ extern "C" { #define controller_thread_wait_timeout_4_seconds_d 20 #define controller_thread_wait_timeout_4_nanoseconds_d 0 + #define controller_thread_exit_helper_timeout_seconds_d 0 + #define controller_thread_exit_helper_timeout_nanoseconds_d 100000000 // 0.1 seconds in nanoseconds. + #define controller_thread_exit_ready_timeout_seconds_d 0 #define controller_thread_exit_ready_timeout_nanoseconds_d 500000000 // 0.5 seconds in nanoseconds. diff --git a/level_3/controller/c/entry/private-entry.c b/level_3/controller/c/entry/private-entry.c index 242bfb7..c9cf31d 100644 --- a/level_3/controller/c/entry/private-entry.c +++ b/level_3/controller/c/entry/private-entry.c @@ -1251,6 +1251,7 @@ extern "C" { } ++cache->ats.array[at_j]; + break; } } @@ -1487,7 +1488,7 @@ extern "C" { } // Check to see if any required processes failed, but do not do this if already operating in failsafe. - if (F_status_is_error_not(status) && !failsafe && global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) { + if (F_status_is_error_not(status) && !failsafe && global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e && global->setting->mode != controller_setting_mode_helper_e) { const f_status_t status_wait = controller_rule_wait_all(*global, is_entry, F_true, 0); if (F_status_is_error(status_wait)) { @@ -2078,6 +2079,9 @@ extern "C" { if (fl_string_dynamic_partial_compare_string(controller_service_s.string, cache->buffer_file, controller_service_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { global.setting->mode = controller_setting_mode_service_e; } + else if (fl_string_dynamic_partial_compare_string(controller_helper_s.string, cache->buffer_file, controller_helper_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { + global.setting->mode = controller_setting_mode_helper_e; + } else if (fl_string_dynamic_partial_compare_string(controller_program_s.string, cache->buffer_file, controller_program_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { global.setting->mode = controller_setting_mode_program_e; } diff --git a/level_3/controller/c/rule/private-rule.c b/level_3/controller/c/rule/private-rule.c index 5b098dc..7905182 100644 --- a/level_3/controller/c/rule/private-rule.c +++ b/level_3/controller/c/rule/private-rule.c @@ -6353,7 +6353,6 @@ extern "C" { f_thread_unlock(&process_list[i]->active); if (f_thread_lock_write_try(&process_list[i]->active) == F_none) { - controller_thread_join(&process_list[i]->id_thread); process_list[i]->state = controller_process_state_idle_e; diff --git a/level_3/controller/c/thread/private-thread.c b/level_3/controller/c/thread/private-thread.c index d5b401a..7ccd06a 100644 --- a/level_3/controller/c/thread/private-thread.c +++ b/level_3/controller/c/thread/private-thread.c @@ -154,6 +154,21 @@ extern "C" { } #endif // _di_controller_thread_cleanup_ +#ifndef _di_controller_thread_detach_ + f_status_t controller_thread_detach(f_thread_id_t * const id) { + + if (!id || !*id) return F_data_not; + + const f_status_t status = f_thread_detach(*id); + + if (F_status_is_error_not(status) || F_status_set_fine(status) == F_found_not) { + *id = 0; + } + + return status; + } +#endif // _di_controller_thread_detach_ + #ifndef _di_controller_thread_is_enabled_ f_status_t controller_thread_is_enabled(const bool is_normal, controller_thread_t * const thread) { @@ -291,6 +306,9 @@ extern "C" { if (setting->mode == controller_setting_mode_service_e) { controller_thread_join(&thread.id_signal); } + else if (setting->mode == controller_setting_mode_helper_e) { + status = controller_rule_wait_all(global, F_true, F_false, 0); + } else if (setting->mode == controller_setting_mode_program_e) { status = controller_rule_wait_all(global, F_true, F_false, 0); } diff --git a/level_3/controller/c/thread/private-thread.h b/level_3/controller/c/thread/private-thread.h index 8137bce..47841f4 100644 --- a/level_3/controller/c/thread/private-thread.h +++ b/level_3/controller/c/thread/private-thread.h @@ -26,6 +26,29 @@ extern "C" { extern void * controller_thread_cleanup(void * const arguments) F_attribute_visibility_internal_d; #endif // _di_controller_thread_cleanup_ +/*** + * Detach a thread, assigning id to NULL on success. + * + * If the ID is not found, then it is also set to NULL. + * + * This should be called for asynchronous processes. + * + * @param id + * The thread ID. + * + * @return + * F_none on success. + * + * Success from: f_thread_detach(). + * + * Errors (with error bit) from: f_thread_detach(). + * + * @see f_thread_detach() + */ +#ifndef _di_controller_thread_detach_ + extern f_status_t controller_thread_detach(f_thread_id_t * const id) F_attribute_visibility_internal_d; +#endif // _di_controller_thread_detach_ + /** * Check to see if thread is enabled for the normal operations like entry and control or for exit operations. * diff --git a/level_3/controller/c/thread/private-thread_entry.c b/level_3/controller/c/thread/private-thread_entry.c index ba707b6..25ef26e 100644 --- a/level_3/controller/c/thread/private-thread_entry.c +++ b/level_3/controller/c/thread/private-thread_entry.c @@ -4,6 +4,7 @@ #include "../lock/private-lock_print.h" #include "../thread/private-thread.h" #include "private-thread_entry.h" +#include "private-thread_process.h" #include "private-thread_signal.h" #ifdef __cplusplus @@ -112,6 +113,16 @@ extern "C" { entry->setting->ready = controller_setting_ready_done_e; } } + + if (F_status_is_error_not(*status) && *status != F_child && entry->global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e && entry->global->setting->mode == controller_setting_mode_helper_e) { + struct timespec time; + time.tv_sec = controller_thread_exit_helper_timeout_seconds_d; + time.tv_nsec = controller_thread_exit_helper_timeout_nanoseconds_d; + + nanosleep(&time, 0); + + controller_thread_process_cancel(*(entry->global), F_true, controller_thread_cancel_exit_e, 0); + } } } diff --git a/level_3/controller/c/thread/private-thread_process.c b/level_3/controller/c/thread/private-thread_process.c index 33fd192..2720596 100644 --- a/level_3/controller/c/thread/private-thread_process.c +++ b/level_3/controller/c/thread/private-thread_process.c @@ -49,40 +49,17 @@ extern "C" { // Only cancel when enabled. if (!controller_thread_is_enabled(is_normal, global.thread)) { - f_thread_mutex_unlock(&global.thread->lock.cancel); return; } - // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock). - f_status_t status = f_thread_mutex_lock(&global.thread->lock.alert); - - if (F_status_is_error(status)) { - global.thread->enabled = controller_thread_enabled_not_e; - } - else { - if (by == controller_thread_cancel_execute_e) { - global.thread->enabled = controller_thread_enabled_execute_e; - } - else if (by == controller_thread_cancel_exit_e) { - global.thread->enabled = controller_thread_enabled_not_e; - } - else if (by == controller_thread_cancel_exit_execute_e) { - global.thread->enabled = controller_thread_enabled_exit_execute_e; - } - else { - global.thread->enabled = controller_thread_enabled_exit_e; - } - - f_thread_mutex_unlock(&global.thread->lock.alert); - } - struct timespec time; controller_entry_t *entry = 0; controller_process_t *process = 0; + f_status_t status = F_none; f_array_length_t i = 0; f_array_length_t j = 0; pid_t pid = 0; @@ -101,6 +78,48 @@ extern "C" { time.tv_sec = 0; time.tv_nsec = interval_nanoseconds; + if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) { + int value = 0; + f_number_unsigned_t lapsed = 0; + + for (i = 0; i < global.thread->processs.used; ++i) { + + if (!global.thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; + + process = global.thread->processs.array[i]; + + if (!process->id_thread) continue; + + controller_thread_detach(&process->id_thread); + + process->id_thread = 0; + } // for + } + + // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock). + status = f_thread_mutex_lock(&global.thread->lock.alert); + + if (F_status_is_error(status)) { + global.thread->enabled = controller_thread_enabled_not_e; + } + else { + if (by == controller_thread_cancel_execute_e) { + global.thread->enabled = controller_thread_enabled_execute_e; + } + else if (by == controller_thread_cancel_exit_e) { + global.thread->enabled = controller_thread_enabled_not_e; + } + else if (by == controller_thread_cancel_exit_execute_e) { + global.thread->enabled = controller_thread_enabled_exit_execute_e; + } + else { + global.thread->enabled = controller_thread_enabled_exit_e; + } + + f_thread_mutex_unlock(&global.thread->lock.alert); + } + if (global.thread->id_cleanup) { f_thread_cancel(global.thread->id_cleanup); f_thread_join(global.thread->id_cleanup, 0); @@ -123,6 +142,12 @@ extern "C" { global.thread->id_signal = 0; } + if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) { + f_thread_mutex_unlock(&global.thread->lock.cancel); + + return; + } + for (; i < global.thread->processs.used; ++i) { if (!global.thread->processs.array[i]) continue; diff --git a/level_3/controller/documents/entry.txt b/level_3/controller/documents/entry.txt index 891a134..394fd95 100644 --- a/level_3/controller/documents/entry.txt +++ b/level_3/controller/documents/entry.txt @@ -60,10 +60,19 @@ Entry Documentation: - The code:"mode" setting\: Represents the mode in which the Entry is operating in. - The following modes are supported: code:"program" and code:"service". + The following modes are supported: code:"helper", code:"program", and code:"service". + + - The code:"helper" mode\: + Designates that the Entry operates as a helper for starting programs or performing actions and exits when complete. + On exit, any background (asynchronous) processes are not cancelled. + If terminated, the foreground (synchronous) process is cancelled. + Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit". + Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process). - The code:"program" mode\: Designates that the Entry operates as a program and exits when complete. + On exit, any background (asynchronous) processes are also cancelled. + If terminated, the foreground (synchronous) process is cancelled. Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit". Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process). diff --git a/level_3/controller/specifications/entry.txt b/level_3/controller/specifications/entry.txt index ef38cdf..92ceeea 100644 --- a/level_3/controller/specifications/entry.txt +++ b/level_3/controller/specifications/entry.txt @@ -33,7 +33,7 @@ Entry Specification: - code:"control_mode": Exactly one Content that is a valid file mode. - code:"control_user": Exactly one Content that is a user name or user id. - code:"define": Two Content, the first Content must be a case-sensitive valid environment variable name (alpha-numeric or underscore, but no leading digits). - - code:"mode": Exactly one Content that is one of code:"program" or code:"service". + - code:"mode": Exactly one Content that is one of code:"helper", code:"program", or code:"service". - code:"parameter": Two Content, the first Content must be a case-sensitive valid IKI name and the second being an IKI value. - code:"pid": Exactly one Content that is one of code:"disable", code:"require", or code:"ready". - code:"pid_file": Exactly one Content that is a relative or absolute path to a pid file. -- 1.8.3.1