]> Kevux Git Server - fll/commitdiff
Progress: controller program and related.
authorKevin Day <thekevinday@gmail.com>
Tue, 26 Jan 2021 03:24:13 +0000 (21:24 -0600)
committerKevin Day <thekevinday@gmail.com>
Tue, 26 Jan 2021 03:33:25 +0000 (21:33 -0600)
Add f_signal_send().
Cleanup some of the f_signal code.

Add "return" mode option to fl_execute.

This adds more thread related changes, much if this I am semi-experimenting.
I will likely do a post review and try to clean it up and remove anything unnecessary.

One thing in particular that I am trying is saving the child process PID for a foreground process.
This can then be manually sent a termination signal on exit.

The program should exit in certain validation modes.

Cleanup f_environment logic in one function.

level_0/f_environment/c/private-environment.c
level_0/f_signal/c/signal.c
level_0/f_signal/c/signal.h
level_1/fl_execute/c/execute-common.h
level_2/fll_execute/c/execute.h
level_2/fll_execute/c/private-execute.c
level_2/fll_execute/c/private-execute.h
level_3/controller/c/private-common.h
level_3/controller/c/private-controller.c
level_3/controller/c/private-rule.c
level_3/controller/c/private-thread.c

index b32841552404fde7d82b0dee43addc9555e24b74..c102d377a137c54c49ad476852c8aa6248c43d12 100644 (file)
@@ -15,10 +15,7 @@ extern "C" {
 
     const f_string_length_t size = strnlen(result, f_environment_max_length);
 
-    if (!size) {
-      value->used = 0;
-    }
-    else {
+    if (size) {
       if (value->used + size > f_environment_max_length) {
         return F_status_set_error(F_string_too_large);
       }
@@ -33,6 +30,9 @@ extern "C" {
       memcpy(value->string + value->used, result, value->used + size);
       value->used = size;
     }
+    else {
+      value->used = 0;
+    }
 
     return F_none;
   }
index 76609c68ee8e2f490db3368493acbf5961454fb1..d046c87fa2d925c45d01f3392aa07712343421db 100644 (file)
@@ -99,6 +99,21 @@ extern "C" {
   }
 #endif // _di_f_signal_read_
 
+#ifndef _di_f_signal_send_
+  f_status_t f_signal_send(const int signal, const pid_t process_id) {
+
+    if (kill(process_id, signal) < 0) {
+      if (errno == EINVAL) return F_status_set_error(F_parameter);
+      if (errno == EPERM) return F_status_set_error(F_prohibited);
+      if (errno == ESRCH) return F_status_set_error(F_found_not);
+
+      return F_status_set_error(F_failure);
+    }
+
+    return F_none;
+  }
+#endif // _di_f_signal_send_
+
 #ifndef _di_f_signal_set_add_
   f_status_t f_signal_set_add(const int signal, sigset_t *set) {
     #ifndef _di_level_0_parameter_checking_
@@ -106,9 +121,7 @@ extern "C" {
     #endif // _di_level_0_parameter_checking_
 
     if (sigaddset(set, signal) < 0) {
-      if (errno == EINVAL) {
-        return F_status_set_error(F_parameter);
-      }
+      if (errno == EINVAL) return F_status_set_error(F_parameter);
 
       return F_status_set_error(F_failure);
     }
@@ -124,9 +137,7 @@ extern "C" {
     #endif // _di_level_0_parameter_checking_
 
     if (sigdelset(set, signal) < 0) {
-      if (errno == EINVAL) {
-        return F_status_set_error(F_parameter);
-      }
+      if (errno == EINVAL) return F_status_set_error(F_parameter);
 
       return F_status_set_error(F_failure);
     }
@@ -142,9 +153,7 @@ extern "C" {
     #endif // _di_level_0_parameter_checking_
 
     if (sigemptyset(set) < 0) {
-      if (errno == EINVAL) {
-        return F_status_set_error(F_parameter);
-      }
+      if (errno == EINVAL) return F_status_set_error(F_parameter);
 
       return F_status_set_error(F_failure);
     }
index 625bbffe7e5f421612ab0db8df9a907ad3aa339a..d3370ecc7dc2877f231f6083121b0b31b2e00387 100644 (file)
@@ -86,6 +86,7 @@ extern "C" {
  *   F_none on success but no signal found.
  *   F_data_not on success, but no descriptor was provided to read.
  *   F_signal on success and signal found.
+ *
  *   F_block (with error bit) if file descriptor is set to non-block and the read would result in a blocking operation.
  *   F_buffer (with error bit) if the buffer is invalid.
  *   F_descriptor (with error bit) if the signal descriptor is invalid.
@@ -104,6 +105,29 @@ extern "C" {
 #endif // _di_f_signal_read_
 
 /**
+ * Send a signal to a process.
+ *
+ * @param signal
+ *   The signal to send
+ * @param process_id
+ *   The process id (PID) that will receive the signal.
+ *   This may also be a process group id.
+ *
+ * @return
+ *   F_none on success but no signal found.
+ *
+ *   F_parameter (with error bit) if a parameter is invalid.
+ *   F_prohibited (with error bit) if not allowed to send signals to the given process.
+ *   F_found_not (with error bit) if the given process was not found.
+ *   F_failure (with error bit) for any other error.
+ *
+ * @see kill()
+ */
+#ifndef _di_f_signal_send_
+  extern f_status_t f_signal_send(const int signal, const pid_t process_id);
+#endif // _di_f_signal_send_
+
+/**
  * Add a signal to the given set of signals.
  *
  * @param signal
@@ -113,6 +137,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
@@ -132,6 +157,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
@@ -149,6 +175,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
@@ -166,6 +193,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
@@ -192,6 +220,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
@@ -213,6 +242,7 @@ extern "C" {
  *
  * @return
  *   F_none on success but no signal found.
+ *
  *   F_found_not (with error bit) if the given PID was found.
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_resource_not (with error bit) if the max signals is reached.
@@ -236,6 +266,7 @@ extern "C" {
  * @return
  *   F_true if signal is found.
  *   F_false if signal is not found.
+ *
  *   F_parameter (with error bit) if a parameter is invalid.
  *   F_failure (with error bit) for any other error.
  *
index dbbacce9e4063b9a558827ce27560dd52675d5c4..dbfb96cbc76e4b72ac7e8a534e6cd243d075c505 100644 (file)
@@ -23,6 +23,7 @@ extern "C" {
  *   fl_execute_parameter_option_exit:       used to desginate to exit after calling child otherwise child process will return.
  *   fl_execute_parameter_option_path:       used to designate that this is a path to a program (such as '/bin/bash').
  *   fl_execute_parameter_option_threadsafe: used to designate that threadsafe functions are to be used (such as: f_thread_signal_mask instead of f_signal_mask).
+ *   fl_execute_parameter_option_return:     used to designate that the parent process will immediately return instead of waiting for the child process to complete.
  *
  * If thread support is disabled in the library, then fl_execute_parameter_option_threadsafe will fallback to non-threadsafe.
  *
@@ -35,6 +36,7 @@ extern "C" {
   #define fl_execute_parameter_option_exit       0x1
   #define fl_execute_parameter_option_path       0x2
   #define fl_execute_parameter_option_threadsafe 0x4
+  #define fl_execute_parameter_option_return     0x8
 
   typedef struct {
     uint8_t option;
index a9006b6ded156cf970b134a54ef45787b82e3c18..7b9c3000e53502208ac961cd663e7bef3011fdba 100644 (file)
@@ -368,6 +368,7 @@ extern "C" {
  *
  * @return
  *   F_none on success.
+ *
  *   F_failure (with error bit) on execution failure.
  *
  * @see execv()
@@ -426,13 +427,17 @@ extern "C" {
  *     A pointer to a string to pipe as standard input to the child process.
  *     The parent will block until the standard input is fully read or the child process exits.
  * @param as
- *   (optional) This and most of its fields are optional and are disabled when set to 0.
+ *   (optional) This and most of its fields are optional and are disabled when set to NULL.
  * @param result
- *   The code returned after finishing execution of program.
+ *   (optional) The code returned after finishing execution of program.
+ *   When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ *   Set to NULL to not use.
  *
  * @return
  *   F_none on success.
- *   F_child on success but this is the child thread.
+ *   F_child on success and this is the child thread.
+ *   F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
  *   F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
  *   F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
  *   F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
index 7b98241d4d720e860313122a8ffd3235cff0127a..3bf527d435bea491d7d1434e843cb1b3a53991d2 100644 (file)
@@ -309,6 +309,15 @@ extern "C" {
         }
       }
 
+      if (parameter && parameter->option & fl_execute_parameter_option_return) {
+
+        if (result != 0) {
+          *result = id_process;
+        }
+
+        return F_parent;
+      }
+
       // have the parent wait for the child process to finish.
       waitpid(id_process, result, WUNTRACED | WCONTINUED);
 
@@ -469,6 +478,15 @@ extern "C" {
         }
       }
 
+      if (parameter && parameter->option & fl_execute_parameter_option_return) {
+
+        if (result != 0) {
+          *result = id_process;
+        }
+
+        return F_parent;
+      }
+
       // have the parent wait for the child process to finish.
       waitpid(id_process, result, WUNTRACED | WCONTINUED);
 
index d24136c9c3e2af6f3729d288271a364f52aefac5..1324303662bbd2363c3db7db4a46fdd15b956f36 100644 (file)
@@ -106,6 +106,7 @@ extern "C" {
  *
  * @return
  *   F_none on success.
+ *
  *   F_capability (with error bit) on failure to set capabilities.
  *   F_group (with error bit) on failure to set GID.
  *   F_nice (with error bit) on failure to set process niceness.
@@ -146,6 +147,7 @@ extern "C" {
  *
  * @return
  *   F_none on success.
+ *
  *   F_control_group (with error bit) on failure to set control group.
  *   F_limit (with error bit) on failure to set a resource limit.
  *   F_processor (with error bit) on failure to set processor (cpu) affinity.
@@ -196,13 +198,17 @@ extern "C" {
  *     A pointer to a string to pipe as standard input to the child process.
  *     The parent will block until the standard input is fully read or the child process exits.
  * @param as
- *   (optional) This and most of its fields are optional and are disabled when set to 0.
+ *   (optional) This and most of its fields are optional and are disabled when set to NULL.
  * @param result
- *   The code returned after finishing execution of program.
+ *   (optional) The code returned after finishing execution of program.
+ *   When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ *   Set to NULL to not use.
  *
  * @return
  *   F_none on success.
- *   F_child on success but this is the child thread.
+ *   F_child on success and this is the child thread.
+ *   F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
  *   F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
  *   F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
  *   F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
@@ -264,13 +270,17 @@ extern "C" {
  *     A pointer to a string to pipe as standard input to the child process.
  *     The parent will block until the standard input is fully read or the child process exits.
  * @param as
- *   (optional) This and most of its fields are optional and are disabled when set to 0.
+ *   (optional) This and most of its fields are optional and are disabled when set to NULL.
  * @param result
- *   The code returned after finishing execution of program.
+ *   (optional) The code returned after finishing execution of program.
+ *   When fl_execute_parameter_option_return is passed via parameter.option, then this instead stores the child process id (PID).
+ *   Set to NULL to not use.
  *
  * @return
  *   F_none on success.
- *   F_child on success but this is the child thread.
+ *   F_child on success and this is the child thread.
+ *   F_parent on success and this is the parent thread (only happens when fl_execute_parameter_option_return is passed).
+ *
  *   F_capability (with error bit) on failure to set capabilities in the child (only the child process returns this).
  *   F_control_group (with error bit) on failure to set control group in the child (only the parent process returns this).
  *   F_child (with error bit) on any failure without an explicit failure code (like F_group) before calling execute but this is the child thread.
index 6085c7b94c1e2af4922ccc8f3cc2562a88707818..86bc79ceeba64e789cebdc49b3278be4387410f9 100644 (file)
@@ -482,6 +482,7 @@ extern "C" {
     f_status_t status;
 
     f_thread_mutex_t lock;
+    f_thread_mutex_t wait;
 
     f_number_unsigned_t timeout_kill;
     f_number_unsigned_t timeout_start;
@@ -523,6 +524,7 @@ extern "C" {
     { \
       F_known_not, \
       f_thread_mutex_t_initialize, \
+      f_thread_mutex_t_initialize, \
       0, \
       0, \
       0, \
@@ -552,6 +554,7 @@ extern "C" {
 
   #define controller_macro_rule_t_delete_simple(rule) \
     f_macro_thread_mutex_t_delete_simple(rule.lock) \
+    f_macro_thread_mutex_t_delete_simple(rule.wait) \
     f_macro_string_dynamic_t_delete_simple(rule.id) \
     f_macro_string_dynamic_t_delete_simple(rule.name) \
     f_macro_string_dynamic_t_delete_simple(rule.path) \
@@ -891,13 +894,14 @@ extern "C" {
     uint8_t state;
     uint8_t action;
     uint8_t options;
+    pid_t child;
 
     void *thread;
     f_array_lengths_t stack;
     controller_cache_action_t cache;
   } controller_asynchronous_t;
 
-  #define controller_asynchronous_t_initialize { f_thread_id_t_initialize, 0, 0, 0, 0, 0, f_array_lengths_t_initialize, controller_cache_action_t_initialize }
+  #define controller_asynchronous_t_initialize { f_thread_id_t_initialize, 0, 0, 0, 0, 0, 0, f_array_lengths_t_initialize, controller_cache_action_t_initialize }
 
   #define controller_macro_asynchronous_t_clear(asynchronous) \
     f_macro_thread_id_t_clear(asynchronous.id) \
@@ -905,6 +909,7 @@ extern "C" {
     asynchronous.state = 0; \
     asynchronous.action = 0; \
     asynchronous.options = 0; \
+    asynchronous.child = 0; \
     asynchronous.thread = 0; \
     f_macro_array_lengths_t_clear(asynchronous.stack) \
     controller_macro_cache_action_t_clear(asynchronous.cache)
@@ -916,6 +921,8 @@ extern "C" {
 
 #ifndef _di_controller_asynchronouss_t_
   typedef struct {
+    bool enabled;
+
     controller_asynchronous_t *array;
 
     f_array_length_t size;
@@ -924,6 +931,7 @@ extern "C" {
 
   #define controller_asynchronouss_t_initialize \
     { \
+      F_true, \
       0, \
       0, \
       0, \
index 02ba77e5254c190fe932f8326d64f074349049d5..755de057a3ea7ca33adfc0f44260affd6dccb293 100644 (file)
@@ -577,7 +577,6 @@ extern "C" {
     controller_entry_actions_t *actions = 0;
 
     const bool simulate = data->parameters[controller_parameter_test].result == f_console_result_found;
-    const bool validate = data->parameters[controller_parameter_validate].result == f_console_result_found;
 
     cache->ats.used = 0;
     cache->stack.used = 0;
index 2516ed3c7c255df21c93cd5f819a376b0b14231a..d6caac2901d247b88463c35f7c763c4723d1602b 100644 (file)
@@ -613,7 +613,7 @@ extern "C" {
         action = &item->actions.array[j];
 
         execute_set.parameter.data = 0;
-        execute_set.parameter.option = fl_execute_parameter_option_threadsafe;
+        execute_set.parameter.option = fl_execute_parameter_option_threadsafe & fl_execute_parameter_option_return;
 
         if (item->type == controller_rule_item_type_command) {
 
@@ -767,6 +767,24 @@ extern "C" {
       status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, &result);
     }
 
+    if (status == F_parent) {
+      controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) thread->setting->rules.array[index].asynchronous;
+
+      // assign the child process id to the asynchronous thread to allow for the cancel process to send appropriate termination signals to the child process.
+      asynchronous->child = result;
+
+      // have the parent wait for the child process to finish. (@todo see comments above about forking into the background, this code block will need to change.)
+      waitpid(asynchronous->child, &result, WUNTRACED | WCONTINUED);
+
+      // remove the pid now that waidpid() has returned.
+      asynchronous->child = 0;
+
+      // this must explicitly check for 0 (as opposed to checking (!result)).
+      if (!WIFEXITED(result)) {
+        status = F_status_set_error(F_failure);
+      }
+    }
+
     if (F_status_is_error(status)) {
       status = F_status_set_fine(status);
 
@@ -858,6 +876,24 @@ extern "C" {
       status = fll_execute_program(program, arguments, &execute_set->parameter, &execute_set->as, &result);
     }
 
+    if (status == F_parent) {
+      controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) thread->setting->rules.array[index].asynchronous;
+
+      // assign the child process id to the asynchronous thread to allow for the cancel process to send appropriate termination signals to the child process.
+      asynchronous->child = result;
+
+      // have the parent wait for the child process to finish. (@todo see comments above about forking into the background, this code block will need to change.)
+      waitpid(asynchronous->child, &result, WUNTRACED | WCONTINUED);
+
+      // remove the pid now that waidpid() has returned.
+      asynchronous->child = 0;
+
+      // this must explicitly check for 0 (as opposed to checking (!result)).
+      if (!WIFEXITED(result)) {
+        status = F_status_set_error(F_failure);
+      }
+    }
+
     if (F_status_is_error(status)) {
       status = F_status_set_fine(status);
 
@@ -1661,6 +1697,12 @@ extern "C" {
 
     f_thread_mutex_lock(&thread->mutex->asynchronous);
 
+    if (!thread->asynchronouss.enabled) {
+      f_thread_mutex_unlock(&thread->mutex->asynchronous);
+
+      return F_signal;
+    }
+
     f_status_t status = controller_asynchronouss_increase(&thread->asynchronouss);
 
     if (F_status_is_error(status)) {
@@ -3917,29 +3959,11 @@ extern "C" {
 #ifndef _di_controller_rule_wait_all_
   void controller_rule_wait_all(controller_thread_t *thread) {
 
-    f_array_length_t i = 0;
-
-    for (; i < thread->asynchronouss.used; ++i) {
+    for (f_array_length_t i = 0; i < thread->asynchronouss.used; ++i) {
 
       if (!thread->asynchronouss.array[i].state) continue;
 
-      if (thread->asynchronouss.array[i].state != controller_asynchronous_state_joined) {
-        f_thread_join(thread->asynchronouss.array[i].id, 0);
-      }
-
-      f_thread_mutex_lock(&thread->mutex->asynchronous);
-
-      if (thread->asynchronouss.array[i].state) {
-        thread->asynchronouss.array[i].state = 0;
-
-        controller_macro_cache_action_t_clear(thread->asynchronouss.array[i].cache);
-      }
-
-      if (i == thread->asynchronouss.used - 1) {
-        thread->asynchronouss.used = 0;
-      }
-
-      f_thread_mutex_unlock(&thread->mutex->asynchronous);
+      controller_rule_wait_for(i, thread);
     } // for
   }
 #endif // _di_controller_rule_wait_all_
@@ -3957,21 +3981,36 @@ extern "C" {
       return;
     }
 
-    controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) rule->asynchronous;
+    if (f_thread_mutex_lock_try(&rule->wait) == F_none) {
+      controller_asynchronous_t *asynchronous = (controller_asynchronous_t *) rule->asynchronous;
 
-    if (asynchronous->state != controller_asynchronous_state_joined) {
-      f_thread_join(asynchronous->id, 0);
-    }
+      if (asynchronous->state == controller_asynchronous_state_done) {
+        f_thread_join(asynchronous->id, 0);
+      }
 
-    f_thread_mutex_lock(&thread->mutex->asynchronous);
+      if (thread->asynchronouss.enabled) {
+        f_thread_mutex_lock(&thread->mutex->asynchronous);
+
+        if (asynchronous->state) {
+          if (asynchronous->state == controller_asynchronous_state_done) {
+            asynchronous->state = controller_asynchronous_state_joined;
+          }
+
+          controller_macro_cache_action_t_clear(asynchronous->cache);
+        }
 
-    if (asynchronous->state) {
-      asynchronous->state = 0;
+        f_thread_mutex_unlock(&thread->mutex->asynchronous);
+      }
 
-      controller_macro_cache_action_t_clear(asynchronous->cache);
+      f_thread_mutex_unlock(&rule->wait);
     }
+    else {
 
-    f_thread_mutex_unlock(&thread->mutex->asynchronous);
+      // a wait lock is already in place, which will also be responsible for thread joining.
+      // this can therefore immediately unlock and return.
+      f_thread_mutex_lock(&rule->wait);
+      f_thread_mutex_unlock(&rule->wait);
+    }
   }
 #endif // _di_controller_rule_wait_for_
 
index dd6f98ea242ba8614d84aa66b5969f3ce51986a0..c8527882b87d019aad648560d8008130b2afeb38 100644 (file)
@@ -18,6 +18,10 @@ extern "C" {
     {
       controller_thread_t *thread_main = (controller_thread_t *) asynchronous->thread;
 
+      if (!thread_main->asynchronouss.enabled) {
+        return 0;
+      }
+
       f_thread_mutex_lock(&thread_main->setting->rules.array[asynchronous->index].lock);
 
       thread.cache_main = thread_main->cache_main;
@@ -30,6 +34,8 @@ extern "C" {
 
     controller_rule_process(asynchronous->index, asynchronous->action, asynchronous->options, &thread);
 
+    asynchronous->state = controller_asynchronous_state_done;
+
     f_thread_mutex_unlock(&thread.setting->rules.array[asynchronous->index].lock);
 
     return 0;
@@ -39,13 +45,21 @@ extern "C" {
 #ifndef _di_controller_thread_asynchronous_cancel_
   void controller_thread_asynchronous_cancel(controller_thread_t *thread) {
 
+    thread->asynchronouss.enabled = F_false;
+
     f_thread_mutex_lock(&thread->mutex->asynchronous);
 
-    for (f_array_length_t i = 0; i < thread->asynchronouss.used; ++i) {
+    f_array_length_t i = 0;
+
+    for (; i < thread->asynchronouss.used; ++i) {
 
       if (!thread->asynchronouss.array[i].state) continue;
 
-      f_thread_cancel(thread->asynchronouss.array[i].id);
+      if (thread->asynchronouss.array[i].child > 0) {
+        f_signal_send(F_signal_termination, thread->asynchronouss.array[i].child);
+      }
+
+      // @todo perhaps a timed join here where if it takes to long, try sending a kill signal to the child process.
       f_thread_join(thread->asynchronouss.array[i].id, 0);
 
       thread->asynchronouss.array[i].state = 0;
@@ -55,6 +69,14 @@ extern "C" {
 
     thread->asynchronouss.used = 0;
 
+    for (i = 0; i < thread->setting->rules.used; ++i) {
+      f_thread_mutex_unlock(&thread->setting->rules.array[i].lock);
+      f_thread_mutex_unlock(&thread->setting->rules.array[i].wait);
+    } // for
+
+    f_thread_mutex_unlock(&thread->mutex->print);
+    f_thread_mutex_unlock(&thread->mutex->cache);
+    f_thread_mutex_unlock(&thread->mutex->rule);
     f_thread_mutex_unlock(&thread->mutex->asynchronous);
   }
 #endif // _di_controller_thread_asynchronous_cancel_
@@ -63,14 +85,11 @@ extern "C" {
   void * controller_thread_cache(void *arguments) {
 
     controller_thread_t *thread = (controller_thread_t *) arguments;
+    controller_rule_t *rule = 0;
+
     f_array_length_t i = 0;
 
     for (;;) {
-
-      // @todo depend on a posix mutex condition that will designate when to sleep and perform actions.
-      //       when the condition is on/off, then the program will either sleep until condition is toggled or sleep a given interval.
-      //       when sleeping a given interval, then after each interval expiration, perform process cleanups until there are no asynchronous processes to cleanup.
-      //       once all asynchronous processes are cleaned up, toggle the condition again and wait indefinitely.
       sleep(controller_thread_cache_cleanup_interval_long);
 
       if (f_thread_mutex_lock_try(&thread->mutex->cache) == F_none) {
@@ -79,26 +98,34 @@ extern "C" {
 
         if (f_thread_mutex_lock_try(&thread->mutex->asynchronous) == F_none) {
 
-          for (i = 0; i < thread->asynchronouss.size; ++i) {
+          if (thread->asynchronouss.used) {
+            for (i = 0; i < thread->asynchronouss.used; ++i) {
 
-            if (thread->asynchronouss.array[i].state == controller_asynchronous_state_done) {
-              f_thread_join(thread->asynchronouss.array[i].id, 0);
-              thread->asynchronouss.array[i].state = controller_asynchronous_state_joined;
-            }
+              if (thread->asynchronouss.array[i].state == controller_asynchronous_state_done) {
+                f_thread_join(thread->asynchronouss.array[i].id, 0);
+                thread->asynchronouss.array[i].state = controller_asynchronous_state_joined;
+              }
 
-            if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
-              controller_macro_cache_action_t_clear(thread->asynchronouss.array[i].cache);
-              thread->asynchronouss.array[i].state = 0;
-            }
-          } // for
+              if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
+                controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i]);
 
-          for (i = thread->asynchronouss.size; i; --i) {
-            if (thread->asynchronouss.array[i - 1].state) break;
+                thread->asynchronouss.array[i].state = 0;
+              }
+
+              if (thread->asynchronouss.array[i].state) break;
+            } // for
 
-            controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i])
-          } // for
+            for (i = thread->asynchronouss.used - 1; thread->asynchronouss.used; --i, --thread->asynchronouss.used) {
 
-          thread->asynchronouss.used = i;
+              if (thread->asynchronouss.array[i].state == controller_asynchronous_state_joined) {
+                controller_macro_asynchronous_t_delete_simple(thread->asynchronouss.array[i]);
+
+                thread->asynchronouss.array[i].state = 0;
+              }
+
+              if (thread->asynchronouss.array[i].state) break;
+            } // for
+          }
 
           if (thread->asynchronouss.used < thread->asynchronouss.size) {
             controller_asynchronouss_resize(thread->asynchronouss.used, &thread->asynchronouss);
@@ -151,7 +178,7 @@ extern "C" {
 
         if (f_file_exists(thread->setting->path_pid.string) == F_true) {
           if (thread->data->error.verbosity != f_console_verbosity_quiet) {
-            f_thread_mutex_lock(&thread->mutex->print);
+            if (thread->asynchronouss.enabled) f_thread_mutex_lock(&thread->mutex->print);
 
             fprintf(thread->data->error.to.stream, "%c", f_string_eol_s[0]);
             fprintf(thread->data->error.to.stream, "%s%sThe pid file '", thread->data->error.context.before->string, thread->data->error.prefix ? thread->data->error.prefix : f_string_empty_s);
@@ -222,22 +249,28 @@ extern "C" {
     // only make the rule and control threads available once any/all pre-processing and is completed.
     if (F_status_is_error_not(status) && status != F_signal && status != F_child) {
 
-      controller_rule_wait_all(thread);
+      if (thread->data->parameters[controller_parameter_validate].result == f_console_result_none) {
+        controller_rule_wait_all(thread);
 
-      status = f_thread_create(0, &thread_rule, &controller_thread_rule, (void *) thread);
+        status = f_thread_create(0, &thread_rule, &controller_thread_rule, (void *) thread);
 
-      if (F_status_is_error_not(status)) {
-        status = f_thread_create(0, &thread_control, &controller_thread_control, (void *) thread);
-      }
+        if (F_status_is_error_not(status)) {
+          status = f_thread_create(0, &thread_control, &controller_thread_control, (void *) thread);
+        }
 
-      if (F_status_is_error(status)) {
-        if (thread->data->error.verbosity != f_console_verbosity_quiet) {
-          controller_error_print_locked(thread->data->error, F_status_set_fine(status), "f_thread_create", F_true, thread);
+        if (F_status_is_error(status)) {
+          if (thread->data->error.verbosity != f_console_verbosity_quiet) {
+            controller_error_print_locked(thread->data->error, F_status_set_fine(status), "f_thread_create", F_true, thread);
+          }
         }
       }
     }
 
-    if (F_status_is_error_not(status) && status != F_signal && status != F_child) {
+    if (status == F_child) {
+      return F_child;
+    }
+
+    if (F_status_is_error_not(status) && status != F_signal && (thread->data->parameters[controller_parameter_validate].result == f_console_result_none || thread->data->parameters[controller_parameter_test].result == f_console_result_found)) {
 
       // wait until signal thread exits, which happens on any termination signal.
       f_thread_join(thread_signal, 0);