From 2b6a19c0707763276d7a4cfbba513c53fee06f68 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Wed, 14 Apr 2021 12:08:27 -0500 Subject: [PATCH] Update: handle asynchronous failures for failsafe, update locks, and related fixes. The failsafe needs to be triggered when a required but asynchronous process fails. I originally planned on implementing this via locks but I would rather avoid adding even more locks. This approach instead provides a wait loop at the end of the entry waiting only on all required processes. If any of these fail, then the wait will return the requirement failure. This is a change to the wait all function and behavior, which is updating to now return statuses. Get rid of process->status, it is no longer needed now that process->rule.status exists. Having it remain is wasteful and confusing. The entry processing is updated to be failsafe aware. This now potentially operates the failsafe rule item. The failsafe must not do a wait all like normal operation inside of the entry processing function. There are some discrepancies between "process options" and "rule options". Technically, there are no "options" on a rule as this is a concept introduced for/by entries. The process uses these options but most of them were named "rule options". I then later created a "process options" to add override so that even if the rule is requested asynchronous, if a dependant thread is requiring it, then it will instead run synchronously in the thread of the depending process. This resulted in both "process options" and "rule options". They really are the same, so instead remove the current "process options" and rename all "rule options" into "process options". There are now no longer any "rule options". I then renamed the "process options" variables to "force options". Remove signal_all from delete process. The signals should now all be timed and will exit when thread disabled is set. The execution used to be designed with the intent that is where the asynchronous processing would handle. After multiple iterations in design, this is no longer the case. Update the code to always pass parameter option fl_execute_parameter_option_return instead of doing it only for asynchronous processes. Add missing re-locks. Some of the functions require that a certain (read) lock be held prior to calling the function. When the function returns, the expected lock should still be held. It so happens that this is not consistently the case. Certain error or exit states are returning without re-establishing the expected (read) locks. The controller_rule_find() returns a boolean and not a status with potential error bits. Fix a block of code where it is checking for error bit on the return value of this function. The controller_rule_read() also returns a boolean and is being handled as a status with a bit. In this case, change it to return a status given that this make more sense in this particular case. Remove relevant/related stale code and add missing comments. --- level_3/controller/c/private-common.c | 4 +- level_3/controller/c/private-common.h | 17 +- level_3/controller/c/private-controller.c | 48 ++-- level_3/controller/c/private-rule.c | 354 +++++++++++++++++------------- level_3/controller/c/private-rule.h | 50 +++-- level_3/controller/c/private-thread.c | 14 +- level_3/controller/documents/entry.txt | 1 + 7 files changed, 269 insertions(+), 219 deletions(-) diff --git a/level_3/controller/c/private-common.c b/level_3/controller/c/private-common.c index b7aa086..870a564 100644 --- a/level_3/controller/c/private-common.c +++ b/level_3/controller/c/private-common.c @@ -220,7 +220,6 @@ extern "C" { process->id_thread = 0; } - f_thread_condition_signal_all(&process->wait); f_thread_condition_delete(&process->wait); controller_lock_delete_rw(&process->lock); @@ -273,7 +272,7 @@ extern "C" { f_thread_lock_read(&process->lock); - if (process->status != F_known_not || !(process->state == controller_process_state_active || process->state == controller_process_state_busy)) { + if (process->rule.status != F_known_not || !(process->state == controller_process_state_active || process->state == controller_process_state_busy)) { f_thread_unlock(&process->lock); return F_none; @@ -367,7 +366,6 @@ extern "C" { return status; } else { - process->status = F_known_not; process->rule.status = F_known_not; } } // for diff --git a/level_3/controller/c/private-common.h b/level_3/controller/c/private-common.h index f90a0ef..b796935 100644 --- a/level_3/controller/c/private-common.h +++ b/level_3/controller/c/private-common.h @@ -635,12 +635,6 @@ extern "C" { controller_rule_setting_type_wish, }; - #define controller_rule_option_asynchronous 0x1 - #define controller_rule_option_require 0x2 - #define controller_rule_option_simulate 0x4 - #define controller_rule_option_validate 0x8 - #define controller_rule_option_wait 0x10 - // bitwise codes representing properties on controller_rule_t that have been found in the rule file. #define controller_rule_has_control_group 0x1 #define controller_rule_has_group 0x2 @@ -771,7 +765,10 @@ extern "C" { */ #ifndef _di_controller_process_t_ #define controller_process_option_asynchronous 0x1 - #define controller_process_option_execute 0x2 + #define controller_process_option_require 0x2 + #define controller_process_option_simulate 0x4 + #define controller_process_option_validate 0x8 + #define controller_process_option_wait 0x10 enum { controller_process_state_idle = 1, @@ -783,8 +780,6 @@ extern "C" { typedef struct { f_array_length_t id; - f_status_t status; - uint8_t state; uint8_t action; uint8_t options; @@ -808,7 +803,6 @@ extern "C" { #define controller_process_t_initialize { \ 0, \ - F_known_not, \ 0, \ 0, \ 0, \ @@ -1093,7 +1087,6 @@ extern "C" { typedef struct { bool enabled; - bool failed; int signal; f_status_t status; @@ -1109,7 +1102,6 @@ extern "C" { #define controller_thread_t_initialize { \ F_true, \ - F_false, \ 0, \ F_none, \ f_thread_id_t_initialize, \ @@ -1366,6 +1358,7 @@ extern "C" { * * @return * F_none on success. + * F_signal on (exit) signal received, lock will not be set when this is returned. * F_status if main thread is disabled and write lock was never achieved. * * Status from: f_thread_lock_write_timed(). diff --git a/level_3/controller/c/private-controller.c b/level_3/controller/c/private-controller.c index e9d4fca..abf6e80 100644 --- a/level_3/controller/c/private-controller.c +++ b/level_3/controller/c/private-controller.c @@ -623,12 +623,11 @@ extern "C" { f_array_length_t i = 0; f_array_length_t j = 0; - f_array_length_t at = 0; f_array_length_t at_i = 0; f_array_length_t at_j = 1; - uint8_t rule_options = 0; - uint8_t process_options = 0; + uint8_t options_force = 0; + uint8_t options_process = 0; controller_entry_action_t *entry_action = 0; controller_entry_actions_t *entry_actions = 0; @@ -865,8 +864,7 @@ extern "C" { } else if (entry_action->type == controller_entry_action_type_item) { - // @todo also prevent failsafe item from being recursively called, when failsafe == F_true. - if (entry_action->number == 0 || entry_action->number >= main->setting->entry.items.used) { + if (entry_action->number == 0 || entry_action->number >= main->setting->entry.items.used || failsafe && entry_action->number == main->setting->failsafe_item_id) { // This should not happen if the pre-process is working as designed, but in case it doesn't, return a critical error to prevent infinite recursion and similar errors. if (main->data->error.verbosity != f_console_verbosity_quiet) { @@ -968,7 +966,7 @@ extern "C" { f_thread_lock_read(&main->thread->lock.rule); - status = controller_rule_find(alias_rule, main->setting->rules, &at); + status = controller_rule_find(alias_rule, main->setting->rules, 0); f_thread_unlock(&main->thread->lock.rule); @@ -1069,7 +1067,6 @@ extern "C" { f_thread_lock_read(&main->thread->lock.process); if (controller_find_process(alias_rule, main->thread->processs, 0) == F_false) { - f_thread_unlock(&main->thread->lock.process); status = controller_lock_write(main->thread, &main->thread->lock.process); @@ -1136,37 +1133,33 @@ extern "C" { } if (F_status_is_error_not(status)) { - process_options = 0; - rule_options = 0; - - if (entry_action->type == controller_entry_action_type_rule) { - process_options |= controller_process_option_execute; - } + options_force = 0; + options_process = 0; if (simulate) { - rule_options |= controller_rule_option_simulate; + options_process |= controller_process_option_simulate; } if (entry_action->code & controller_entry_rule_code_require) { - rule_options |= controller_rule_option_require; + options_process |= controller_process_option_require; } if (entry_action->code & controller_entry_rule_code_wait) { - rule_options |= controller_rule_option_wait; + options_process |= controller_process_option_wait; } if (main->data->parameters[controller_parameter_validate].result == f_console_result_found) { - rule_options |= controller_rule_option_validate; + options_process |= controller_process_option_validate; } if (entry_action->code & controller_entry_rule_code_asynchronous) { if (main->data->parameters[controller_parameter_validate].result != f_console_result_found) { - process_options |= controller_process_option_asynchronous; - rule_options |= controller_rule_option_asynchronous; + options_force |= controller_process_option_asynchronous; + options_process |= controller_process_option_asynchronous; } } - status = controller_rule_process_begin(process_options, alias_rule, controller_rule_action_type_start, rule_options, stack, *main, *cache); + status = controller_rule_process_begin(options_force, alias_rule, controller_rule_action_type_start, options_process, stack, *main, *cache); if (F_status_set_fine(status) == F_memory_not || status == F_child || status == F_signal || !main->thread->enabled) { break; @@ -1322,9 +1315,18 @@ extern "C" { return status; } - // @todo wait for all asynchronous processes to complete. - // then, check to see if any "required" rule failed. - // if failed, then return F_status_set_error(F_require). + // check to see if any requied processes failed, but do not do this if already operating in failsafe. + if (F_status_is_error_not(status) && !failsafe) { + const f_status_t status_wait = controller_rule_wait_all(*main, F_true, 0); + + if (status_wait == F_signal) { + return F_signal; + } + + if (F_status_set_fine(status_wait) == F_require) { + return F_status_set_error(F_require); + } + } if (simulate) { if (main->data->error.verbosity != f_console_verbosity_quiet) { diff --git a/level_3/controller/c/private-rule.c b/level_3/controller/c/private-rule.c index 70e6bb2..70ed1e6 100644 --- a/level_3/controller/c/private-rule.c +++ b/level_3/controller/c/private-rule.c @@ -776,7 +776,7 @@ extern "C" { execute_set.as.control_group = &process->rule.control_group; // make sure all required cgroup directories exist. - if (process->status == F_known_not) { + if (process->rule.status == F_known_not) { status = fll_control_group_prepare(process->rule.control_group); if (F_status_is_error(status)) { @@ -833,11 +833,7 @@ extern "C" { if (process->rule.items.array[i].actions.array[j].type != action) continue; execute_set.parameter.data = 0; - execute_set.parameter.option = fl_execute_parameter_option_threadsafe; - - if (options & controller_rule_option_asynchronous) { - execute_set.parameter.option |= fl_execute_parameter_option_return; - } + execute_set.parameter.option = fl_execute_parameter_option_threadsafe | fl_execute_parameter_option_return; if (process->rule.items.array[i].type == controller_rule_item_type_command) { @@ -852,7 +848,7 @@ extern "C" { if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); - if (!(options & controller_rule_option_simulate)) break; + if (!(options & controller_process_option_simulate)) break; success = F_failure; } @@ -875,7 +871,7 @@ extern "C" { if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); - if (!(options & controller_rule_option_simulate)) break; + if (!(options & controller_process_option_simulate)) break; success = F_status_set_error(F_failure); } @@ -896,7 +892,7 @@ extern "C" { if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); - if (!(options & controller_rule_option_simulate)) break; + if (!(options & controller_process_option_simulate)) break; success = F_failure; } @@ -924,7 +920,7 @@ extern "C" { } } // for - if (status == F_child || status == F_signal || F_status_is_error(status) && !(options & controller_rule_option_simulate)){ + if (status == F_child || status == F_signal || F_status_is_error(status) && !(options & controller_process_option_simulate)) { break; } } // for @@ -960,7 +956,7 @@ extern "C" { int result = 0; pid_t id_child = 0; - if (options & controller_rule_option_simulate) { + if (options & controller_process_option_simulate) { if (main.data->error.verbosity != f_console_verbosity_quiet) { f_thread_mutex_lock(&main.thread->lock.print); @@ -1001,11 +997,14 @@ extern "C" { status_lock = controller_lock_write(main.thread, &process->lock); if (status_lock == F_signal) { + f_thread_lock_read(&process->lock); + return status_lock; } if (!main.thread->enabled) { f_thread_unlock(&process->lock); + f_thread_lock_read(&process->lock); return F_signal; } @@ -1019,12 +1018,12 @@ extern "C" { // have the parent wait for the child process to finish. waitpid(id_child, &result, 0); - f_thread_unlock(&process->lock); - if (!main.thread->enabled) { return F_signal; } + f_thread_unlock(&process->lock); + status_lock = controller_lock_write(main.thread, &process->lock); if (status_lock == F_signal) { @@ -1033,6 +1032,7 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); + f_thread_lock_read(&process->lock); return F_signal; } @@ -1113,7 +1113,7 @@ extern "C" { // otherwise this needs to call an asynchronous execute process. // until then, this controller_rule_execute_pid_with() function is not correct and only represents a process that forks to the background. - if (options & controller_rule_option_simulate) { + if (options & controller_process_option_simulate) { if (main.data->error.verbosity != f_console_verbosity_quiet) { @@ -1683,7 +1683,7 @@ extern "C" { return status; } - if ((process->options & controller_rule_option_simulate) && main.data->parameters[controller_parameter_validate].result == f_console_result_found) { + if ((process->options & controller_process_option_simulate) && main.data->parameters[controller_parameter_validate].result == f_console_result_found) { controller_rule_validate(process->rule, controller_rule_action_type_start, process->options, main, &process->cache); } @@ -1700,7 +1700,7 @@ extern "C" { controller_process_t *process_other = 0; - uint8_t rule_options = 0; + uint8_t options_process = 0; f_string_dynamics_t * const dynamics[] = { &process->rule.need, @@ -1716,9 +1716,9 @@ extern "C" { // i==0 is need, i==1 is want, i==2 is wish. // loop through all dependencies: wait for depedency, execute dependency, fail due to missing required dependency, or skip unrequired missing dependencies. - for (i = 0; i < 3; ++i) { + for (i = 0; i < 3 && main.thread->enabled; ++i) { - for (j = 0; j < dynamics[i]->used; ++j) { + for (j = 0; j < dynamics[i]->used && main.thread->enabled; ++j) { process_other = 0; found = F_false; @@ -1728,29 +1728,26 @@ extern "C" { status = controller_find_process(dynamics[i]->array[j], main.thread->processs, &id_process); if (status == F_true) { + found = F_true; + process_other = main.thread->processs.array[id_process]; f_thread_lock_read(&process_other->active); - } - - f_thread_unlock(&main.thread->lock.process); - - if (status == F_true) { + f_thread_unlock(&main.thread->lock.process); f_thread_lock_read(&main.thread->lock.rule); status = controller_rule_find(dynamics[i]->array[j], main.setting->rules, &id_rule); f_thread_unlock(&main.thread->lock.rule); - - found = F_true; + } + else { + f_thread_unlock(&main.thread->lock.process); } if (status != F_true) { found = F_false; id_rule = 0; - f_thread_lock_read(&main.thread->lock.rule); - if (i == 0) { f_thread_mutex_lock(&main.thread->lock.print); @@ -1761,9 +1758,7 @@ extern "C" { status = F_status_set_error(F_found_not); - if (!(process->options & controller_rule_option_simulate)) { - f_thread_unlock(&main.thread->lock.rule); - + if (!(process->options & controller_process_option_simulate)) { if (process_other) { f_thread_unlock(&process_other->active); } @@ -1782,11 +1777,8 @@ extern "C" { } } } - else { + else if (found) { f_thread_lock_read(&main.thread->lock.rule); - } - - if (found) { // the process_other may have write locks, which needs to be avoided, so copy the alias from the rule. char alias_other_buffer[main.setting->rules.array[id_rule].alias.used + 1]; @@ -1810,6 +1802,8 @@ extern "C" { f_thread_unlock(&process_other->lock); controller_process_wait(main, process_other); + + status = process_other->rule.status; } else { f_thread_lock_read(&main.thread->lock.rule); @@ -1818,18 +1812,18 @@ extern "C" { f_thread_unlock(&main.thread->lock.rule); f_thread_unlock(&process_other->lock); - rule_options = controller_process_option_execute; + options_process = 0; if (main.data->parameters[controller_parameter_test].result == f_console_result_found) { - rule_options |= controller_rule_option_simulate; + options_process |= controller_process_option_simulate; } if (main.data->parameters[controller_parameter_validate].result == f_console_result_found) { - rule_options |= controller_rule_option_validate; + options_process |= controller_process_option_validate; } // synchronously execute dependency. - status = controller_rule_process_begin(controller_process_option_execute, alias_other, controller_rule_action_type_start, rule_options, process->stack, main, process_other->cache); + status = controller_rule_process_begin(0, alias_other, controller_rule_action_type_start, options_process, process->stack, main, process_other->cache); if (status == F_child || status == F_signal) { f_thread_unlock(&process_other->active); @@ -1846,7 +1840,7 @@ extern "C" { controller_print_unlock_flush(main.data->error.to.stream, &main.thread->lock.print); - if (!(process_other->options & controller_rule_option_simulate) || F_status_set_fine(status) == F_memory_not) { + if (!(process_other->options & controller_process_option_simulate) || F_status_set_fine(status) == F_memory_not) { f_thread_unlock(&process_other->active); break; @@ -1865,6 +1859,8 @@ extern "C" { } } else { + status = main.setting->rules.array[id_rule].status; + f_thread_unlock(&main.thread->lock.rule); f_thread_unlock(&process_other->lock); } @@ -1879,6 +1875,8 @@ extern "C" { f_thread_lock_read(&main.thread->lock.rule); if (F_status_is_error(main.setting->rules.array[id_rule].status)) { + f_thread_unlock(&main.thread->lock.rule); + if (i == 0 || i == 1) { f_thread_mutex_lock(&main.thread->lock.print); @@ -1889,12 +1887,8 @@ extern "C" { controller_print_unlock_flush(main.data->error.to.stream, &main.thread->lock.print); - if (!(process_other->options & controller_rule_option_simulate)) { - f_thread_unlock(&main.thread->lock.rule); - - if (process_other) { - f_thread_unlock(&process_other->active); - } + if (!(process_other->options & controller_process_option_simulate)) { + f_thread_unlock(&process_other->active); break; } @@ -1910,25 +1904,19 @@ extern "C" { } } } - - f_thread_unlock(&main.thread->lock.rule); - } - else { - f_thread_unlock(&main.thread->lock.rule); + else { + f_thread_unlock(&main.thread->lock.rule); + } } if (process_other) { f_thread_unlock(&process_other->active); } - - if (!main.thread->enabled) break; } // for - if (!main.thread->enabled) break; - if (status == F_child || status == F_signal) break; - if (F_status_is_error(status) && !(process->options & controller_rule_option_simulate)) break; + if (F_status_is_error(status) && !(process->options & controller_process_option_simulate)) break; } // for } @@ -1940,18 +1928,18 @@ extern "C" { return F_signal; } - if ((process->options & controller_rule_option_wait) && F_status_is_error_not(status)) { - controller_rule_wait_all(main, process); // @fixme review this, it needs to check anything depending on this! + if ((process->options & controller_process_option_wait) && F_status_is_error_not(status)) { + controller_rule_wait_all(main, F_false, process); if (!main.thread->enabled) { return F_signal; } } - if (!(process->options & controller_rule_option_validate) && F_status_is_error_not(status)) { + if (!(process->options & controller_process_option_validate) && F_status_is_error_not(status)) { // find at least one of the requested action when the rule is required. - if (process->options & controller_rule_option_require) { + if (process->options & controller_process_option_require) { bool missing = F_true; f_array_length_t j = 0; @@ -2036,7 +2024,7 @@ extern "C" { f_thread_unlock(&process->lock); f_thread_lock_read(&process->lock); - return status_lock; + return F_signal; } if (!main.thread->enabled) { @@ -2047,12 +2035,7 @@ extern "C" { return F_signal; } - status = controller_rule_find(process->rule.alias, main.setting->rules, &id_rule); - - if (F_status_is_error(status)) { - controller_rule_error_print(main.data->error, process->cache.action, F_status_set_fine(status), "controller_rule_find", F_true, F_true, main.thread); - } - else { + if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { controller_rule_t *rule = &main.setting->rules.array[id_rule]; rule->status = process->rule.status; @@ -2083,7 +2066,7 @@ extern "C" { #endif // _di_controller_rule_process_ #ifndef _di_controller_rule_process_begin_ - f_status_t controller_rule_process_begin(const uint8_t process_options, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const f_array_lengths_t stack, const controller_main_t main, const controller_cache_t cache) { + f_status_t controller_rule_process_begin(const uint8_t options_force, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const f_array_lengths_t stack, const controller_main_t main, const controller_cache_t cache) { if (!main.thread->enabled) { return F_signal; @@ -2124,6 +2107,7 @@ extern "C" { if (status == F_signal) { f_thread_unlock(&process->active); + f_thread_unlock(&main.thread->lock.process); return status; } @@ -2131,6 +2115,7 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); f_thread_unlock(&process->active); + f_thread_unlock(&main.thread->lock.process); return F_signal; } @@ -2146,9 +2131,8 @@ extern "C" { return F_busy; } - // the thread is done, so detach/close the thread. + // the thread is done, so close the thread. if (process->state == controller_process_state_done) { - f_thread_cancel(process->id_thread); f_thread_join(process->id_thread, 0); } @@ -2239,7 +2223,7 @@ extern "C" { f_thread_unlock(&process->lock); if (F_status_is_error_not(status)) { - if ((process_options & controller_process_option_execute) && (process_options & controller_process_option_asynchronous)) { + if (options_force & controller_process_option_asynchronous) { status = f_thread_create(0, &process->id_thread, controller_thread_process, (void *) process); if (F_status_is_error(status)) { @@ -2247,7 +2231,7 @@ extern "C" { } } else { - status = controller_rule_process_do(process_options, process); + status = controller_rule_process_do(options_force, process); if (status == F_child || status == F_signal) { f_thread_unlock(&process->active); @@ -2268,10 +2252,10 @@ extern "C" { #endif // _di_controller_rule_process_begin_ #ifndef _di_controller_rule_process_do_ - f_status_t controller_rule_process_do(const uint8_t options, controller_process_t *process) { + f_status_t controller_rule_process_do(const uint8_t options_force, controller_process_t *process) { // the process and active locks shall be held for the duration of this processing (aside from switching between read to/from write). - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_lock_read(&process->active); } @@ -2282,7 +2266,7 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2299,13 +2283,14 @@ extern "C" { f_thread_lock_read(&main.thread->lock.rule); if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { - f_thread_unlock(&process->lock); status_lock = controller_lock_write(main.thread, &process->lock); if (status_lock == F_signal) { - if (options & controller_process_option_asynchronous) { + f_thread_unlock(&main.thread->lock.rule); + + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2313,9 +2298,10 @@ extern "C" { } if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.rule); f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2328,13 +2314,12 @@ extern "C" { f_thread_unlock(&process->lock); f_thread_lock_read(&process->lock); - f_thread_unlock(&main.thread->lock.rule); if (F_status_is_error(status)) { controller_entry_error_print(main.data->error, process->cache.action, F_status_set_fine(status), "controller_rule_copy", F_true, main.thread); } - else if (options & controller_process_option_execute) { + else { for (f_array_length_t i = 0; i < process->stack.used && main.thread->enabled; ++i) { if (process->stack.array[i] == id_rule) { @@ -2361,7 +2346,7 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2380,7 +2365,7 @@ extern "C" { status_lock = controller_lock_write(main.thread, &process->lock); if (status_lock == F_signal) { - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2390,7 +2375,7 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2405,90 +2390,67 @@ extern "C" { } } - if (F_status_is_error(status)) { - f_thread_unlock(&main.thread->lock.rule); - - status_lock = controller_lock_write(main.thread, &main.thread->lock.rule); - - if (status_lock == F_signal) { - if (options & controller_process_option_asynchronous) { - f_thread_unlock(&process->active); - } - - return status_lock; - } - - if (!main.thread->enabled) { - f_thread_unlock(&main.thread->lock.rule); - - if (options & controller_process_option_asynchronous) { - f_thread_unlock(&process->active); - } - - return F_signal; - } - - if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { - main.setting->rules.array[id_rule].status = status; - } - - f_thread_unlock(&main.thread->lock.rule); - } - else if (options & controller_process_option_execute) { - if (main.thread->enabled) { - status = controller_rule_process(controller_rule_action_type_start, main, process); - } + if (F_status_is_error_not(status)) { + status = controller_rule_process(controller_rule_action_type_start, main, process); } } else { f_thread_unlock(&main.thread->lock.rule); - status_lock = controller_lock_write(main.thread, &main.thread->lock.rule); + status = F_status_set_error(F_found_not); - if (status_lock == F_signal) { - if (options & controller_process_option_asynchronous) { - f_thread_unlock(&process->active); - } + if (main.data->error.verbosity != f_console_verbosity_quiet) { + f_thread_mutex_lock(&main.thread->lock.print); - return status_lock; - } + controller_rule_item_error_print_rule_not_loaded(main.data->error, process->rule.alias.string); + controller_rule_error_print_cache(main.data->error, process->cache.action, F_false); - if (!main.thread->enabled) { - f_thread_unlock(&main.thread->lock.rule); + controller_print_unlock_flush(main.data->error.to.stream, &main.thread->lock.print); + } + } - if (options & controller_process_option_asynchronous) { - f_thread_unlock(&process->active); - } + if (status == F_child) { + f_thread_unlock(&process->lock); - return F_signal; + if (options_force & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); } - status = F_status_set_error(F_found_not); + return status; + } - if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { - main.setting->rules.array[id_rule].status = status; - } + status_lock = controller_lock_write(main.thread, &main.thread->lock.rule); - f_thread_unlock(&main.thread->lock.rule); + if (status_lock == F_signal) { + f_thread_unlock(&process->lock); - if (main.data->error.verbosity != f_console_verbosity_quiet) { - f_thread_mutex_lock(&main.thread->lock.print); + if (options_force & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } - controller_rule_item_error_print_rule_not_loaded(main.data->error, process->rule.alias.string); - controller_rule_error_print_cache(main.data->error, process->cache.action, F_false); + return status_lock; + } - controller_print_unlock_flush(main.data->error.to.stream, &main.thread->lock.print); + if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.rule); + f_thread_unlock(&process->lock); + + if (options_force & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); } + + return F_signal; } - if (status == F_child) { - return status; + if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { + main.setting->rules.array[id_rule].status = status; } + f_thread_unlock(&main.thread->lock.rule); f_thread_unlock(&process->lock); if (status == F_signal) { - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2498,7 +2460,7 @@ extern "C" { status_lock = controller_lock_write(main.thread, &process->lock); if (status_lock == F_signal) { - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2508,14 +2470,14 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } return F_signal; } - if ((options & controller_process_option_execute) && (options & controller_process_option_asynchronous)) { + if (options_force & controller_process_option_asynchronous) { process->state = controller_process_state_done; } else { @@ -2531,7 +2493,7 @@ extern "C" { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) { + if (options_force & controller_process_option_asynchronous) { f_thread_unlock(&process->active); } @@ -2787,10 +2749,10 @@ extern "C" { controller_rule_item_error_print(main.data->error, cache->action, for_item, main.thread); rule->status = controller_status_simplify_error(F_status_set_fine(status)); - return F_false; + return rule->status; } - return F_true; + return F_none; } #endif // _di_controller_rule_read_ @@ -4640,9 +4602,9 @@ extern "C" { fprintf(data->output.stream, "' has no '"); fprintf(data->output.stream, "%s%s%s", data->context.set.important.before->string, controller_rule_action_type_name(action).string, data->context.set.important.after->string); fprintf(data->output.stream, "' action to execute and would '"); - fprintf(data->output.stream, "%s%s%s", data->context.set.important.before->string, options & controller_rule_option_require ? controller_string_fail_s : controller_string_succeed_s, data->context.set.important.after->string); + fprintf(data->output.stream, "%s%s%s", data->context.set.important.before->string, options & controller_process_option_require ? controller_string_fail_s : controller_string_succeed_s, data->context.set.important.after->string); fprintf(data->output.stream, "' because it is '"); - fprintf(data->output.stream, "%s%s%s", data->context.set.important.before->string, options & controller_rule_option_require ? controller_string_required_s : controller_string_optional_s, data->context.set.important.after->string); + fprintf(data->output.stream, "%s%s%s", data->context.set.important.before->string, options & controller_process_option_require ? controller_string_required_s : controller_string_optional_s, data->context.set.important.after->string); fprintf(data->output.stream, "'.%c", f_string_eol_s[0]); } } @@ -4650,8 +4612,8 @@ extern "C" { fprintf(data->output.stream, "%c", f_string_eol_s[0]); fprintf(data->output.stream, "Rule %s%s%s {%c", data->context.set.title.before->string, rule.alias.used ? rule.alias.string : f_string_empty_s, data->context.set.title.after->string, f_string_eol_s[0]); fprintf(data->output.stream, " %s%s%s %s%c", data->context.set.important.before->string, controller_string_name_s, data->context.set.important.after->string, rule.name.used ? rule.name.string : f_string_empty_s, f_string_eol_s[0]); - fprintf(data->output.stream, " %s%s%s %s%c", data->context.set.important.before->string, controller_string_how_s, data->context.set.important.after->string, options & controller_rule_option_asynchronous ? controller_string_asynchronous : controller_string_synchronous_s, f_string_eol_s[0]); - fprintf(data->output.stream, " %s%s%s %s%c", data->context.set.important.before->string, controller_string_wait_s, data->context.set.important.after->string, options & controller_rule_option_wait ? controller_string_yes : controller_string_no_s, f_string_eol_s[0]); + fprintf(data->output.stream, " %s%s%s %s%c", data->context.set.important.before->string, controller_string_how_s, data->context.set.important.after->string, options & controller_process_option_asynchronous ? controller_string_asynchronous : controller_string_synchronous_s, f_string_eol_s[0]); + fprintf(data->output.stream, " %s%s%s %s%c", data->context.set.important.before->string, controller_string_wait_s, data->context.set.important.after->string, options & controller_process_option_wait ? controller_string_yes : controller_string_no_s, f_string_eol_s[0]); if (f_capability_supported()) { fprintf(data->output.stream, " %s%s%s ", data->context.set.important.before->string, controller_string_capability_s, data->context.set.important.after->string); @@ -4880,17 +4842,18 @@ extern "C" { #endif // _di_controller_rule_validate_ #ifndef _di_controller_rule_wait_all_ - void controller_rule_wait_all(const controller_main_t main, controller_process_t *caller) { + f_status_t controller_rule_wait_all(const controller_main_t main, const bool required, controller_process_t *caller) { f_thread_lock_read(&main.thread->lock.process); if (!main.thread->processs.used) { f_thread_unlock(&main.thread->lock.process); - return; + return F_data_not; } f_status_t status = F_none; + bool required_not_run = F_false; f_array_length_t i = 0; f_array_length_t j = 0; @@ -4899,13 +4862,16 @@ extern "C" { bool skip = F_false; - for (; i < main.thread->processs.used && main.thread->enabled; ++i, skip = F_false) { + for (; i < main.thread->processs.used && main.thread->enabled; ++i) { process = main.thread->processs.array[i]; if (caller) { + f_thread_unlock(&main.thread->lock.process); f_thread_lock_read(&main.thread->lock.rule); + skip = F_false; + for (j = 0; j < caller->stack.used && main.thread->enabled; ++j) { if (main.thread->processs.array[caller->stack.array[j]] && fl_string_dynamic_compare(process->rule.alias, main.thread->processs.array[caller->stack.array[j]]->rule.alias) == F_equal_to) { @@ -4916,6 +4882,7 @@ extern "C" { } // for f_thread_unlock(&main.thread->lock.rule); + f_thread_lock_read(&main.thread->lock.process); if (!main.thread->enabled) break; if (skip) continue; @@ -4925,6 +4892,16 @@ extern "C" { f_thread_unlock(&main.thread->lock.process); f_thread_lock_read(&process->lock); + if (required) { + if (!(process->options & controller_process_option_require)) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); + + continue; + } + } + if (!process->state || process->state == controller_process_state_idle || process->state == controller_process_state_done) { if (process->state == controller_process_state_done) { @@ -4934,6 +4911,7 @@ extern "C" { if (status == F_signal) { f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); break; } @@ -4941,17 +4919,42 @@ extern "C" { if (!main.thread->enabled) { f_thread_unlock(&process->lock); f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); break; } if (process->state == controller_process_state_done) { + f_thread_unlock(&process->active); + if (f_thread_lock_write_try(&process->active) == F_none) { f_thread_join(process->id_thread, 0); process->state = controller_process_state_idle; process->id_thread = 0; + + f_thread_unlock(&process->active); } + + f_thread_lock_read(&process->active); + } + + f_thread_unlock(&process->lock); + f_thread_lock_read(&process->lock); + } + + if (required && (process->options & controller_process_option_require)) { + if (F_status_is_error(process->rule.status)) { + status = F_status_set_error(F_require); + + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); + + break; + } + else if (process->rule.status == F_known_not) { + required_not_run = F_true; } } @@ -4959,13 +4962,42 @@ extern "C" { f_thread_unlock(&process->active); f_thread_lock_read(&main.thread->lock.process); + if (F_status_set_fine(status) == F_require) break; + continue; } if (process->state == controller_process_state_active || process->state == controller_process_state_busy) { f_thread_unlock(&process->lock); - controller_process_wait(main, process); + status = controller_process_wait(main, process); + + if (status == F_signal) { + f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); + + break; + } + + if (required) { + f_thread_lock_read(&process->lock); + + if ((process->options & controller_process_option_require)) { + f_thread_unlock(&process->lock); + + if (F_status_is_error(process->rule.status)) { + status = F_status_set_error(F_require); + + f_thread_unlock(&process->active); + f_thread_lock_read(&main.thread->lock.process); + + break; + } + } + else { + f_thread_unlock(&process->lock); + } + } } else { f_thread_unlock(&process->lock); @@ -4973,9 +5005,21 @@ extern "C" { f_thread_unlock(&process->active); f_thread_lock_read(&main.thread->lock.process); + + if (status == F_signal || F_status_set_fine(status) == F_require) break; } // for f_thread_unlock(&main.thread->lock.process); + + if (status == F_signal || F_status_set_fine(status) == F_require) { + return status; + } + + if (required_not_run) { + return F_require; + } + + return F_none; } #endif // _di_controller_rule_wait_all_ diff --git a/level_3/controller/c/private-rule.h b/level_3/controller/c/private-rule.h index 5062509..698e664 100644 --- a/level_3/controller/c/private-rule.h +++ b/level_3/controller/c/private-rule.h @@ -338,7 +338,7 @@ extern "C" { * - controller_rule_action_type_stop * @param options * A number using bits to represent specific boolean options. - * If bit controller_rule_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * If bit controller_process_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). * @param main * The main data. * @param process @@ -379,7 +379,7 @@ extern "C" { * The arguments to pass to the program. * @param options * A number using bits to represent specific boolean options. - * If bit controller_rule_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * If bit controller_process_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). * @param main * The main data. * @param execute_set @@ -425,7 +425,7 @@ extern "C" { * The arguments to pass to the program. * @param options * A number using bits to represent specific boolean options. - * If bit controller_rule_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * If bit controller_process_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). * @param main * The main data. * @param execute_set @@ -590,20 +590,19 @@ extern "C" { #endif // _di_controller_rule_process_ /** - * Synchronously or Asynchronously begin processing some rule. + * Synchronously or asynchronously begin processing some rule. * - * @param process_options - * If controller_process_option_asynchronous, then asynchronously execute. - * If controller_process_option_execute, then load and execute. + * @param options_force + * Force the given process options, only supporting a subset of process options. * + * If controller_process_option_asynchronous, then asynchronously execute. * If not controller_process_option_asynchronous, then synchronously execute. - * If not controller_process_option_execute, then load only, do not execute at all. * @param alias_rule * The alias of the rule, such as "boot/init". * @param action * The action to perform based on the action type codes. * @param options - * A number using bits to represent specific boolean options. + * The process options to pass to the process. * @param stack * A stack representing the processes already running in this rule process dependency tree. * This is used to prevent circular dependencies. @@ -631,7 +630,7 @@ extern "C" { * @see f_thread_create() */ #ifndef _di_controller_rule_process_begin_ - extern f_status_t controller_rule_process_begin(const uint8_t process_options, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const f_array_lengths_t stack, const controller_main_t main, const controller_cache_t cache) f_gcc_attribute_visibility_internal; + extern f_status_t controller_rule_process_begin(const uint8_t options_force, const f_string_static_t alias_rule, const uint8_t action, const uint8_t options, const f_array_lengths_t stack, const controller_main_t main, const controller_cache_t cache) f_gcc_attribute_visibility_internal; #endif // _di_controller_rule_process_begin_ /** @@ -640,12 +639,11 @@ extern "C" { * This does all the preparation work that needs to be synchronously performed within the same thread. * This will copy the rule by the alias to the process structure. * - * @param options - * If controller_process_option_asynchronous, then asynchronously execute. - * If controller_process_option_execute, then load and execute. + * @param options_force + * Force the given process options, only supporting a subset of process options. * + * If controller_process_option_asynchronous, then asynchronously execute. * If not controller_process_option_asynchronous, then synchronously execute. - * If not controller_process_option_execute, then load only, do not execute at all. * @param process * The process data. * @@ -666,7 +664,7 @@ extern "C" { * @see controller_rule_process_begin() */ #ifndef _di_controller_rule_process_do_ - extern f_status_t controller_rule_process_do(const uint8_t options, controller_process_t *process) f_gcc_attribute_visibility_internal; + extern f_status_t controller_rule_process_do(const uint8_t options_force, controller_process_t *process) f_gcc_attribute_visibility_internal; #endif // _di_controller_rule_process_do_ /** @@ -685,8 +683,9 @@ extern "C" { * The rule status will be updated by this function. * * @return - * F_true on success. - * F_false on failure. + * F_none on success. + * + * Simplified status (with error bit) from controller_status_simplify_error() on failure. * * @see controller_rule_items_increase_by(). * @see controller_rule_item_read(). @@ -763,8 +762,8 @@ extern "C" { * @param options * A number using bits to represent specific boolean options. * If no bits set, then operate normally in a synchronous manner. - * If bit controller_rule_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). - * If bit controller_rule_option_asynchronous, then run asynchronously. + * If bit controller_process_option_simulate, then the rule execution is in simulation mode (printing a message that the rule would be executed but does not execute the rule). + * If bit controller_process_option_asynchronous, then run asynchronously. * @param main * The main data. * @param cache @@ -779,13 +778,24 @@ extern "C" { * * @param main * The main data. + * @param required + * If TRUE, then only process required rules and if a required rule has failed, return. + * If FALSE, process all waits, returning normally. * @param caller * The process representing the caller so that the process never waits on itself. * (optional) set to 0 when calling from a thread that is not running/executing any process. * Failure to set this to the process on a thread running/executing a process will likely result in a deadlock. + * + * @return + * F_none on success. + * F_data_not on success and nothing to do. + * F_require on success, but a required rule has not been run yet. + * F_signal on (exit) signal received. + * + * F_require (with error bit set) if a required process is in failed status when required is TRUE. */ #ifndef _di_controller_rule_wait_all_ - extern void controller_rule_wait_all(const controller_main_t main, controller_process_t *process) f_gcc_attribute_visibility_internal; + extern f_status_t controller_rule_wait_all(const controller_main_t main, const bool required, controller_process_t *process) f_gcc_attribute_visibility_internal; #endif // _di_controller_rule_wait_all_ #ifdef __cplusplus diff --git a/level_3/controller/c/private-thread.c b/level_3/controller/c/private-thread.c index 349faf9..e7b769f 100644 --- a/level_3/controller/c/private-thread.c +++ b/level_3/controller/c/private-thread.c @@ -191,16 +191,16 @@ extern "C" { } } else { - f_thread_join(thread.id_rule, 0); - - thread.id_rule = 0; - status = thread.status; - if (status == F_child) { controller_thread_delete_simple(&thread); return F_child; } + + f_thread_join(thread.id_rule, 0); + + thread.id_rule = 0; + status = thread.status; } } } @@ -315,7 +315,9 @@ extern "C" { if (!thread->enabled) return 0; } - if (controller_rule_process_do(controller_process_option_asynchronous | controller_process_option_execute, process) == F_child) { + const f_status_t status = controller_rule_process_do(controller_process_option_asynchronous, process); + + if (status == F_child) { // A forked child process should deallocate memory on exit. // It seems that this function doesn't return to the calling thread for a forked child process, even with the "return 0;" below. diff --git a/level_3/controller/documents/entry.txt b/level_3/controller/documents/entry.txt index 88ba038..038b4fd 100644 --- a/level_3/controller/documents/entry.txt +++ b/level_3/controller/documents/entry.txt @@ -21,6 +21,7 @@ Entry Documentation: The "failsafe" Action accepts only a valid Item Name in which will be executed when a failure is detected. Only a single "failsafe" Action may exist at a time. Each successive "failsafe" Action specified replaces the previously defined "failsafe" Action (in a top-down manner). + When operating in "failsafe", the "require" Action is ignored given that it is meaningless once operating in failsafe. The "item" Action accepts only a valid Item Name in which will be immediately executed. Any valid Item Name, except for the reserved "main", may be used. -- 1.8.3.1