From: Kevin Day Date: Thu, 21 Jul 2022 22:44:14 +0000 (-0500) Subject: Update: Timeout changes and documentation updates. X-Git-Tag: 0.6.0~30 X-Git-Url: https://git.kevux.org/?a=commitdiff_plain;h=6d1882b1870a4f63a8255528449a8a2d725125bb;p=fll Update: Timeout changes and documentation updates. I seem to have forgotten to complete this functionality. I believe it is too much work to do and this will not make the stable release. Update the documentation to reflect this. Add a new timeout option called "exit". This allows for handling the timeout when exiting the program. This is implemented and in use. The implementation is dirt simple and sub-optimal but allows for the functionality to be quickly implemented. The important addition here is the ability to disable the respective timeouts. No child processes are killed when the exit timeout is disabled. This can result in the program stalling for a long time or indefinitely. A new flag property is added to controller_entry_t and controller_entry_action_t. --- diff --git a/level_3/controller/c/common/private-entry.h b/level_3/controller/c/common/private-entry.h index a5dde65..ddbecc6 100644 --- a/level_3/controller/c/common/private-entry.h +++ b/level_3/controller/c/common/private-entry.h @@ -15,6 +15,9 @@ extern "C" { /** * An Entry Item Action. * + * controller_entry_action_flag_*: + * - undefined: The given type and code are designated as undefined. + * * controller_entry_action_type_*: * - consider: Designate a rule to be pre-loaded. * - execute: Execute into another program. @@ -37,8 +40,15 @@ extern "C" { * - require: Require Rule operations to succeed or the Entry/Exit will fail. * - wait: Wait for all existing asynchronous processes to finish before operating Rule. * + * controller_entry_timeout_code_*: + * - exit: The timeout Action represents an exit timeout. + * - kill: The timeout Action represents a kill timeout. + * - start: The timeout Action represents a start timeout. + * - stop: The timeout Action represents a stop timeout. + * * type: The type of Action. * code: A single code or sub-type associated with the Action. + * flag: A set of flags to describe special behavior for the given type and code (flags may be different per type and code). * line: The line number where the Entry Item begins. * number: The unsigned number that some types use instead of the "parameters". * status: The overall status. @@ -49,13 +59,20 @@ extern "C" { #define controller_entry_rule_code_require_d 0x2 #define controller_entry_rule_code_wait_d 0x4 - #define controller_entry_timeout_code_kill_d 0x1 - #define controller_entry_timeout_code_start_d 0x2 - #define controller_entry_timeout_code_stop_d 0x4 + #define controller_entry_timeout_code_exit_d 0x1 + #define controller_entry_timeout_code_kill_d 0x2 + #define controller_entry_timeout_code_start_d 0x4 + #define controller_entry_timeout_code_stop_d 0x8 + + enum { + controller_entry_action_flag_none_e = 0x0, + controller_entry_action_flag_undefined_e = 0x1, + }; typedef struct { uint8_t type; uint8_t code; + uint8_t flag; f_array_length_t line; f_number_unsigned_t number; @@ -69,6 +86,7 @@ extern "C" { 0, \ 0, \ 0, \ + controller_entry_action_flag_none_e, \ 0, \ F_known_not, \ f_string_dynamics_t_initialize, \ @@ -153,6 +171,18 @@ extern "C" { * Entry and Exit files are essentially the same structure with minor differences in settings and behavior. * The structure is identical and due to lacking any particularly good name to represent both "entry" or "exit", the name "entry" is being used for both. * + * controller_entry_flag_*: + * - none_e: No flags are set. + * - timeout_exit_no_e: The exit timeout is disabled. + * - timeout_kill_no_e: The kill timeout is disabled for Rules by default. + * - timeout_start_no_e: The start timeout is disabled for Rules by default. + * - timeout_stop_no_e: The stop timeout is disabled for Rules by default. + * + * controller_entry_session_*: + * - none: No special session configuration specified, use built in defaults. + * - new: Designate the default to use a new session, ignoring built in defaults (passing FL_execute_parameter_option_session_d to the execute functions). + * - same: Designate the default to use a same session, ignoring built in defaults. + * * controller_entry_pid_*: * - disable: Do not check for or create a PID file to represent the entry execution. * - require: Check to see if the PID file exists for an entry at startup and then when "ready" create a pid file, display error on pid file already exists or on failure and then fail. @@ -162,21 +192,18 @@ extern "C" { * - normal: Do not print anything other than warnings and errors, but allow executed programs and scripts to output however they like. * - init: Print like an init program, printing status of entry and rules as they are being started, stopped, etc... * - * controller_entry_session_*: - * - none: No special session configuration specified, use built in defaults. - * - new: Designate the default to use a new session, ignoring built in defaults (passing FL_execute_parameter_option_session_d to the execute functions). - * - same: Designate the default to use a same session, ignoring built in defaults. - * - * status: The overall status. + * define: Any defines (environment variables) made available to all Rules in this entry for IKI substitution or just as environment variables. + * flag: A set of flags, primarily used to designate that timeouts are disabled. + * items: The array of entry items. + * parameter: Any parameters made available to all Rules in this entry for IKI substitution. * pid: The PID file generation setting. * session: The default session settings (when NULL, no default is specified). * show: The show setting for controlling what to show when executing entry items and rules. + * status: The overall status. + * timeout_exit: The timeout to wait when exiting the Controller program after sending the terminate signal to send the kill signal. * timeout_kill: The timeout to wait relating to using a kill signal. * timeout_start: The timeout to wait relating to starting a process. * timeout_stop: The timeout to wait relating to stopping a process. - * define: Any defines (environment variables) made available to all Rules in this entry for IKI substitution or just as environment variables. - * parameter: Any parameters made available to all Rules in this entry for IKI substitution. - * items: The array of entry items. */ #ifndef _di_controller_entry_t_ enum { @@ -196,13 +223,23 @@ extern "C" { controller_entry_session_same_e, }; + enum { + controller_entry_flag_none_e = 0x0, + controller_entry_flag_timeout_exit_no_e = 0x1, + controller_entry_flag_timeout_kill_no_e = 0x2, + controller_entry_flag_timeout_start_no_e = 0x4, + controller_entry_flag_timeout_stop_no_e = 0x8, + }; + typedef struct { f_status_t status; uint8_t pid; uint8_t session; uint8_t show; + uint8_t flag; + f_number_unsigned_t timeout_exit; f_number_unsigned_t timeout_kill; f_number_unsigned_t timeout_start; f_number_unsigned_t timeout_stop; @@ -219,6 +256,8 @@ extern "C" { controller_entry_session_none_e, \ controller_entry_show_normal_e, \ 0, \ + controller_thread_exit_timeout_d, \ + 0, \ 0, \ 0, \ f_string_maps_t_initialize, \ diff --git a/level_3/controller/c/common/private-process.h b/level_3/controller/c/common/private-process.h index fa06e28..7ccdf64 100644 --- a/level_3/controller/c/common/private-process.h +++ b/level_3/controller/c/common/private-process.h @@ -54,20 +54,22 @@ extern "C" { * - control: The process is started from a control operation. * * id: The ID of this process relative to the processes array. - * result: The last return code from an execution of a process. - * status: The last execution status of the process. - * state: The state of the process. + * id_thread: The thread id, a valid ID when state is "active", and an invalid ID when the state is "busy". * action: The action being performed. - * options: Configuration options for this asynchronous thread. + * active: A read/write lock representing that something is currently using this (read locks = in use, write lock = begin deleting). + * 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). - * id_thread: The thread id, a valid ID when state is "active", and an invalid ID when the state is "busy". * lock: A read/write lock on the structure. - * active: A read/write lock representing that something is currently using this (read locks = in use, write lock = begin deleting). + * options: Configuration options for this asynchronous 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). + * state: The state of the process. + * status: The last execution status of the process. + * type: The currently active process type (from the controller_process_type_*_e). * wait: A thread condition to tell a process waiting process that the rule has is done being processed. * wait_lock: A mutex lock for working with "wait". - * cache: The cache used in this process. - * 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). - * rule: A copy of the rule actively being executed. + * * main_data: Used for passing the controller_main_t data to the process thread (to populate controller_global_t). * main_setting: Used for passing the controller_setting_t data to the process thread (to populate controller_global_t). * main_thread: Used for passing the controller_thread_t data to the process thread (to populate controller_global_t). diff --git a/level_3/controller/c/common/private-rule.h b/level_3/controller/c/common/private-rule.h index fc95573..7492262 100644 --- a/level_3/controller/c/common/private-rule.h +++ b/level_3/controller/c/common/private-rule.h @@ -305,6 +305,7 @@ extern "C" { * path: The path to the Rule file. * scheduler: The scheduler setting if the Rule "has" a scheduler. * status: A set of action-specific success/failure status of the Rule. Each index represents a controller_rule_action_type_* enum value. Index 0 represents a global status. + * timeout_exit: The timeout to wait when exiting the Controller program after sending the terminate signal to send the kill signal. * timeout_kill: The timeout to wait relating to using a kill signal. * timeout_start: The timeout to wait relating to starting a process. * timeout_stop: The timeout to wait relating to stopping a process. diff --git a/level_3/controller/c/common/private-thread.h b/level_3/controller/c/common/private-thread.h index 55d9aaf..6cb0fe2 100644 --- a/level_3/controller/c/common/private-thread.h +++ b/level_3/controller/c/common/private-thread.h @@ -34,6 +34,7 @@ extern "C" { #ifndef _di_controller_thread_t_ #define controller_thread_cleanup_interval_long_d 3600 // 1 hour in seconds. #define controller_thread_cleanup_interval_short_d 180 // 3 minutes in seconds. + #define controller_thread_exit_timeout_d 500 // 0.5 seconds in milliseconds. #define controller_thread_exit_process_cancel_wait_d 600000000 // 0.6 seconds in nanoseconds. #define controller_thread_exit_process_cancel_total_d 150 // 90 seconds in multiples of wait. #define controller_thread_simulation_timeout_d 200 // 0.2 seconds in milliseconds. diff --git a/level_3/controller/c/entry/private-entry.c b/level_3/controller/c/entry/private-entry.c index 65a6342..e46955c 100644 --- a/level_3/controller/c/entry/private-entry.c +++ b/level_3/controller/c/entry/private-entry.c @@ -245,7 +245,7 @@ extern "C" { } else if (action->type == controller_entry_action_type_timeout_e) { allocate = 2; - at_least = 2; + at_least = 1; at_most = 2; } else if (action->type == controller_entry_action_type_ready_e) { @@ -525,36 +525,45 @@ extern "C" { } if (action->status == F_none) { - status = fl_conversion_dynamic_to_unsigned_detect(fl_conversion_data_base_10_c, action->parameters.array[1], &action->number); + if (action->parameters.used == 2) { + if (action->flag & controller_entry_action_flag_undefined_e) { + action->flag -= controller_entry_action_flag_undefined_e; + } - if (F_status_is_error(status) || status == F_data_not) { - action->number = 0; + status = fl_conversion_dynamic_to_unsigned_detect(fl_conversion_data_base_10_c, action->parameters.array[1], &action->number); - if (status == F_data_not) { - action->status = F_status_set_error(F_number); - } - else { - action->status = controller_status_simplify_error(F_status_set_fine(status)); - } + if (F_status_is_error(status) || status == F_data_not) { + action->number = 0; - if (F_status_set_fine(status) == F_memory_not) { - controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_dynamic_to_unsigned_detect", F_true, global.thread); + if (status == F_data_not) { + action->status = F_status_set_error(F_number); + } + else { + action->status = controller_status_simplify_error(F_status_set_fine(status)); + } - status_action = status; + if (F_status_set_fine(status) == F_memory_not) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_dynamic_to_unsigned_detect", F_true, global.thread); - break; - } + status_action = status; - if (global.main->error.verbosity != f_console_verbosity_quiet_e) { - flockfile(global.main->error.to.stream); + break; + } - fl_print_format("%r%[%QThe %r item action parameter '%]", global.main->error.to.stream, f_string_eol_s, global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); - fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[1], global.main->error.notable); - fl_print_format("%[' is not a valid supported number.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s); + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); - funlockfile(global.main->error.to.stream); + fl_print_format("%r%[%QThe %r item action parameter '%]", global.main->error.to.stream, f_string_eol_s, global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, action->parameters.array[1], global.main->error.notable); + fl_print_format("%[' is not a valid supported number.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s); + + funlockfile(global.main->error.to.stream); + } } } + else { + action->flag |= controller_entry_action_flag_undefined_e; + } } } else if (action->type == controller_entry_action_type_ready_e) { @@ -1368,7 +1377,12 @@ extern "C" { return F_execute; } else if (entry_action->type == controller_entry_action_type_timeout_e) { - if (entry_action->code == controller_entry_timeout_code_kill_d) { + if (entry_action->code == controller_entry_timeout_code_exit_d) { + entry->timeout_exit = entry_action->number; + + controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_timeout_s, controller_exit_s, entry->items.array[global->setting->failsafe_item_id].name, controller_entry_print_suffix_megatime_s); + } + else if (entry_action->code == controller_entry_timeout_code_kill_d) { entry->timeout_kill = entry_action->number; controller_entry_preprocess_print_simulate_setting_value(*global, is_entry, controller_timeout_s, controller_kill_s, entry->items.array[global->setting->failsafe_item_id].name, controller_entry_print_suffix_megatime_s); @@ -2181,6 +2195,97 @@ extern "C" { continue; } } + else if (fl_string_dynamic_compare(controller_timeout_s, cache->action.name_action) == F_equal_to) { + if (cache->content_actions.array[i].used < 1 || cache->content_actions.array[i].used > 2) { + controller_entry_settings_read_print_setting_requires_between(global, is_entry, *cache, 1, 2); + + continue; + } + + f_number_unsigned_t *time = 0; + + if (fl_string_dynamic_partial_compare_string(controller_exit_s.string, cache->buffer_file, controller_exit_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { + if (cache->content_actions.array[i].used == 1) { + entry->flag |= controller_entry_flag_timeout_exit_no_e; + + continue; + } + + if (entry->flag & controller_entry_flag_timeout_exit_no_e) { + entry->flag -= controller_entry_flag_timeout_exit_no_e; + } + + time = &entry->timeout_exit; + } + else if (fl_string_dynamic_partial_compare_string(controller_kill_s.string, cache->buffer_file, controller_kill_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { + if (cache->content_actions.array[i].used == 1) { + entry->flag |= controller_entry_flag_timeout_kill_no_e; + + continue; + } + + if (entry->flag & controller_entry_flag_timeout_kill_no_e) { + entry->flag -= controller_entry_flag_timeout_kill_no_e; + } + + time = &entry->timeout_kill; + } + else if (fl_string_dynamic_partial_compare_string(controller_start_s.string, cache->buffer_file, controller_start_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { + if (cache->content_actions.array[i].used == 1) { + entry->flag |= controller_entry_flag_timeout_start_no_e; + + continue; + } + + if (entry->flag & controller_entry_flag_timeout_start_no_e) { + entry->flag -= controller_entry_flag_timeout_start_no_e; + } + + time = &entry->timeout_start; + } + else if (fl_string_dynamic_partial_compare_string(controller_stop_s.string, cache->buffer_file, controller_stop_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) { + if (cache->content_actions.array[i].used == 1) { + entry->flag |= controller_entry_flag_timeout_stop_no_e; + + continue; + } + + if (entry->flag & controller_entry_flag_timeout_stop_no_e) { + entry->flag -= controller_entry_flag_timeout_stop_no_e; + } + + time = &entry->timeout_stop; + } + else { + controller_entry_settings_read_print_setting_unknown_action_value(global, is_entry, *cache, i); + + continue; + } + + const f_number_unsigned_t time_previous = *time; + + status = fl_conversion_dynamic_partial_to_unsigned_detect(fl_conversion_data_base_10_c, cache->buffer_file, cache->content_actions.array[i].array[1], time); + + if (F_status_is_error(status) || status == F_data_not) { + *time = time_previous; + + if (F_status_set_fine(status) == F_memory_not) { + controller_entry_print_error(is_entry, global.main->error, cache->action, F_status_set_fine(status), "fl_conversion_dynamic_partial_to_unsigned_detect", F_true, global.thread); + + continue; + } + + if (global.main->error.verbosity != f_console_verbosity_quiet_e) { + flockfile(global.main->error.to.stream); + + fl_print_format("%r%[%QThe %r setting '%]", global.main->error.to.stream, f_string_eol_s, global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%/Q%]", global.main->error.to.stream, global.main->error.notable, cache->buffer_file, cache->content_actions.array[i].array[1], global.main->error.notable); + fl_print_format("%[' is not a valid supported number.%]", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s); + + funlockfile(global.main->error.to.stream); + } + } + } else { if (global.main->warning.verbosity == f_console_verbosity_debug_e) { controller_entry_settings_read_print_setting_unknown_action(global, is_entry, *cache); diff --git a/level_3/controller/c/entry/private-entry_print.c b/level_3/controller/c/entry/private-entry_print.c index 4b606b4..35bbee9 100644 --- a/level_3/controller/c/entry/private-entry_print.c +++ b/level_3/controller/c/entry/private-entry_print.c @@ -160,6 +160,27 @@ extern "C" { } #endif // _di_controller_entry_settings_read_print_setting_ignored_ +#ifndef _di_controller_entry_settings_read_print_setting_requires_between_ + void controller_entry_settings_read_print_setting_requires_between(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_number_unsigned_t minimum, const f_number_unsigned_t maximum) { + + if (global.main->error.verbosity == f_console_verbosity_quiet_e) return; + + controller_lock_print(global.main->error.to, global.thread); + + fl_print_format("%r%[%QThe %Q item setting '%]", global.main->error.to.stream, f_string_eol_s, global.main->error.context, global.main->error.prefix, is_entry ? controller_entry_s : controller_exit_s, global.main->error.context); + fl_print_format("%[%Q%]", global.main->error.to.stream, global.main->error.notable, cache.action.name_action, global.main->error.notable); + fl_print_format("%[' requires at least %]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%un%]", global.main->error.to.stream, global.main->error.notable, minimum, global.main->error.notable); + fl_print_format("%[' and at most %]", global.main->error.to.stream, global.main->error.context, global.main->error.context); + fl_print_format("%[%un%]", global.main->error.to.stream, global.main->error.notable, maximum, global.main->error.notable); + fl_print_format("%[ Content.%]%r", global.main->error.to.stream, global.main->error.context, global.main->error.context, f_string_eol_s); + + controller_entry_print_error_cache(is_entry, global.main->error, cache.action); + + controller_unlock_print_flush(global.main->error.to, global.thread); + } +#endif // _di_controller_entry_settings_read_print_setting_requires_between_ + #ifndef _di_controller_entry_settings_read_print_setting_requires_exactly_ void controller_entry_settings_read_print_setting_requires_exactly(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_number_unsigned_t total) { diff --git a/level_3/controller/c/entry/private-entry_print.h b/level_3/controller/c/entry/private-entry_print.h index 7e2fd4f..f723a9c 100644 --- a/level_3/controller/c/entry/private-entry_print.h +++ b/level_3/controller/c/entry/private-entry_print.h @@ -196,7 +196,26 @@ extern "C" { #endif // _di_controller_entry_settings_read_print_setting_ignored_ /** - * Print a message for when an entry setting action has the incorrect number of parameters. + * Print a message for when an entry setting action has the incorrect number of parameters when the required amount is between a range. + * + * @param global + * The global data. + * @param is_entry + * If TRUE, then this loads as an entry. + * If FALSE, then this loads as an exit. + * @param cache + * A structure for containing and caching relevant data. + * @param minimum + * The expected minimum number of arguments. + * @param maximum + * The expected maximum number of arguments. + */ +#ifndef _di_controller_entry_settings_read_print_setting_requires_between_ + extern void controller_entry_settings_read_print_setting_requires_between(const controller_global_t global, const bool is_entry, const controller_cache_t cache, const f_number_unsigned_t minimum, const f_number_unsigned_t maximum) F_attribute_visibility_internal_d; +#endif // _di_controller_entry_settings_read_print_setting_requires_between_ + +/** + * Print a message for when an entry setting action has the incorrect number of parameters when the required amount is fixed. * * @param global * The global data. diff --git a/level_3/controller/c/thread/private-thread_process.c b/level_3/controller/c/thread/private-thread_process.c index 57e046f..dfed74b 100644 --- a/level_3/controller/c/thread/private-thread_process.c +++ b/level_3/controller/c/thread/private-thread_process.c @@ -77,12 +77,24 @@ extern "C" { struct timespec time; + controller_entry_t *entry = 0; controller_process_t *process = 0; f_array_length_t i = 0; f_array_length_t j = 0; pid_t pid = 0; + if (is_normal) { + entry = &global.setting->entry; + } + else { + entry = &global.setting->exit; + } + + // A simple but inaccurate interval counter (expect this to be replaced in the future). + const f_number_unsigned_t interval_nanoseconds = entry->timeout_exit < 1000 ? (entry->timeout_exit < 100 ? 5000000 : 100000000) : 500000000; + const f_number_unsigned_t interval_milliseconds = entry->timeout_exit < 1000 ? (entry->timeout_exit < 100 ? 5 : 100) : 500; + if (global.thread->id_cleanup) { f_thread_cancel(global.thread->id_cleanup); f_thread_join(global.thread->id_cleanup, 0); @@ -136,69 +148,70 @@ extern "C" { } // for } // for - for (i = 0; i < global.thread->processs.size && spent < controller_thread_exit_process_cancel_total_d; ++i) { - - if (!global.thread->processs.array[i]) continue; - if (caller && i == caller->id) continue; + if (entry->timeout_exit && !(entry->flag & controller_entry_flag_timeout_exit_no_e)) { + f_number_unsigned_t lapsed = 0; - process = global.thread->processs.array[i]; + for (i = 0; i < global.thread->processs.used && lapsed < entry->timeout_exit; ++i) { - // Do not cancel exit processes, when not performing "execute" during exit. - if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) continue; + if (!global.thread->processs.array[i]) continue; + if (caller && i == caller->id) continue; - do { - if (!process->id_thread) break; + process = global.thread->processs.array[i]; - f_thread_signal_write(process->id_thread, global.thread->signal ? global.thread->signal : F_signal_termination); + // Do not wait for processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) { + continue; + } - controller_time(0, controller_thread_exit_process_cancel_wait_d, &time); + for (j = 0; j < process->childs.used && lapsed < entry->timeout_exit; ++j) { - status = f_thread_join_timed(process->id_thread, time, 0); + while (process->childs.array[j] > 0 && lapsed < entry->timeout_exit) { - if (status == F_none) { - for (j = 0; j < process->childs.size; ++j) { - process->childs.array[j] = 0; - } // for - - process->childs.used = 0; - process->id_thread = 0; - } + // A hackish way to determine if the child process exists while waiting. + if (getpgid(process->childs.array[j]) >= 0) { + time.tv_sec = 0; + time.tv_nsec = interval_nanoseconds; - ++spent; + nanosleep(&time, 0); - } while (status == F_time && spent < controller_thread_exit_process_cancel_total_d); + lapsed += interval_milliseconds; + } + else { + process->childs.array[j] = 0; - if (process->path_pids.used) { - for (j = 0; j < process->path_pids.used; ++j) { + break; + } + } // while + } // for - for (; spent < controller_thread_exit_process_cancel_total_d; ++spent) { + for (j = 0; j < process->path_pids.used && lapsed < entry->timeout_exit; ++j) { - if (process->path_pids.array[j].used && f_file_exists(process->path_pids.array[j], F_true) == F_true) { - status = controller_file_pid_read(process->path_pids.array[j], &pid); + if (process->path_pids.array[j].used && f_file_exists(process->path_pids.array[j], F_true) == F_true) { + status = controller_file_pid_read(process->path_pids.array[j], &pid); - if (pid) { + if (pid) { + while (lapsed < entry->timeout_exit) { - // A hackish way to determine if the pid exists while waiting. + // A hackish way to determine if the process exists while waiting. if (getpgid(pid) >= 0) { time.tv_sec = 0; - time.tv_nsec = controller_thread_exit_process_cancel_wait_d; + time.tv_nsec = interval_nanoseconds; nanosleep(&time, 0); - continue; + lapsed += interval_milliseconds; } else { - f_file_remove(process->path_pids.array[j]); process->path_pids.array[j].used = 0; + + break; } - } + } // while } - - break; - } // for + } } // for - } - } // for + } // for + } for (i = 0; i < global.thread->processs.size; ++i) { @@ -232,36 +245,62 @@ extern "C" { process->id_thread = 0; } - for (j = 0; j < process->childs.size; ++j) { + if (!(entry->flag & controller_entry_flag_timeout_exit_no_e)) { + for (j = 0; j < process->childs.size; ++j) { - if (process->childs.array[j]) { + // Do not kill exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) continue; - // A hackish way to determine if the child process exists, and if it does then forcibly terminate it. - if (getpgid(process->childs.array[j]) >= 0) { - f_signal_send(F_signal_kill, process->childs.array[j]); + if (process->childs.array[j]) { + + // A hackish way to determine if the child process exists, and if it does then forcibly terminate it. + if (getpgid(process->childs.array[j]) >= 0) { + f_signal_send(F_signal_kill, process->childs.array[j]); + } + + process->childs.array[j] = 0; } + } // for + } - process->childs.array[j] = 0; - } - } // for + if (!(entry->flag & controller_entry_flag_timeout_exit_no_e)) { + for (j = 0; j < process->path_pids.used; ++j) { - process->childs.used = 0; + // Do not kill exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) continue; - for (j = 0; j < process->path_pids.used; ++j) { + if (f_file_exists(process->path_pids.array[j], F_true) == F_true) { + status = controller_file_pid_read(process->path_pids.array[j], &pid); - if (f_file_exists(process->path_pids.array[j], F_true) == F_true) { - status = controller_file_pid_read(process->path_pids.array[j], &pid); + if (pid) { + f_signal_send(F_signal_kill, pid); + } - if (pid) { - f_signal_send(F_signal_kill, pid); + f_file_remove(process->path_pids.array[j]); + process->path_pids.array[j].used = 0; } + } // for + } - f_file_remove(process->path_pids.array[j]); - process->path_pids.array[j].used = 0; - } - } // for + // Shrink the child pids as much as possible. + while (process->childs.used) { + + // Do not shrink below an exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) break; + if (process->childs.array[j] > 0) break; + + --process->childs.used; + } // while + + // Shrink the path pids as much as possible. + while (process->path_pids.used) { + + // Do not shrink below an exit processes, when not performing "execute" during exit. + if (process->type == controller_process_type_exit_e && global.thread->enabled != controller_thread_enabled_exit_execute_e) break; + if (process->path_pids.array[j].used) break; - process->path_pids.used = 0; + --process->path_pids.used; + } // while } // for } #endif // _di_controller_thread_process_cancel_ diff --git a/level_3/controller/documents/entry.txt b/level_3/controller/documents/entry.txt index 7ae86aa..62d74a3 100644 --- a/level_3/controller/documents/entry.txt +++ b/level_3/controller/documents/entry.txt @@ -98,6 +98,10 @@ Entry Documentation: For "normal", will not report the start or stop of some Entry or Rule execution but will report any errors or warnings as appropriate. For "init", will report when starting programs and may include reporting success and failure status. + - The "timeout" setting\: + Represents the default timeouts for the Entry. + See the "timeout" Action below for details. + - The "item" item Object\: Each "item" supports the following Action Names: "consider", "execute", "failsafe", "freeze", "item", "kill", "pause", "reload", "restart", "ready", "resume", "start", "stop", and "timeout". Of those types, the following are considered a "rule" Action: "freeze", "kill", "pause", "reload", "restart", "resume", "start", "stop", and "thaw". @@ -174,14 +178,21 @@ Entry Documentation: Once implemented this documentation will need to be updated and clarified. - The "timeout" Item Action\: - Provides default global settings for each of the three special situations: "kill", "start", and "stop". - Each of these may only have a single one exist at a time (one "kill", one "start", one "stop", and one "wait"). + (This is not currently fully implemented, only "exit" is implemented.) + Provides default global settings for each of the four special situations: "exit", "kill", "start", and "stop". + Each of these may only have a single one exist at a time (one "exit", one "kill", one "start", and one "stop"). Each successive "timeout" Item Action, specific to each Action Name (such as "start"), specified replaces the previously defined "timeout" Action (in a top-down manner). - Each of these accepts a single Action Parameter that is a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). + The second Content for each of these, when specified, may be a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). For "kill", this represents the number of MegaTime to wait after stopping some Rule and that Rule has not yet stopped to forcefully stop the Rule (aka kill the Rule). For "start", this represents the number of MegaTime to wait after starting some Rule before assuming something went wrong and the Rule is returned as failed. For "stop", this represents the number of MegaTime to wait after stopping some Rule before assuming something went wrong and the Rule is returned as failed. - A value of 0 disables this (prevents the timeout action). + If the second Content is not specified, then this disables the type (prevents the specified timeout action). + + For "exit", this represents the number of MegaTime to wait when the Controller program is exiting (such as having received a terminate signal). + In this case, a terminate signal is sent to all child processes. + The "exit" timeout represents the amount of time to wait after sending the terminate signal before sending a kill signal to each child process still running. + When disabled, the program will not send a kill signal will continue running until all child processes to terminate. + The "exit" timeout does not get applied to any Rule. Entry Rule Documentation: There are multiple Entry Actions that are considered "rule" Actions. diff --git a/level_3/controller/documents/exit.txt b/level_3/controller/documents/exit.txt index ad1ad87..3dd2de1 100644 --- a/level_3/controller/documents/exit.txt +++ b/level_3/controller/documents/exit.txt @@ -47,6 +47,10 @@ Exit Documentation: For "normal", will not report the start or stop of some Exit or Rule execution but will report any errors or warnings as appropriate. For "init", will report when starting programs and may include reporting success and failure status. + - The "timeout" setting\: + Represents the default timeouts for the Exit. + See the "timeout" Action below for details. + - The "main" Item Object\: Is always executed first (Therefore "main" is both reserved and required). All other Basic List Objects are not executed unless either an "item" or a "failsafe" specifies a valid Item name. @@ -127,15 +131,21 @@ Exit Documentation: Once implemented this documentation will need to be updated and clarified. - The "timeout" Item Action\: - Provides default global settings for each of the three special situations: "start", "stop", and "kill". - Each of these may only have a single one exist at a time (one "start", one "stop", and one "kill"). + (This is not currently fully implemented, only "exit" is implemented.) + Provides default global settings for each of the four special situations: "exit", "kill", "start", and "stop". + Each of these may only have a single one exist at a time (one "exit", one "kill", one "start", and one "stop"). Each successive "timeout" Item Action, specific to each Action Name (such as "start"), specified replaces the previously defined "timeout" Action (in a top-down manner). - Each of these accepts a single Action Parameter that is a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). + The second Content for each of these, when specified, may be a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). + For "kill", this represents the number of MegaTime to wait after stopping some Rule and that Rule has not yet stopped to forcefully stop the Rule (aka kill the Rule). For "start", this represents the number of MegaTime to wait after starting some Rule before assuming something went wrong and the Rule is returned as failed. For "stop", this represents the number of MegaTime to wait after stopping some Rule before assuming something went wrong and the Rule is returned as failed. - For "kill", this represents the number of MegaTime to wait after stopping some Rule and that Rule has not yet stopped to forcefully stop the Rule (aka kill the Rule). - The timeouts are generally only valid for services such as daemon services. - A value of 0 disables this (prevents any action). + If the second Content is not specified, then this disables the type (prevents the specified timeout action). + + For "exit", this represents the number of MegaTime to wait when the Controller program is exiting (such as having received a terminate signal). + In this case, a terminate signal is sent to all child processes. + The "exit" timeout represents the amount of time to wait after sending the terminate signal before sending a kill signal to each child process still running. + When disabled, the program will not send a kill signal will continue running until all child processes to terminate. + The "exit" timeout does not get applied to any Rule. Exit Rule Documentation: There are multiple Exit Actions that are considered "rule" Actions. diff --git a/level_3/controller/documents/rule.txt b/level_3/controller/documents/rule.txt index 58d2d3e..bf0c50e 100644 --- a/level_3/controller/documents/rule.txt +++ b/level_3/controller/documents/rule.txt @@ -88,14 +88,15 @@ Rule Documentation: Supported real-time schedulers are: "deadline", "fifo", "round_robin". - The "timeout" setting\: - The "timeout" Item Action provides default global settings for each of the three special situations: "kill", "start", and "stop". - Each of these may only have a single one exist at a time (one "kill", one "start", one "stop", and one "wait"). + (This is not currently implemented.) + Provides settings for each of the three special situations: "kill", "start", and "stop". + Each of these may only have a single one exist at a time (one "kill", one "start", and one "stop"). Each successive "timeout" Item Action, specific to each Action Name (such as "start"), specified replaces the previously defined "timeout" Action (in a top-down manner). - Each of these accepts a single Action Parameter that is a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). - For "kill", this represents the number of MegaTime to wait after stopping some rule and that rule has not yet stopped to forcefully stop the rule (aka kill the rule). - For "start", this represents the number of MegaTime to wait after starting some rule before assuming something went wrong and the rule is returned as failed. - For "stop", this represents the number of MegaTime to wait after stopping some rule before assuming something went wrong and the rule is returned as failed. - A value of 0 disables this (prevents the timeout action). + The second Content for each of these, when specified, may be a 0 or greater whole number representing the number of MegaTime (MT) (equivalent to milliseconds). + For "kill", this represents the number of MegaTime to wait after stopping some Rule and that Rule has not yet stopped to forcefully stop the Rule (aka kill the Rule). + For "start", this represents the number of MegaTime to wait after starting some Rule before assuming something went wrong and the Rule is returned as failed. + For "stop", this represents the number of MegaTime to wait after stopping some Rule before assuming something went wrong and the Rule is returned as failed. + If the second Content is not specified, then this disables the type (prevents the specified timeout action). There are four available Rule Types to choose from: "command", "service", "script", and "utility". diff --git a/level_3/controller/specifications/entry.txt b/level_3/controller/specifications/entry.txt index b45dc8a..c0f88ea 100644 --- a/level_3/controller/specifications/entry.txt +++ b/level_3/controller/specifications/entry.txt @@ -38,6 +38,7 @@ Entry Specification: - "pid_file": Exactly one Content that is a relative or absolute path to a pid file. - "session": Exactly one Content that is one of "new" or "same". - "show": Exactly one Content that is one of "normal" or "init". + - "timeout": One or Two content with the first being one of "exit", "start", "stop", or "kill" and the (optional) second Content being a positive whole number or 0. The Entry file may have any other valid Item Objects, but only the above are reserved. @@ -134,9 +135,10 @@ Entry Specification: - "require" - "wait" - - "timeout": Two Content. + - "timeout": One or Two Content. The first being one of\: + - "exit" - "start" - "stop" - "kill" - The second being a positive whole number or 0. + The (optional) second Content being a positive whole number or 0. diff --git a/level_3/controller/specifications/exit.txt b/level_3/controller/specifications/exit.txt index 603ac6d..cefae44 100644 --- a/level_3/controller/specifications/exit.txt +++ b/level_3/controller/specifications/exit.txt @@ -23,6 +23,7 @@ Exit Specification: - "pid": Exactly one Content that is one of "disable", "require", or "ready". - "session": Exactly one Content that is one of "new" or "same". - "show": Exactly one Content that is one of "normal" or "init". + - "timeout": One or Two content with the first being one of "exit", "start", "stop", or "kill" and the (optional) second Content being a positive whole number or 0. The Exit file may have any other valid Item Objects, but only the above are reserved. @@ -117,7 +118,8 @@ Exit Specification: - "timeout": Two Content. The first being one of\: + - "exit" - "start" - "stop" - "kill" - The second being a positive whole number or 0. + The (optional) second Content being a positive whole number or 0. diff --git a/level_3/controller/specifications/rule.txt b/level_3/controller/specifications/rule.txt index a4b8361..bed9b29 100644 --- a/level_3/controller/specifications/rule.txt +++ b/level_3/controller/specifications/rule.txt @@ -46,6 +46,7 @@ Rule Specification: - "parameter": Two Content, the first Content must be a case-sensitive valid IKI name and the second being an IKI value. - "path": One Content representing a valid PATH environment string (such as "/bin:/sbin:/usr/bin"). - "scheduler": One or Two Content representing a scheduler name and the optional numeric priority (Any whole number inclusively between 0 and 99). + - "timeout": One or Two content with the first being one of "exit", "start", "stop", or "kill" and the (optional) second Content being a positive whole number or 0. - "user": One Content representing a user name or user id. The "command" and "script" Rule Types allow the following the FSS-0001 (Extended)\: