This is a bug fix disguised as a new feature.
When controller runs in program mode and is cancelled, all background (asynchronous) processes are cancelled.
My original thoughts were that background processes should stay open in program mode.
This is the bug being fixed.
I believe that there are use cases to operate in "program" mode and to always terminate the background processes in this manner.
Rather than fixing one case and breaking the other, this is a new feature that helps solve both cases.
The "program" mode operates in the same manner unchanged.
The new "helper" mode operates by detaching background (asynchronous) processes on exit.
The foreground process still runs and blocks normally.
A terminate signal might still terminate background processes.
More work is likely needed in this regard.
This stretches the original design to its limits.
The 0.7.x versions and later will need re-design to better handle these cases.
The original design I used was a learn as I go for thread design.
This resulted in rather messy code.
Now that I made it to this point, the controller program (0.6.x and earlier) can be used as a stepping stone for a better design.
Some of the problems are worked-around.
The program starts and exits too fast in controller mode.
The child processes end up getting terminated before a complete process is started and then backgrounded.
The work-around is to add a short sleep.
This is not reliable but for most cases it should be fine.
Additional work-arounds may be needed by the user such as executig their own sleep foreground process.
const f_string_static_t controller_full_path_s = macro_f_string_static_t_initialize(CONTROLLER_full_path_s, 0, CONTROLLER_full_path_s_length);
const f_string_static_t controller_group_s = macro_f_string_static_t_initialize(CONTROLLER_group_s, 0, CONTROLLER_group_s_length);
const f_string_static_t controller_groups_s = macro_f_string_static_t_initialize(CONTROLLER_groups_s, 0, CONTROLLER_groups_s_length);
+ const f_string_static_t controller_helper_s = macro_f_string_static_t_initialize(CONTROLLER_helper_s, 0, CONTROLLER_helper_s_length);
const f_string_static_t controller_how_s = macro_f_string_static_t_initialize(CONTROLLER_how_s, 0, CONTROLLER_how_s_length);
const f_string_static_t controller_idle_s = macro_f_string_static_t_initialize(CONTROLLER_idle_s, 0, CONTROLLER_idle_s_length);
const f_string_static_t controller_iki_s = macro_f_string_static_t_initialize(CONTROLLER_iki_s, 0, CONTROLLER_iki_s_length);
#define CONTROLLER_full_path_s "full_path"
#define CONTROLLER_group_s "group"
#define CONTROLLER_groups_s "groups"
+ #define CONTROLLER_helper_s "helper"
#define CONTROLLER_how_s "how"
#define CONTROLLER_idle_s "idle"
#define CONTROLLER_iki_s "iki"
#define CONTROLLER_full_path_s_length 9
#define CONTROLLER_group_s_length 5
#define CONTROLLER_groups_s_length 6
+ #define CONTROLLER_helper_s_length 6
#define CONTROLLER_how_s_length 3
#define CONTROLLER_idle_s_length 4
#define CONTROLLER_iki_s_length 3
extern const f_string_static_t controller_full_path_s;
extern const f_string_static_t controller_group_s;
extern const f_string_static_t controller_groups_s;
+ extern const f_string_static_t controller_helper_s;
extern const f_string_static_t controller_how_s;
extern const f_string_static_t controller_idle_s;
extern const f_string_static_t controller_iki_s;
* cache: The cache used in this process.
* child: The process id of a child process, if one is running (when forking to execute a child process).
* lock: A read/write lock on the structure.
- * options: Configuration options for this asynchronous thread.
+ * options: Configuration options for this thread.
* result: The last return code from an execution of a process.
* rule: A copy of the rule actively being executed.
* stack: A stack used to represent dependencies as Rule ID's to avoid circular rule dependencies (If Rule A waits on Rule B, then Rule B must not wait on Rule A).
* - abort: Abort received before finished processing Entry/Exit.
*
* controller_setting_mode_*:
+ * - helper: Run as a helper, exiting when finished prrocess entry (and any respective exit).
* - program: Run as a program, exiting when finished prrocess entry (and any respective exit).
* - service: Run as a service, listening for requests after processing entry.
*
controller_setting_ready_done_e,
controller_setting_ready_fail_e,
controller_setting_ready_abort_e,
- };
+ }; // enum
enum {
controller_setting_mode_service_e = 0,
+ controller_setting_mode_helper_e,
controller_setting_mode_program_e,
- };
+ }; // enum
enum {
controller_setting_flag_interruptible_e = 0x1,
controller_setting_flag_pid_created_e = 0x2,
controller_setting_flag_failsafe_e = 0x4,
- };
+ }; // enum
typedef struct {
uint8_t flag;
#define controller_thread_wait_timeout_4_seconds_d 20
#define controller_thread_wait_timeout_4_nanoseconds_d 0
+ #define controller_thread_exit_helper_timeout_seconds_d 0
+ #define controller_thread_exit_helper_timeout_nanoseconds_d 100000000 // 0.1 seconds in nanoseconds.
+
#define controller_thread_exit_ready_timeout_seconds_d 0
#define controller_thread_exit_ready_timeout_nanoseconds_d 500000000 // 0.5 seconds in nanoseconds.
}
++cache->ats.array[at_j];
+
break;
}
}
}
// Check to see if any required processes failed, but do not do this if already operating in failsafe.
- if (F_status_is_error_not(status) && !failsafe && !(global->main->parameters.array[controller_parameter_validate_e].result & f_console_result_found_e)) {
+ if (F_status_is_error_not(status) && !failsafe && !(global->main->parameters.array[controller_parameter_validate_e].result & f_console_result_found_e) && global->setting->mode != controller_setting_mode_helper_e) {
const f_status_t status_wait = controller_rule_wait_all(*global, is_entry, F_true);
if (F_status_is_error(status_wait)) return status_wait;
if (status_wait == F_require) return F_status_set_error(F_require);
if (fl_string_dynamic_partial_compare_string(controller_service_s.string, cache->buffer_file, controller_service_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
global.setting->mode = controller_setting_mode_service_e;
}
+ else if (fl_string_dynamic_partial_compare_string(controller_helper_s.string, cache->buffer_file, controller_helper_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
+ global.setting->mode = controller_setting_mode_helper_e;
+ }
else if (fl_string_dynamic_partial_compare_string(controller_program_s.string, cache->buffer_file, controller_program_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
global.setting->mode = controller_setting_mode_program_e;
}
f_thread_unlock(&process_list[i]->active);
if (f_thread_lock_write_try(&process_list[i]->active) == F_none) {
-
controller_thread_join(&process_list[i]->id_thread);
process_list[i]->state = controller_process_state_idle_e;
}
#endif // _di_controller_thread_cleanup_
+#ifndef _di_controller_thread_detach_
+ f_status_t controller_thread_detach(f_thread_id_t * const id) {
+
+ if (!id || !*id) return F_data_not;
+
+ const f_status_t status = f_thread_detach(*id);
+
+ if (F_status_is_error_not(status) || F_status_set_fine(status) == F_found_not) {
+ *id = 0;
+ }
+
+ return status;
+ }
+#endif // _di_controller_thread_detach_
+
#ifndef _di_controller_thread_is_enabled_
f_status_t controller_thread_is_enabled(const bool is_normal, controller_thread_t * const thread) {
if (setting->mode == controller_setting_mode_service_e) {
controller_thread_join(&thread.id_signal);
}
+ else if (setting->mode == controller_setting_mode_helper_e) {
+ status = controller_rule_wait_all(global, F_true, F_false, 0);
+ }
else if (setting->mode == controller_setting_mode_program_e) {
status = controller_rule_wait_all(global, F_true, F_false);
}
extern void * controller_thread_cleanup(void * const arguments) F_attribute_visibility_internal_d;
#endif // _di_controller_thread_cleanup_
+/***
+ * Detach a thread, assigning id to NULL on success.
+ *
+ * If the ID is not found, then it is also set to NULL.
+ *
+ * This should be called for asynchronous processes.
+ *
+ * @param id
+ * The thread ID.
+ *
+ * @return
+ * F_none on success.
+ *
+ * Success from: f_thread_detach().
+ *
+ * Errors (with error bit) from: f_thread_detach().
+ *
+ * @see f_thread_detach()
+ */
+#ifndef _di_controller_thread_detach_
+ extern f_status_t controller_thread_detach(f_thread_id_t * const id) F_attribute_visibility_internal_d;
+#endif // _di_controller_thread_detach_
+
/**
* Check to see if thread is enabled for the normal operations like entry and control or for exit operations.
*
#include "../lock/private-lock_print.h"
#include "../thread/private-thread.h"
#include "private-thread_entry.h"
+#include "private-thread_process.h"
#include "private-thread_signal.h"
#ifdef __cplusplus
entry->setting->ready = controller_setting_ready_done_e;
}
}
+
+ if (F_status_is_error_not(*status) && *status != F_child && entry->global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e && entry->global->setting->mode == controller_setting_mode_helper_e) {
+ struct timespec time;
+ time.tv_sec = controller_thread_exit_helper_timeout_seconds_d;
+ time.tv_nsec = controller_thread_exit_helper_timeout_nanoseconds_d;
+
+ nanosleep(&time, 0);
+
+ controller_thread_process_cancel(*(entry->global), F_true, controller_thread_cancel_exit_e, 0);
+ }
}
}
// Only cancel when enabled.
if (!controller_thread_is_enabled(is_normal, global.thread)) {
-
f_thread_mutex_unlock(&global.thread->lock.cancel);
return;
}
- // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock).
- f_status_t status = f_thread_mutex_lock(&global.thread->lock.alert);
-
- if (F_status_is_error(status)) {
- global.thread->enabled = controller_thread_enabled_not_e;
- }
- else {
- if (by == controller_thread_cancel_execute_e) {
- global.thread->enabled = controller_thread_enabled_execute_e;
- }
- else if (by == controller_thread_cancel_exit_e) {
- global.thread->enabled = controller_thread_enabled_not_e;
- }
- else if (by == controller_thread_cancel_exit_execute_e) {
- global.thread->enabled = controller_thread_enabled_exit_execute_e;
- }
- else {
- global.thread->enabled = controller_thread_enabled_exit_e;
- }
-
- f_thread_mutex_unlock(&global.thread->lock.alert);
- }
-
struct timespec time;
controller_entry_t *entry = 0;
controller_process_t *process = 0;
+ f_status_t status = F_none;
f_array_length_t i = 0;
f_array_length_t j = 0;
pid_t pid = 0;
time.tv_sec = 0;
time.tv_nsec = interval_nanoseconds;
+ if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) {
+ int value = 0;
+ f_number_unsigned_t lapsed = 0;
+
+ for (i = 0; i < global.thread->processs.used; ++i) {
+
+ if (!global.thread->processs.array[i]) continue;
+ if (caller && i == caller->id) continue;
+
+ process = global.thread->processs.array[i];
+
+ if (!process->id_thread) continue;
+
+ controller_thread_detach(&process->id_thread);
+
+ process->id_thread = 0;
+ } // for
+ }
+
+ // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock).
+ status = f_thread_mutex_lock(&global.thread->lock.alert);
+
+ if (F_status_is_error(status)) {
+ global.thread->enabled = controller_thread_enabled_not_e;
+ }
+ else {
+ if (by == controller_thread_cancel_execute_e) {
+ global.thread->enabled = controller_thread_enabled_execute_e;
+ }
+ else if (by == controller_thread_cancel_exit_e) {
+ global.thread->enabled = controller_thread_enabled_not_e;
+ }
+ else if (by == controller_thread_cancel_exit_execute_e) {
+ global.thread->enabled = controller_thread_enabled_exit_execute_e;
+ }
+ else {
+ global.thread->enabled = controller_thread_enabled_exit_e;
+ }
+
+ f_thread_mutex_unlock(&global.thread->lock.alert);
+ }
+
if (global.thread->id_cleanup) {
f_thread_cancel(global.thread->id_cleanup);
f_thread_join(global.thread->id_cleanup, 0);
global.thread->id_signal = 0;
}
+ if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) {
+ f_thread_mutex_unlock(&global.thread->lock.cancel);
+
+ return;
+ }
+
for (; i < global.thread->processs.used; ++i) {
if (!global.thread->processs.array[i]) continue;
- The code:"mode" setting\:
Represents the mode in which the Entry is operating in.
- The following modes are supported: code:"program" and code:"service".
+ The following modes are supported: code:"helper", code:"program", and code:"service".
+
+ - The code:"helper" mode\:
+ Designates that the Entry operates as a helper for starting programs or performing actions and exits when complete.
+ On exit, any background (asynchronous) processes are not cancelled.
+ If terminated, the foreground (synchronous) process is cancelled.
+ Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit".
+ Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process).
- The code:"program" mode\:
Designates that the Entry operates as a program and exits when complete.
+ On exit, any background (asynchronous) processes are also cancelled.
+ If terminated, the foreground (synchronous) process is cancelled.
Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit".
Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process).
- code:"control_mode": Exactly one Content that is a valid file mode.
- code:"control_user": Exactly one Content that is a user name or user id.
- code:"define": Two Content, the first Content must be a case-sensitive valid environment variable name (alpha-numeric or underscore, but no leading digits).
- - code:"mode": Exactly one Content that is one of code:"program" or code:"service".
+ - code:"mode": Exactly one Content that is one of code:"helper", code:"program", or code:"service".
- code:"parameter": Two Content, the first Content must be a case-sensitive valid IKI name and the second being an IKI value.
- code:"pid": Exactly one Content that is one of code:"disable", code:"require", or code:"ready".
- code:"pid_file": Exactly one Content that is a relative or absolute path to a pid file.