]> 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:07:50 +0000 (23:07 -0600)
committerKevin Day <kevin@kevux.org>
Thu, 9 Mar 2023 05:07:50 +0000 (23:07 -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 225fbb9e5a9080c4c3c713eeb1fbd92854592e22..c8f53bf341599ba01727de739081ab0fe7bae2c4 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 126196c955d14e732f6b89042a8aa618c25c7dde..8a09e7c53acfb7e0822e7feb8682fadbc9d6684f 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 cf373f3dffd3b4c44aa4388f7de153b8e0e7386e..e22f5fda57fba4b02260262e55a782907222b9e5 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 8e2141636f72628e4a1c3ef5d9a2e72130046c2f..96554ce030e043cb657fc52c9f1c184d7529c7ec 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 d1e8c92d3a51ade079a579bf927d177f8fef385e..a556fea3492e48db5438a56403db6e198a9c8982 100644 (file)
@@ -1251,6 +1251,7 @@ extern "C" {
                 }
 
                 ++cache->ats.array[at_j];
+
                 break;
               }
             }
@@ -1485,7 +1486,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_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);
@@ -2070,6 +2071,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 8c54cb92de4605d4bcdaccdeeb54570e6e8673d2..7be8d861893d0159927baf2ed67958f53bbbdb36 100644 (file)
@@ -6272,7 +6272,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 da90885fd899158d59dc82876261ff899bc231dc..e30de085c47fcefe25ae16c3fd50dd3d9a249e14 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);
       }
index dbf6ed2db84f9af030cd939b8f01c61ed572ad1e..2d80f2946f7aeba1b5b7ce25408bc038871c16e8 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 a231e28b21ddc6025d2aeb2b616c34e329ab740f..65d36572c3e2e37a5566508bcd6a8784ab9dc0d3 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 f59f02b372939fe36c5a276f8d20c739af29e7ef..4d92d9d82e02a42e7dab39e830072d8a033b57ea 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.