]> Kevux Git Server - fll/commitdiff
Feature: Add support for 'helper' mode to compliment the 'program' mode.
authorKevin Day <kevin@kevux.org>
Thu, 9 Mar 2023 05:00:33 +0000 (23:00 -0600)
committerKevin Day <kevin@kevux.org>
Thu, 9 Mar 2023 05:00:33 +0000 (23:00 -0600)
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.

13 files changed:
level_3/controller/c/common/private-common.c
level_3/controller/c/common/private-common.h
level_3/controller/c/common/private-process.h
level_3/controller/c/common/private-setting.h
level_3/controller/c/common/private-thread.h
level_3/controller/c/entry/private-entry.c
level_3/controller/c/rule/private-rule.c
level_3/controller/c/thread/private-thread.c
level_3/controller/c/thread/private-thread.h
level_3/controller/c/thread/private-thread_entry.c
level_3/controller/c/thread/private-thread_process.c
level_3/controller/documents/entry.txt
level_3/controller/specifications/entry.txt

index 4bdee30f3e726096f1ea21f2e64e49e58050f378..18aeb4485626673bcdd1d1bb74fe4954613e117f 100644 (file)
@@ -49,6 +49,7 @@ extern "C" {
   const f_string_static_t controller_full_path_s = macro_f_string_static_t_initialize(CONTROLLER_full_path_s, 0, CONTROLLER_full_path_s_length);
   const f_string_static_t controller_group_s = macro_f_string_static_t_initialize(CONTROLLER_group_s, 0, CONTROLLER_group_s_length);
   const f_string_static_t controller_groups_s = macro_f_string_static_t_initialize(CONTROLLER_groups_s, 0, CONTROLLER_groups_s_length);
+  const f_string_static_t controller_helper_s = macro_f_string_static_t_initialize(CONTROLLER_helper_s, 0, CONTROLLER_helper_s_length);
   const f_string_static_t controller_how_s = macro_f_string_static_t_initialize(CONTROLLER_how_s, 0, CONTROLLER_how_s_length);
   const f_string_static_t controller_idle_s = macro_f_string_static_t_initialize(CONTROLLER_idle_s, 0, CONTROLLER_idle_s_length);
   const f_string_static_t controller_iki_s = macro_f_string_static_t_initialize(CONTROLLER_iki_s, 0, CONTROLLER_iki_s_length);
index 8b68e0488afc7a00db3523781013a5c5950c1dec..01a47df3a63fbe32e84a9de945ded85bbaaeb870 100644 (file)
@@ -73,6 +73,7 @@ extern "C" {
   #define CONTROLLER_full_path_s     "full_path"
   #define CONTROLLER_group_s         "group"
   #define CONTROLLER_groups_s        "groups"
+  #define CONTROLLER_helper_s        "helper"
   #define CONTROLLER_how_s           "how"
   #define CONTROLLER_idle_s          "idle"
   #define CONTROLLER_iki_s           "iki"
@@ -204,6 +205,7 @@ extern "C" {
   #define CONTROLLER_full_path_s_length     9
   #define CONTROLLER_group_s_length         5
   #define CONTROLLER_groups_s_length        6
+  #define CONTROLLER_helper_s_length        6
   #define CONTROLLER_how_s_length           3
   #define CONTROLLER_idle_s_length          4
   #define CONTROLLER_iki_s_length           3
@@ -335,6 +337,7 @@ extern "C" {
   extern const f_string_static_t controller_full_path_s;
   extern const f_string_static_t controller_group_s;
   extern const f_string_static_t controller_groups_s;
+  extern const f_string_static_t controller_helper_s;
   extern const f_string_static_t controller_how_s;
   extern const f_string_static_t controller_idle_s;
   extern const f_string_static_t controller_iki_s;
index 7ccdf642a7017c45f479826a1bb297ddbc5bfc4d..eb33b73f2350b9b9ad7fe25ea9215069f0720eb1 100644 (file)
@@ -60,7 +60,7 @@ extern "C" {
  * cache:        The cache used in this process.
  * child:        The process id of a child process, if one is running (when forking to execute a child process).
  * lock:         A read/write lock on the structure.
- * options:      Configuration options for this asynchronous thread.
+ * options:      Configuration options for this thread.
  * result:       The last return code from an execution of a process.
  * rule:         A copy of the rule actively being executed.
  * stack:        A stack used to represent dependencies as Rule ID's to avoid circular rule dependencies (If Rule A waits on Rule B, then Rule B must not wait on Rule A).
index fdf3f5619523af697a7a3435413b8f6d12d35fc9..8a50bd991521ee25640eaed9968f6a43e118afb5 100644 (file)
@@ -24,6 +24,7 @@ extern "C" {
  *   - abort: Abort received before finished processing Entry/Exit.
  *
  * controller_setting_mode_*:
+ *   - helper:  Run as a helper, exiting when finished prrocess entry (and any respective exit).
  *   - program: Run as a program, exiting when finished prrocess entry (and any respective exit).
  *   - service: Run as a service, listening for requests after processing entry.
  *
@@ -52,18 +53,19 @@ extern "C" {
     controller_setting_ready_done_e,
     controller_setting_ready_fail_e,
     controller_setting_ready_abort_e,
-  };
+  }; // enum
 
   enum {
     controller_setting_mode_service_e = 0,
+    controller_setting_mode_helper_e,
     controller_setting_mode_program_e,
-  };
+  }; // enum
 
   enum {
     controller_setting_flag_interruptible_e = 0x1,
     controller_setting_flag_pid_created_e   = 0x2,
     controller_setting_flag_failsafe_e      = 0x4,
-  };
+  }; // enum
 
   typedef struct {
     uint8_t flag;
index 6cb0fe2870e875b1a7918acc1e62f898f97542cd..6b48d1ccd27ffde63b7a7602ac7abbb228a8363b 100644 (file)
@@ -60,6 +60,9 @@ extern "C" {
   #define controller_thread_wait_timeout_4_seconds_d     20
   #define controller_thread_wait_timeout_4_nanoseconds_d 0
 
+  #define controller_thread_exit_helper_timeout_seconds_d     0
+  #define controller_thread_exit_helper_timeout_nanoseconds_d 100000000 // 0.1 seconds in nanoseconds.
+
   #define controller_thread_exit_ready_timeout_seconds_d     0
   #define controller_thread_exit_ready_timeout_nanoseconds_d 500000000 // 0.5 seconds in nanoseconds.
 
index 242bfb73d8d91477db28ca1e0cf6d313676cbd6f..c9cf31ddef90bb6773a474215ac8b1310a0455c1 100644 (file)
@@ -1251,6 +1251,7 @@ extern "C" {
                 }
 
                 ++cache->ats.array[at_j];
+
                 break;
               }
             }
@@ -1487,7 +1488,7 @@ extern "C" {
     }
 
     // Check to see if any required processes failed, but do not do this if already operating in failsafe.
-    if (F_status_is_error_not(status) && !failsafe && global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) {
+    if (F_status_is_error_not(status) && !failsafe && global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e && global->setting->mode != controller_setting_mode_helper_e) {
       const f_status_t status_wait = controller_rule_wait_all(*global, is_entry, F_true, 0);
 
       if (F_status_is_error(status_wait)) {
@@ -2078,6 +2079,9 @@ extern "C" {
         if (fl_string_dynamic_partial_compare_string(controller_service_s.string, cache->buffer_file, controller_service_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
           global.setting->mode = controller_setting_mode_service_e;
         }
+        else if (fl_string_dynamic_partial_compare_string(controller_helper_s.string, cache->buffer_file, controller_helper_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
+          global.setting->mode = controller_setting_mode_helper_e;
+        }
         else if (fl_string_dynamic_partial_compare_string(controller_program_s.string, cache->buffer_file, controller_program_s.used, cache->content_actions.array[i].array[0]) == F_equal_to) {
           global.setting->mode = controller_setting_mode_program_e;
         }
index 5b098dc318720eb40ae559881d7f32c4f590ab04..790518225c68cec75c77e73422cbf017cd843680 100644 (file)
@@ -6353,7 +6353,6 @@ extern "C" {
             f_thread_unlock(&process_list[i]->active);
 
             if (f_thread_lock_write_try(&process_list[i]->active) == F_none) {
-
               controller_thread_join(&process_list[i]->id_thread);
 
               process_list[i]->state = controller_process_state_idle_e;
index d5b401a37c34319b49a57cd6773b071c8f8f98a2..7ccd06a8026400e64b87c89b6146193b39cd01d5 100644 (file)
@@ -154,6 +154,21 @@ extern "C" {
   }
 #endif // _di_controller_thread_cleanup_
 
+#ifndef _di_controller_thread_detach_
+  f_status_t controller_thread_detach(f_thread_id_t * const id) {
+
+    if (!id || !*id) return F_data_not;
+
+    const f_status_t status = f_thread_detach(*id);
+
+    if (F_status_is_error_not(status) || F_status_set_fine(status) == F_found_not) {
+      *id = 0;
+    }
+
+    return status;
+  }
+#endif // _di_controller_thread_detach_
+
 #ifndef _di_controller_thread_is_enabled_
   f_status_t controller_thread_is_enabled(const bool is_normal, controller_thread_t * const thread) {
 
@@ -291,6 +306,9 @@ extern "C" {
       if (setting->mode == controller_setting_mode_service_e) {
         controller_thread_join(&thread.id_signal);
       }
+      else if (setting->mode == controller_setting_mode_helper_e) {
+        status = controller_rule_wait_all(global, F_true, F_false, 0);
+      }
       else if (setting->mode == controller_setting_mode_program_e) {
         status = controller_rule_wait_all(global, F_true, F_false, 0);
       }
index 8137bce4856771180c7b013b655539e1470699f2..47841f4752d3640559dd4a1a68677ee9cae34693 100644 (file)
@@ -26,6 +26,29 @@ extern "C" {
   extern void * controller_thread_cleanup(void * const arguments) F_attribute_visibility_internal_d;
 #endif // _di_controller_thread_cleanup_
 
+/***
+ * Detach a thread, assigning id to NULL on success.
+ *
+ * If the ID is not found, then it is also set to NULL.
+ *
+ * This should be called for asynchronous processes.
+ *
+ * @param id
+ *   The thread ID.
+ *
+ * @return
+ *   F_none on success.
+ *
+ *   Success from: f_thread_detach().
+ *
+ *   Errors (with error bit) from: f_thread_detach().
+ *
+ * @see f_thread_detach()
+ */
+#ifndef _di_controller_thread_detach_
+  extern f_status_t controller_thread_detach(f_thread_id_t * const id) F_attribute_visibility_internal_d;
+#endif // _di_controller_thread_detach_
+
 /**
  * Check to see if thread is enabled for the normal operations like entry and control or for exit operations.
  *
index ba707b64b141900ebf01656aca849a45ef2361f1..25ef26e83e213d2e9870b7ef446516aba04dbb17 100644 (file)
@@ -4,6 +4,7 @@
 #include "../lock/private-lock_print.h"
 #include "../thread/private-thread.h"
 #include "private-thread_entry.h"
+#include "private-thread_process.h"
 #include "private-thread_signal.h"
 
 #ifdef __cplusplus
@@ -112,6 +113,16 @@ extern "C" {
             entry->setting->ready = controller_setting_ready_done_e;
           }
         }
+
+        if (F_status_is_error_not(*status) && *status != F_child && entry->global->main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e && entry->global->setting->mode == controller_setting_mode_helper_e) {
+          struct timespec time;
+          time.tv_sec = controller_thread_exit_helper_timeout_seconds_d;
+          time.tv_nsec = controller_thread_exit_helper_timeout_nanoseconds_d;
+
+          nanosleep(&time, 0);
+
+          controller_thread_process_cancel(*(entry->global), F_true, controller_thread_cancel_exit_e, 0);
+        }
       }
     }
 
index 33fd192970a22c29c27e0240fe6e15dac997ef0d..2720596ea850fcab962e6e13b772a5bc96a344b8 100644 (file)
@@ -49,40 +49,17 @@ extern "C" {
 
     // Only cancel when enabled.
     if (!controller_thread_is_enabled(is_normal, global.thread)) {
-
       f_thread_mutex_unlock(&global.thread->lock.cancel);
 
       return;
     }
 
-    // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock).
-    f_status_t status = f_thread_mutex_lock(&global.thread->lock.alert);
-
-    if (F_status_is_error(status)) {
-      global.thread->enabled = controller_thread_enabled_not_e;
-    }
-    else {
-      if (by == controller_thread_cancel_execute_e) {
-        global.thread->enabled = controller_thread_enabled_execute_e;
-      }
-      else if (by == controller_thread_cancel_exit_e) {
-        global.thread->enabled = controller_thread_enabled_not_e;
-      }
-      else if (by == controller_thread_cancel_exit_execute_e) {
-        global.thread->enabled = controller_thread_enabled_exit_execute_e;
-      }
-      else {
-        global.thread->enabled = controller_thread_enabled_exit_e;
-      }
-
-      f_thread_mutex_unlock(&global.thread->lock.alert);
-    }
-
     struct timespec time;
 
     controller_entry_t *entry = 0;
     controller_process_t *process = 0;
 
+    f_status_t status = F_none;
     f_array_length_t i = 0;
     f_array_length_t j = 0;
     pid_t pid = 0;
@@ -101,6 +78,48 @@ extern "C" {
     time.tv_sec = 0;
     time.tv_nsec = interval_nanoseconds;
 
+    if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) {
+      int value = 0;
+      f_number_unsigned_t lapsed = 0;
+
+      for (i = 0; i < global.thread->processs.used; ++i) {
+
+        if (!global.thread->processs.array[i]) continue;
+        if (caller && i == caller->id) continue;
+
+        process = global.thread->processs.array[i];
+
+        if (!process->id_thread) continue;
+
+        controller_thread_detach(&process->id_thread);
+
+        process->id_thread = 0;
+      } // for
+    }
+
+    // Use the alert lock to toggle enabled (using it as if it is a write like and a signal lock).
+    status = f_thread_mutex_lock(&global.thread->lock.alert);
+
+    if (F_status_is_error(status)) {
+      global.thread->enabled = controller_thread_enabled_not_e;
+    }
+    else {
+      if (by == controller_thread_cancel_execute_e) {
+        global.thread->enabled = controller_thread_enabled_execute_e;
+      }
+      else if (by == controller_thread_cancel_exit_e) {
+        global.thread->enabled = controller_thread_enabled_not_e;
+      }
+      else if (by == controller_thread_cancel_exit_execute_e) {
+        global.thread->enabled = controller_thread_enabled_exit_execute_e;
+      }
+      else {
+        global.thread->enabled = controller_thread_enabled_exit_e;
+      }
+
+      f_thread_mutex_unlock(&global.thread->lock.alert);
+    }
+
     if (global.thread->id_cleanup) {
       f_thread_cancel(global.thread->id_cleanup);
       f_thread_join(global.thread->id_cleanup, 0);
@@ -123,6 +142,12 @@ extern "C" {
       global.thread->id_signal = 0;
     }
 
+    if (global.setting->mode == controller_setting_mode_helper_e && global.main->parameters.array[controller_parameter_validate_e].result == f_console_result_none_e) {
+      f_thread_mutex_unlock(&global.thread->lock.cancel);
+
+      return;
+    }
+
     for (; i < global.thread->processs.used; ++i) {
 
       if (!global.thread->processs.array[i]) continue;
index 891a134a23d2a3e9c7c246bb2e715721dbd9212a..394fd95826e898a161a37156313e4f8435120ac8 100644 (file)
@@ -60,10 +60,19 @@ Entry Documentation:
 
     - The code:"mode" setting\:
       Represents the mode in which the Entry is operating in.
-      The following modes are supported: code:"program" and code:"service".
+      The following modes are supported: code:"helper", code:"program", and code:"service".
+
+      - The code:"helper" mode\:
+        Designates that the Entry operates as a helper for starting programs or performing actions and exits when complete.
+        On exit, any background (asynchronous) processes are not cancelled.
+        If terminated, the foreground (synchronous) process is cancelled.
+        Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit".
+        Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process).
 
       - The code:"program" mode\:
         Designates that the Entry operates as a program and exits when complete.
+        On exit, any background (asynchronous) processes are also cancelled.
+        If terminated, the foreground (synchronous) process is cancelled.
         Will call the code:"exit" with the same name as this Entry, but with the extension code:"exit", such as code:"default.exit".
         Supports the Item Action code:"execute" to execute a program (switching the code:"controller" program entirely with the executed process).
 
index ef38cdf8a0193e45070b04c4223dc235b148a664..92ceeea8e87f5aa4c42d78fd031a63d6ce6bbc14 100644 (file)
@@ -33,7 +33,7 @@ Entry Specification:
       - code:"control_mode": Exactly one Content that is a valid file mode.
       - code:"control_user": Exactly one Content that is a user name or user id.
       - code:"define": Two Content, the first Content must be a case-sensitive valid environment variable name (alpha-numeric or underscore, but no leading digits).
-      - code:"mode": Exactly one Content that is one of code:"program" or code:"service".
+      - code:"mode": Exactly one Content that is one of code:"helper", code:"program", or code:"service".
       - code:"parameter": Two Content, the first Content must be a case-sensitive valid IKI name and the second being an IKI value.
       - code:"pid": Exactly one Content that is one of code:"disable", code:"require", or code:"ready".
       - code:"pid_file": Exactly one Content that is a relative or absolute path to a pid file.