From c7582cb4958fdfc3e5768f93ab1fa34849191bf6 Mon Sep 17 00:00:00 2001 From: Kevin Day Date: Sat, 10 Apr 2021 10:35:00 -0500 Subject: [PATCH] Bugfix: improve cancellation point processing and handling. This avoids the use of f_thread_cancel_test() which is horribly limited in design. Because f_thread_cancel_test() never returns when thread is in cancelled state, the caller cannot properly handle the situation. This limits the code design in order to properly use f_thread_cancel_test(). There are already cancellation points in place due to the extensive use of thread.enabled. Improve these cancellation points, adding timed checks to write locks to further check if a cancellation was received (via thread.enabled). The timeout is arbitrarily pick as "0.1 seconds". Hopefully, this doesn't make things too busy, but really the write locks should (ideally) never have to be waiting that long anyway. Remove stale code in private-common.c involving a write lock and then an immediate unlock on deallocation. Add additional thread.enabled checks. There were some places where cancellation points were being returned but were not properly unlocking all held locks that are within the scope of the function. All threads should now be set to PTHREAD_CANCEL_DEFERRED. The forced thread termination via kill signals are now removed. They shouldn't be needed now that cancellation (should be) guaranteed. This will have to be tested over time to confirm the truth of. --- level_3/controller/c/private-common.c | 29 ++- level_3/controller/c/private-common.h | 25 +++ level_3/controller/c/private-controller.c | 75 ++++++- level_3/controller/c/private-rule.c | 314 +++++++++++++++++++++++++++--- level_3/controller/c/private-thread.c | 42 ++-- 5 files changed, 430 insertions(+), 55 deletions(-) diff --git a/level_3/controller/c/private-common.c b/level_3/controller/c/private-common.c index 08edc83..888cec0 100644 --- a/level_3/controller/c/private-common.c +++ b/level_3/controller/c/private-common.c @@ -140,9 +140,6 @@ extern "C" { if (F_status_is_error(status)) { if (F_status_set_fine(status) == F_busy) { - f_thread_lock_write(lock); - f_thread_unlock(lock); - if (f_thread_lock_delete(lock) == F_none) { lock = 0; } @@ -163,6 +160,32 @@ extern "C" { } #endif // _di_controller_lock_delete_simple_ +#ifndef _di_controller_lock_write_ + f_status_t controller_lock_write(controller_thread_t * const thread, f_thread_lock_t *lock) { + + struct timespec time; + time.tv_sec = 0; + time.tv_nsec = controller_thread_lock_timeout; + + f_status_t status = F_none; + + for (;;) { + status = f_thread_lock_write_timed(&time, lock); + + if (status == F_time) { + if (!thread->enabled) { + return F_signal; + } + } + else { + break; + } + } // for + + return status; + } +#endif // _di_controller_lock_write_ + #ifndef _di_controller_print_unlock_flush_ void controller_print_unlock_flush(FILE * const stream, f_thread_mutex_t *mutex) { diff --git a/level_3/controller/c/private-common.h b/level_3/controller/c/private-common.h index 83278b5..32b6321 100644 --- a/level_3/controller/c/private-common.h +++ b/level_3/controller/c/private-common.h @@ -1077,6 +1077,7 @@ extern "C" { #define controller_thread_cleanup_interval_short 180 // 3 minutes in seconds. #define controller_thread_exit_process_cancel_wait 60000000 // 0.06 seconds in nanoseconds. #define controller_thread_exit_process_cancel_total 150 // 9 seconds in multiples of wait. + #define controller_thread_lock_timeout 100000000 // 0.1 seconds in nanoseconds. #define controller_thread_simulation_timeout 200000 // 0.2 seconds in microseconds. #define controller_thread_wait_timeout_seconds 10 #define controller_thread_wait_timeout_nanoseconds 0 @@ -1315,6 +1316,30 @@ extern "C" { #endif // _di_controller_lock_delete_simple_ /** + * Wait to get a write lock. + * + * Given a r/w lock, periodically check to see if main thread is disabled while waiting. + * + * @param lock + * The r/w lock to obtain a write lock on. + * @param thread + * The thread data used to determine if the main thread is disabled or not. + * + * @return + * F_none on success. + * F_status if main thread is disabled and write lock was never achieved. + * + * Status from: f_thread_lock_write_timed(). + * + * Errors (with error bit) from: f_thread_lock_write_timed(). + * + * @see f_thread_lock_write_timed() + */ +#ifndef _di_controller_lock_write_ + extern f_status_t controller_lock_write(controller_thread_t * const thread, f_thread_lock_t *lock) f_gcc_attribute_visibility_internal; +#endif // _di_controller_lock_write_ + +/** * Flush the stream buffer and then unlock the mutex. * * Weird behavior was observed when piping data from this program. diff --git a/level_3/controller/c/private-controller.c b/level_3/controller/c/private-controller.c index ca34fe7..df0e2fe 100644 --- a/level_3/controller/c/private-controller.c +++ b/level_3/controller/c/private-controller.c @@ -437,7 +437,7 @@ extern "C" { return status; } - for (;;) { + for (; main.thread->enabled; ) { actions = &main.setting->entry.items.array[cache->ats.array[at_i]].actions; @@ -605,6 +605,10 @@ extern "C" { } } // for + if (!main.thread->enabled) { + return F_signal; + } + // if ready was never found in the entry, then default to always ready. if (main.setting->ready == controller_setting_ready_no) { main.setting->ready = controller_setting_ready_yes; @@ -687,7 +691,7 @@ extern "C" { } } - for (;;) { + for (; main.thread->enabled; ) { entry_actions = &main.setting->entry.items.array[cache->ats.array[at_i]].actions; @@ -930,7 +934,18 @@ extern "C" { break; } else if (entry_action->type == controller_entry_action_type_consider || entry_action->type == controller_entry_action_type_rule) { - f_thread_lock_write(&main.thread->lock.rule); + + status = controller_lock_write(main.thread, &main.thread->lock.rule); + + if (status == F_signal) { + break; + } + + if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.rule); + + break; + } status = controller_rules_increase(&main.setting->rules); @@ -971,6 +986,8 @@ extern "C" { } } + if (!main.thread->enabled) break; + // the rule is not yet loaded, ensure that it is loaded. if (status != F_true) { @@ -990,7 +1007,17 @@ extern "C" { memcpy(cache_name_item, cache->action.name_item.string, cache->action.name_item.used); memcpy(cache_name_file, cache->action.name_file.string, cache->action.name_file.used); - f_thread_lock_write(&main.thread->lock.rule); + status = controller_lock_write(main.thread, &main.thread->lock.rule); + + if (status == F_signal) { + break; + } + + if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.rule); + + break; + } status = controller_rule_read(alias_rule, main, cache, &main.setting->rules.array[main.setting->rules.used]); @@ -1010,6 +1037,12 @@ extern "C" { cache->action.line_action = cache_line_action; cache->action.line_item = cache_line_item; + if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.rule); + + break; + } + if (F_status_is_error(status)) { if (main.data->error.verbosity != f_console_verbosity_quiet) { @@ -1039,7 +1072,18 @@ extern "C" { if (controller_find_process(alias_rule, main.thread->processs, 0) == F_false) { f_thread_unlock(&main.thread->lock.process); - f_thread_lock_write(&main.thread->lock.process); + + status = controller_lock_write(main.thread, &main.thread->lock.process); + + if (status == F_signal) { + break; + } + + if (!main.thread->enabled) { + f_thread_unlock(&main.thread->lock.process); + + break; + } status = controller_processs_increase(&main.thread->processs); @@ -1051,7 +1095,20 @@ extern "C" { // only copy the rule alias, as that is all that is needed at this point (the entire rule gets copied prior to executing/processing). controller_process_t *process = main.thread->processs.array[main.thread->processs.used]; - f_thread_lock_write(&process->lock); + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + f_thread_unlock(&main.thread->lock.process); + + break; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&main.thread->lock.process); + + break; + } process->rule.alias.used = 0; @@ -1108,7 +1165,7 @@ extern "C" { status = controller_rule_process_begin(process_options, alias_rule, controller_rule_action_type_start, rule_options, stack, main, *cache); - if (F_status_set_fine(status) == F_memory_not || status == F_child || status == F_signal) { + if (F_status_set_fine(status) == F_memory_not || status == F_child || status == F_signal || !main.thread->enabled) { break; } } @@ -1236,6 +1293,10 @@ extern "C" { } } // for + if (!main.thread->enabled) { + return F_signal; + } + if (status == F_child || status == F_signal) { return status; } diff --git a/level_3/controller/c/private-rule.c b/level_3/controller/c/private-rule.c index f9679dc..764f090 100644 --- a/level_3/controller/c/private-rule.c +++ b/level_3/controller/c/private-rule.c @@ -839,12 +839,7 @@ extern "C" { return status; } - for (i = 0; i < process->rule.items.used; ++i) { - - if (!main.thread->enabled) { - status = F_signal; - break; - } + for (i = 0; i < process->rule.items.used && main.thread->enabled; ++i) { if (process->rule.items.array[i].type == controller_rule_item_type_setting) continue; @@ -872,7 +867,7 @@ extern "C" { status = controller_rule_execute_foreground(process->rule.items.array[i].type, process->rule.items.array[i].actions.array[j], 0, process->rule.items.array[i].actions.array[j].parameters, options, main, &execute_set, process); - if (status == F_child) break; + if (status == F_child || status == F_signal) break; if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); @@ -895,7 +890,7 @@ extern "C" { status = controller_rule_execute_foreground(process->rule.items.array[i].type, process->rule.items.array[i].actions.array[j], process->rule.script.used ? process->rule.script.string : controller_default_program_script, arguments_none, options, main, &execute_set, process); - if (status == F_child) break; + if (status == F_child || status == F_signal) break; if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); @@ -916,7 +911,7 @@ extern "C" { status = controller_rule_execute_pid_with(process->rule.items.array[i].type, process->rule.items.array[i].actions.array[j], 0, process->rule.items.array[i].actions.array[j].parameters, options, main, &execute_set, process); - if (status == F_child) break; + if (status == F_child || status == F_signal) break; if (F_status_is_error(status)) { process->rule.items.array[i].actions.array[j].status = F_status_set_error(F_failure); @@ -956,6 +951,10 @@ extern "C" { f_macro_string_maps_t_delete_simple(environment); + if (!main.thread->enabled) { + return F_signal; + } + if (status == F_child || status == F_signal || F_status_is_error(status)) { return status; } @@ -1016,7 +1015,18 @@ extern "C" { result = 0; f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } // assign the child process id to allow for the cancel process to send appropriate termination signals to the child process. process->child = id_child; @@ -1027,8 +1037,25 @@ extern "C" { // have the parent wait for the child process to finish. waitpid(id_child, &result, 0); + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } + f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } // remove the pid now that waidpid() has returned. process->child = 0; @@ -1138,7 +1165,18 @@ extern "C" { result = 0; f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } // assign the child process id to allow for the cancel process to send appropriate termination signals to the child process. process->child = id_process; @@ -1149,8 +1187,25 @@ extern "C" { // have the parent wait for the child process to finish. @todo do not wait, this is a background execution! waitpid(id_process, &result, 0); + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } + f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + return F_signal; + } // remove the pid now that waidpid() has returned. @todo do not clear until forked execution is known to have exited, this is a background execution process->child = 0; @@ -1840,8 +1895,12 @@ extern "C" { else { f_thread_unlock(&main.thread->lock.rule); } + + 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; @@ -1923,7 +1982,21 @@ extern "C" { f_array_length_t id_rule = 0; f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + f_thread_lock_read(&process->lock); + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_lock_read(&process->lock); + + return F_signal; + } if (F_status_is_error(status)) { process->rule.status = controller_status_simplify_error(F_status_set_fine(status)); @@ -1932,7 +2005,22 @@ extern "C" { process->rule.status = status; } - f_thread_lock_write(&main.thread->lock.rule); + status = controller_lock_write(main.thread, &main.thread->lock.rule); + + if (status == F_signal) { + f_thread_unlock(&process->lock); + f_thread_lock_read(&process->lock); + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&main.thread->lock.rule); + f_thread_lock_read(&process->lock); + + return F_signal; + } status = controller_rule_find(process->rule.alias, main.setting->rules, &id_rule); @@ -2006,7 +2094,21 @@ extern "C" { process = main.thread->processs.array[at]; f_thread_lock_read(&process->active); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + f_thread_unlock(&process->active); + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + + return F_signal; + } // once a write lock on the process is achieved, it is safe to unlock the main process read lock. f_thread_unlock(&main.thread->lock.process); @@ -2029,7 +2131,21 @@ extern "C" { } f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + f_thread_unlock(&process->active); + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + + return F_signal; + } process->state = controller_process_state_active; process->action = action; @@ -2109,6 +2225,8 @@ extern "C" { status = controller_rule_process_do(process_options, process); if (status == F_child || status == F_signal) { + f_thread_unlock(&process->active); + return status; } } @@ -2127,15 +2245,21 @@ extern "C" { #ifndef _di_controller_rule_process_do_ f_status_t controller_rule_process_do(const uint8_t options, controller_process_t *process) { - // the process lock shall be held for the duration of this processing (aside from switching between read to/from write). - if (options & controller_process_option_asynchronous) f_thread_lock_read(&process->active); + // 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) { + f_thread_lock_read(&process->active); + } + f_thread_lock_read(&process->lock); controller_main_t main = controller_macro_main_t_initialize((controller_data_t *) process->main_data, (controller_setting_t *) process->main_setting, (controller_thread_t *) process->main_thread); if (!main.thread->enabled) { f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) f_thread_unlock(&process->active); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } return F_signal; } @@ -2150,7 +2274,26 @@ extern "C" { if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return F_signal; + } controller_rule_delete_simple(&process->rule); @@ -2188,6 +2331,16 @@ extern "C" { } } // for + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return F_signal; + } + if (F_status_is_error_not(status)) { status = f_type_array_lengths_increase(&process->stack); @@ -2196,7 +2349,26 @@ extern "C" { } else { f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return F_signal; + } process->stack.array[process->stack.used++] = id_rule; @@ -2208,7 +2380,26 @@ extern "C" { if (F_status_is_error(status)) { f_thread_unlock(&main.thread->lock.rule); - f_thread_lock_write(&main.thread->lock.rule); + + status = controller_lock_write(main.thread, &main.thread->lock.rule); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } + + 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; @@ -2225,9 +2416,27 @@ extern "C" { else { f_thread_unlock(&main.thread->lock.rule); - status = F_status_set_error(F_found_not); + status = controller_lock_write(main.thread, &main.thread->lock.rule); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } - f_thread_lock_write(&main.thread->lock.rule); + 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; + } + + status = F_status_set_error(F_found_not); if (controller_rule_find(process->rule.alias, main.setting->rules, &id_rule) == F_true) { main.setting->rules.array[id_rule].status = status; @@ -2245,12 +2454,39 @@ extern "C" { } } - if (status == F_child || status == F_signal) { + if (status == F_child) { return status; } f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return status; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } + + return F_signal; + } if ((options & controller_process_option_execute) && (options & controller_process_option_asynchronous)) { process->state = controller_process_state_done; @@ -2262,7 +2498,10 @@ extern "C" { process->stack.used = used_original_stack; f_thread_unlock(&process->lock); - if (options & controller_process_option_asynchronous) f_thread_unlock(&process->active); + + if (options & controller_process_option_asynchronous) { + f_thread_unlock(&process->active); + } if (main.thread->enabled) { if (options & controller_process_option_execute) { @@ -4629,9 +4868,12 @@ extern "C" { if (!main.thread->processs.used) { f_thread_unlock(&main.thread->lock.process); + return; } + f_status_t status = F_none; + f_array_length_t i = 0; f_array_length_t j = 0; @@ -4668,7 +4910,21 @@ extern "C" { if (process->state == controller_process_state_done) { f_thread_unlock(&process->lock); - f_thread_lock_write(&process->lock); + + status = controller_lock_write(main.thread, &process->lock); + + if (status == F_signal) { + f_thread_unlock(&process->active); + + break; + } + + if (!main.thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + + break; + } if (process->state == controller_process_state_done) { f_thread_unlock(&process->active); diff --git a/level_3/controller/c/private-thread.c b/level_3/controller/c/private-thread.c index fb0b5bf..bb23ee3 100644 --- a/level_3/controller/c/private-thread.c +++ b/level_3/controller/c/private-thread.c @@ -12,12 +12,16 @@ extern "C" { #ifndef _di_controller_thread_cleanup_ void * controller_thread_cleanup(void *arguments) { + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + const controller_main_t *main = (controller_main_t *) arguments; if (!main->thread->enabled) return 0; const unsigned int interval = main->data->parameters[controller_parameter_test].result == f_console_result_found ? controller_thread_cleanup_interval_short : controller_thread_cleanup_interval_long; + f_status_t status = F_none; + while (main->thread->enabled) { sleep(interval); @@ -65,7 +69,20 @@ extern "C" { break; } - f_thread_lock_write(&process->lock); + status = controller_lock_write(main->thread, &process->lock); + + if (status == F_signal) { + f_thread_unlock(&process->active); + + break; + } + + if (!main->thread->enabled) { + f_thread_unlock(&process->lock); + f_thread_unlock(&process->active); + + break; + } process->state = controller_process_state_idle; process->id_thread = 0; @@ -96,6 +113,8 @@ extern "C" { #ifndef _di_controller_thread_control_ void * controller_thread_control(void *arguments) { + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + controller_main_t *main = (controller_main_t *) arguments; if (!main->thread->enabled) return 0; @@ -288,6 +307,8 @@ extern "C" { #ifndef _di_controller_thread_process_ void * controller_thread_process(void *arguments) { + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + controller_process_t *process = (controller_process_t *) arguments; { @@ -411,33 +432,20 @@ extern "C" { nanosleep(&wait, 0); } - f_thread_signal(process->id_thread, F_signal_kill); - f_thread_join(process->id_thread, 0); process->child = 0; process->id_thread = 0; } } // for - - // guarantee these threads are terminated. - if (main->thread->id_cleanup) { - f_thread_signal(main->thread->id_cleanup, F_signal_kill); - } - - if (main->thread->id_control) { - f_thread_signal(main->thread->id_control, F_signal_kill); - } - - if (main->thread->id_rule) { - f_thread_signal(main->thread->id_rule, F_signal_kill); - } } #endif // _di_controller_thread_process_cancel_ #ifndef _di_controller_thread_entry_ void * controller_thread_entry(void *arguments) { + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + controller_main_entry_t *entry = (controller_main_entry_t *) arguments; if (!entry->main->thread->enabled) return 0; @@ -508,6 +516,8 @@ extern "C" { #ifndef _di_controller_thread_rule_ void * controller_thread_rule(void *arguments) { + f_thread_cancel_state_set(PTHREAD_CANCEL_DEFERRED, 0); + controller_main_t *main = (controller_main_t *) arguments; if (!main->thread->enabled) return 0; -- 1.8.3.1