]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Unify elements of child request handling for parallel, subrequests, and manual subreq...
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Thu, 8 May 2025 23:47:10 +0000 (17:47 -0600)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Fri, 9 May 2025 00:27:02 +0000 (18:27 -0600)
Fix signalling in parallel.  Both timeouts triggered on the parent side, and timeouts triggered on the child side now work.

Use relative timers for the retry keyword so synthetic time works correctly, same with the synchronous interpreter.

30 files changed:
doc/antora/modules/reference/pages/unlang/detach.adoc
share/dictionary/freeradius/dictionary.freeradius.internal
src/lib/io/worker.c
src/lib/server/request.c
src/lib/server/virtual_servers.h
src/lib/tls/session.c
src/lib/tls/virtual_server.c
src/lib/unlang/all.mk
src/lib/unlang/base.c
src/lib/unlang/child_request.c [new file with mode: 0644]
src/lib/unlang/child_request_priv.h [new file with mode: 0644]
src/lib/unlang/compile.c
src/lib/unlang/interpret.c
src/lib/unlang/interpret_synchronous.c
src/lib/unlang/parallel.c
src/lib/unlang/parallel_priv.h
src/lib/unlang/subrequest.c
src/lib/unlang/subrequest.h
src/lib/unlang/subrequest_child.c [deleted file]
src/lib/unlang/subrequest_child_priv.h [deleted file]
src/lib/unlang/subrequest_priv.h
src/lib/unlang/unlang_priv.h
src/modules/rlm_eap/rlm_eap.c
src/modules/rlm_eap/types/rlm_eap_peap/peap.c
src/tests/keywords/parallel
src/tests/keywords/parallel-child-timeout [new file with mode: 0644]
src/tests/keywords/parallel-parent-timeout [new file with mode: 0644]
src/tests/keywords/subrequest-rcode
src/tests/keywords/subrequest-timeout-child [moved from src/tests/keywords/subrequest-timeout with 100% similarity]
src/tests/keywords/subrequest-timeout-parent [new file with mode: 0644]

index 3edabba13d8581f725e07c0c2a1efca2ab6f3c5c..3edb86089c425f6bbf2012876ddb6a631a383d54 100644 (file)
@@ -36,16 +36,6 @@ of the parent request.  The default configuration has
 `max_request_time = 30`, so child requests will be cancelled 30
 seconds after the parent request was received.
 
-When it is necessary to have the child request execute for longer, the
-`Request-Lifetime` attribute can be set.  This attribute sets the
-lifetime of the child request after it has been detached.  This
-setting allows child requests to last longer than `max_request_time`
-seconds from when the parent request was received.
-
-The `Request-Lifetime` attribute sets the lifetime in seconds.  The
-minimum lifetime is five (5) seconds.  The maximum lifetime is 3600
-seconds.  Only integer seconds can be set.
-
 .Example
 [source,unlang]
 ----
@@ -55,8 +45,6 @@ subrequest Disconnect-Request {
     NAS-Port := parent.request.NAS-Port
     Acct-Session-Id := parent.request.Acct-Session-Id
 
-    control.Request-Lifetime := 60
-
     detach
     radius
 }
index da10735fd46b62e78052068c257a5b186d0a8803..8cb90c2794ac5ba98503321570d40f48b5dceed0 100644 (file)
@@ -247,7 +247,6 @@ VALUE       Listen-Socket-Type              coa                     8
 
 ATTRIBUTE      Outer-Realm-Name                        1251    string
 ATTRIBUTE      Inner-Realm-Name                        1252    string
-ATTRIBUTE      Request-Lifetime                        1253    integer
 ATTRIBUTE      Proto                                   1254    tlv
 
 #
index 4db64deb02fcb0bf16310f80647bb7feb74eface..ee61e79bc16394659f9666602d6e1049ef8df784 100644 (file)
@@ -1232,6 +1232,8 @@ static void _worker_request_detach(request_t *request, void *uctx)
 {
        fr_worker_t     *worker = talloc_get_type_abort(uctx, fr_worker_t);
 
+       RDEBUG4("%s - Request detaching", __FUNCTION__);
+
        if (request_is_detachable(request)) {
                /*
                *       End the time tracking...  We don't track detached requests,
@@ -1261,7 +1263,7 @@ static void _worker_request_runnable(request_t *request, void *uctx)
 {
        fr_worker_t     *worker = uctx;
 
-       RDEBUG3("Request marked as runnable");
+       RDEBUG4("%s - Request marked as runnable", __FUNCTION__);
        fr_heap_insert(&worker->runnable, request);
 }
 
@@ -1270,7 +1272,7 @@ static void _worker_request_runnable(request_t *request, void *uctx)
  */
 static void _worker_request_yield(request_t *request, UNUSED void *uctx)
 {
-       RDEBUG3("Request yielded");
+       RDEBUG4("%s - Request yielded", __FUNCTION__);
        if (likely(!request_is_detached(request))) fr_time_tracking_yield(&request->async->tracking, fr_time());
 }
 
@@ -1279,7 +1281,7 @@ static void _worker_request_yield(request_t *request, UNUSED void *uctx)
  */
 static void _worker_request_resume(request_t *request, UNUSED void *uctx)
 {
-       RDEBUG3("Request resuming");
+       RDEBUG4("%s - Request resuming", __FUNCTION__);
        if (likely(!request_is_detached(request))) fr_time_tracking_resume(&request->async->tracking, fr_time());
 }
 
@@ -1298,7 +1300,7 @@ static void _worker_request_prioritise(request_t *request, void *uctx)
 {
        fr_worker_t *worker = talloc_get_type_abort(uctx, fr_worker_t);
 
-       RDEBUG3("Request priority changed");
+       RDEBUG4("%s - Request priority changed", __FUNCTION__);
 
        /* Extract the request from the runnable queue _if_ it's in the runnable queue */
        if (fr_heap_extract(&worker->runnable, request) < 0) return;
index 65a41f94c9b7e3dfc67899fdb29f27b8d1b2d545..6d0a283701e1c804cb11c364277b8d74e1468f7d 100644 (file)
@@ -437,6 +437,8 @@ static inline CC_HINT(always_inline) request_t *request_alloc_pool(TALLOC_CTX *c
 
 static int _request_local_free(request_t *request)
 {
+       RDEBUG4("Local request freed (%p)", request);
+
        /*
         *      Ensure anything that might reference the request is
         *      freed before it is.
index 525b3aa5fc2ffde74c44e45b39ad5e02fb8f2231..7479a90bf7e3405f91546b22538d7e7ee43637c8 100644 (file)
  * @copyright 2019 The FreeRADIUS server project
  */
 
+#include <freeradius-devel/server/virtual_servers.h>
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
 typedef struct virtual_server_s virtual_server_t;
+typedef struct virtual_server_compile_s virtual_server_compile_t;
 
 #include <freeradius-devel/io/schedule.h>
 #include <freeradius-devel/server/cf_parse.h>
@@ -97,7 +99,7 @@ bool                  listen_record(fr_listen_t *li) CC_HINT(nonnull);
 /** Processing sections which are allowed in this virtual server.
  *
  */
-typedef struct {
+struct virtual_server_compile_s {
        section_name_t const                    *section;       //!< Identifier for the section.
        size_t                                  offset;         //!< where the CONF_SECTION pointer is written
        bool                                    dont_cache;     //!< If true, the CONF_SECTION pointer won't be written
@@ -106,7 +108,7 @@ typedef struct {
        unlang_mod_actions_t const              *actions;       //!< Default actions for this section.
        section_name_t const                    **methods;      //!< list of auxilliary module methods which are allowed in
                                                                ///< if the main name doesn't match.
-} virtual_server_compile_t;
+};
 
 #define COMPILE_TERMINATOR { .section = NULL }
 
index f6479875fdf919ea353055122fdeb70494657b13..99fa5eb5c15af310282b1da10d4d4a711fa76b13 100644 (file)
@@ -2011,12 +2011,7 @@ unlang_action_t fr_tls_new_session_push(request_t *request, fr_tls_conf_t const
        MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
        vp->vp_uint32 = enum_tls_packet_type_new_session->vb_uint32;
 
-       if (unlang_subrequest_child_push(NULL, child,
-                                       &(unlang_subrequest_session_t){
-                                               .enable = true,
-                                               .unique_ptr = child->parent
-                                       },
-                                       true, UNLANG_SUB_FRAME) < 0) {
+       if (unlang_subrequest_child_push(child, NULL, child->parent, true, UNLANG_SUB_FRAME) < 0) {
                return UNLANG_ACTION_FAIL;
        }
        if (unlang_function_push(child, NULL, tls_new_session_result, NULL, 0, UNLANG_SUB_FRAME, NULL) < 0) return UNLANG_ACTION_FAIL;
index 59daeea4b55e05bc6d8cfe6d06b63d6cbbf35e86..c1e664941fc0db048c54ab963e119b8f3cedb226 100644 (file)
@@ -61,11 +61,8 @@ unlang_action_t fr_tls_call_push(request_t *child, unlang_function_t resume,
         *      Sets up a dispatch frame in the parent
         *      and a result processing frame in the child.
         */
-       if (unlang_subrequest_child_push(NULL, child,
-                                        &(unlang_subrequest_session_t){
-                                               .enable = true,
-                                               .unique_ptr = tls_session
-                                        },
+       if (unlang_subrequest_child_push(child, NULL,
+                                        tls_session,
                                         true, UNLANG_SUB_FRAME) < 0) {
                return UNLANG_ACTION_FAIL;
        }
index 194c9f582381667857ea1c53039beaf45d32f981..d88675e394fe5876307ad00f3c13377b285d06fd 100644 (file)
@@ -5,6 +5,7 @@ SOURCES :=      base.c \
                call_env.c \
                caller.c \
                catch.c \
+               child_request.c \
                compile.c \
                condition.c \
                detach.c \
@@ -24,7 +25,6 @@ SOURCES       :=      base.c \
                parallel.c \
                return.c \
                subrequest.c \
-               subrequest_child.c \
                switch.c \
                timeout.c \
                tmpl.c \
index eacb063ae667f8a82380cf1bf471771505d1b33a..91d9ef2669f374e60aaf1b8ded3f0bf58912d38a 100644 (file)
@@ -71,7 +71,6 @@ static TALLOC_CTX *unlang_ctx = NULL;
 
 static int _unlang_global_free(UNUSED void *uctx)
 {
-       unlang_subrequest_op_free();
        TALLOC_FREE(unlang_ctx);
 
        return 0;
diff --git a/src/lib/unlang/child_request.c b/src/lib/unlang/child_request.c
new file mode 100644 (file)
index 0000000..bea7c6c
--- /dev/null
@@ -0,0 +1,311 @@
+/*
+ *   This program is free software; you can redistribute it and/or modify
+ *   it under the terms of the GNU General Public License as published by
+ *   the Free Software Foundation; either version 2 of the License, or
+ *   (at your option) any later version.
+ *
+ *   This program is distributed in the hope that it will be useful,
+ *   but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *   GNU General Public License for more details.
+ *
+ *   You should have received a copy of the GNU General Public License
+ *   along with this program; if not, write to the Free Software
+ *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/child_request.c
+ * @brief Common child request management code.
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/state.h>
+#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/server/signal.h>
+
+#include "lib/server/rcode.h"
+#include "lib/util/talloc.h"
+#include "unlang_priv.h"
+#include "child_request_priv.h"
+
+typedef struct {
+       unlang_child_request_t *cr;             //!< A pointer to memory in the parent's frame state
+                                               ///< allocated for this child to write results to.
+} unlang_frame_state_child_request_t;
+
+fr_table_num_ordered_t const unlang_child_states_table[] = {
+       { L("CANCELLED"),               CHILD_CANCELLED },
+       { L("DETACH"),                  CHILD_DETACHED  },
+       { L("DONE"),                    CHILD_DONE      },
+       { L("EXITED"),                  CHILD_EXITED    },
+       { L("INIT"),                    CHILD_INIT      },
+       { L("RUNNABLE"),                CHILD_RUNNABLE  }
+};
+size_t unlang_child_states_table_len = NUM_ELEMENTS(unlang_child_states_table);
+
+/** Process a detach signal in the child
+ *
+ * This processes any detach signals the child receives
+ * The child doesn't actually do the detaching
+ */
+static void unlang_child_request_signal(request_t *request, UNUSED unlang_stack_frame_t *frame, fr_signal_t action)
+{
+       unlang_frame_state_child_request_t *state;
+       unlang_child_request_t *cr;
+
+       /*
+        *      We're already detached so we don't
+        *      need to notify the parent we're
+        *      waking up, and we don't need to detach
+        *      again...
+        */
+       if (request_is_detached(request)) return;
+
+       state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t);
+       cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */
+
+       /*
+        *      Ignore signals which aren't detach, and ar
+        *      and ignore the signal if we have no parent.
+        */
+       switch (action) {
+       case FR_SIGNAL_DETACH:
+               /*
+                *      Place child's state back inside the parent
+                */
+               if (cr->config.session_unique_ptr) fr_state_store_in_parent(request,
+                                                                           cr->config.session_unique_ptr,
+                                                                           cr->num);
+
+               RDEBUG3("Detached - Removing subrequest from parent, and marking parent as runnable");
+
+               /*
+                *      Indicate to the parent there's no longer a child
+                */
+               cr->state = CHILD_DETACHED;
+
+               /*
+                *      Don't run the request-done frame, the request will have been
+                *      detached by whatever signalled us, so we can't inform the parent
+                *      when we exit.
+                */
+               repeatable_clear(frame);
+
+               /*
+                *      Tell the parent to resume if all the request's siblings are done
+                */
+               if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) unlang_interpret_mark_runnable(request->parent);
+               break;
+
+       /*
+        *      This frame is not cancellable, so FR_SIGNAL_CANCEL
+        *      does nothing.  If the child is cancelled in its
+        *      entirety, then its stack will unwind up to this point
+        *      and unlang_subrequest_child_done will mark the
+        *      parent as runnable.  We don't need to do anything here.
+        *
+        *      We set the state for debugging purposes, and to reduce
+        *      the amount of work we do in unlang_subrequest_child_done.
+        */
+       case FR_SIGNAL_CANCEL:
+               cr->state = CHILD_CANCELLED;
+               break;
+
+       default:
+               return;
+       }
+}
+
+/** When the child is done, tell the parent that we've exited.
+ *
+ * This is pushed as a frame at the top of the child's stack, so when
+ * the child is done executing, it runs this to inform the parent
+ * that its done.
+ */
+static unlang_action_t unlang_child_request_done(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+{
+       unlang_frame_state_child_request_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t);
+       unlang_child_request_t *cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */
+
+       /*
+        *      The repeat function for this frame should've
+        *      been cleared, so this function should not run
+        *      for detached requests.
+        */
+       fr_assert(!request_is_detached(request));
+
+       switch (cr->state) {
+       case CHILD_RUNNABLE:
+               /*
+                *      Place child state back inside the parent
+                */
+               if (cr->config.session_unique_ptr) {
+                       fr_state_store_in_parent(request,
+                                                cr->config.session_unique_ptr,
+                                                cr->num);
+               }
+
+               /*
+                *      Record the child's result and the last
+                *      priority. For parallel, this lets one
+                *      child be used to control the rcode of
+                *      the parallel keyword.
+                */
+               cr->result.rcode = *p_result;
+               cr->result.priority = frame->priority;
+               if (cr->result.p_result) *(cr->result.p_result) = cr->result.rcode;
+               break;
+
+       case CHILD_CANCELLED:
+               /*
+                *      Child session state is no longer consistent
+                *      after cancellation, so discard it.
+                */
+               if (cr->config.session_unique_ptr) {
+                       fr_state_discard_child(request->parent, cr->config.session_unique_ptr, cr->num);
+               }
+               break;
+
+       default:
+               fr_assert_msg(0, "child %s resumed top frame with invalid state %s",
+                             request->name,
+                             fr_table_str_by_value(unlang_child_states_table, cr->state, "<INVALID>"));
+       }
+
+       cr->state = CHILD_EXITED;
+
+       /*
+        *      Tell the parent to resume if all the request's siblings are done
+        */
+       if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) {
+               RDEBUG3("All children have exited, marking parent %s as runnable", request->parent->name);
+               unlang_interpret_mark_runnable(request->parent);
+       } else {
+               RDEBUG3("Child %s exited, %u sibling(s) remaining",
+                       request->name,
+                       *cr->sibling_count);
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Push a resumption frame onto a child's stack
+ *
+ * Push a frame onto the stack of the child to inform the parent when it's complete.
+ * An additional frame is pushed onto the child's stack by the 'run' function which
+ * executes in the context of the parent.
+ *
+ * @param[in] cr       state for this child request.  This is a pointer
+ *                     to a structure in the parent's frame state.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+static int unlang_child_request_stack_init(unlang_child_request_t *cr)
+{
+       request_t                               *child = cr->request;
+       unlang_frame_state_child_request_t      *state;
+       unlang_stack_frame_t                    *frame;
+
+       static unlang_t inform_parent = {
+               .type = UNLANG_TYPE_CHILD_REQUEST,
+               .name = "child-request",
+               .debug_name = "child-request-resume",
+               .actions = {
+                       .actions = {
+                               [RLM_MODULE_REJECT]     = 0,
+                               [RLM_MODULE_FAIL]       = 0,
+                               [RLM_MODULE_OK]         = 0,
+                               [RLM_MODULE_HANDLED]    = 0,
+                               [RLM_MODULE_INVALID]    = 0,
+                               [RLM_MODULE_DISALLOW]   = 0,
+                               [RLM_MODULE_NOTFOUND]   = 0,
+                               [RLM_MODULE_NOOP]       = 0,
+                               [RLM_MODULE_UPDATED]    = 0
+                       },
+                       .retry = RETRY_INIT,
+               }
+       };
+
+       /* Sets up the frame for us to use immediately */
+       if (unlikely(unlang_interpret_push_instruction(child, &inform_parent, RLM_MODULE_NOOP, true) < 0)) {
+               return -1;
+       }
+
+       frame = frame_current(child);
+       state = frame->state;
+       state->cr = cr;
+       repeatable_set(frame);  /* Run this on the way back up */
+
+       return 0;
+}
+
+/** Initialize a child request
+ *
+ * This initializes the child request result and configuration structure,
+ * and pushes a resumption frame onto the child's stack.
+ *
+ * @param[in] ctx                      Memory to use for any additional memory allocated
+ *                                     to the unlang_child_request_t.
+ * @param[out] out                     Child request to initialize.
+ * @param[in] child                    The child request to initialize.
+ * @param[in] p_result                 Where to write out the rcode from the child.
+ * @param[in,out] sibling_count                If non-null the bumber of siblings.  This is incremented
+ *                                     for each child created.
+ * @param[in] unique_session_ptr       Unique session pointer for this child.
+ *                                     If NULL session data won't be stored/restored for the child.
+ * @param[in] free_child               Free the child when done?
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child,
+                             rlm_rcode_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child)
+{
+       *out = (unlang_child_request_t){
+               .name = talloc_bstrdup(ctx, child->name),
+               .num = sibling_count ? (*sibling_count)++ : 0,
+               .request = child,
+               .state = CHILD_INIT,
+               .sibling_count = sibling_count,
+               .config = {
+                       .session_unique_ptr = unique_session_ptr,
+                       .free_child = free_child
+               },
+               .result = {
+                       .p_result = p_result,
+                       .rcode = RLM_MODULE_NOT_SET
+               }
+       };
+
+       return unlang_child_request_stack_init(out);
+}
+
+int unlang_child_request_op_init(void)
+{
+       unlang_register(UNLANG_TYPE_CHILD_REQUEST,
+                       &(unlang_op_t){
+                               .name = "child-request",
+                               .interpret = unlang_child_request_done,
+                               .signal = unlang_child_request_signal,
+                               /*
+                                *      Frame can't be cancelled, because children need to
+                                *      write out status to the parent.  If we don't do this,
+                                *      then all children must be detachable and must detach
+                                *      so they don't try and write out status to a "done"
+                                *      parent.
+                                *
+                                *      It's easier to allow the child/parent relationship
+                                *      to end normally so that non-detachable requests are
+                                *      guaranteed the parent still exists.
+                                */
+                               .flag = UNLANG_OP_FLAG_NO_CANCEL,
+                               .frame_state_size = sizeof(unlang_frame_state_child_request_t),
+                               .frame_state_type = "unlang_frame_state_child_request_t"
+                       });
+
+       return 0;
+}
diff --git a/src/lib/unlang/child_request_priv.h b/src/lib/unlang/child_request_priv.h
new file mode 100644 (file)
index 0000000..b7b89dc
--- /dev/null
@@ -0,0 +1,94 @@
+#pragma once
+/*
+ *  This program is free software; you can redistribute it and/or modify
+ *  it under the terms of the GNU General Public License as published by
+ *  the Free Software Foundation; either version 2, or (at your option)
+ *  any later version.
+ *
+ *  This program is distributed in the hope that it will be useful,
+ *  but WITHOUT ANY WARRANTY; without even the implied warranty of
+ *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+ *  GNU General Public License for more details.
+ *
+ *  You should have received a copy of the GNU General Public License
+ *  along with this program; if not, write to the Free Software Foundation,
+ *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/subrequest_child_priv.h
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/request.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** Parallel child states
+ *
+ */
+typedef enum {
+       CHILD_INIT = 0,                                         //!< Initial state, has no request allocated.
+
+       CHILD_RUNNABLE,                                         //!< Running/runnable.  The request is currently
+                                                               ///< running, or is yielded but runnable.
+       CHILD_EXITED,                                           //!< Child has run to completion, and is waiting
+                                                               ///< to be reaped.
+       CHILD_DETACHED,                                         //!< Child has detached, we can't signal it or
+                                                               ///< communicate with it anymore.  It will be freed
+                                                               ///< by the interpreter when it completes.
+       CHILD_CANCELLED,                                        //!< Child was cancelled.  The request is still
+                                                               ///< available, but the result from it should not
+                                                               ///< be used and it should be free by the parent.
+       CHILD_DONE,                                             //!< The child has been processed by the parent
+                                                               ///< the request should still exist, and should be freed.
+
+       CHILD_FREED                                             //!< The child has been freed.  The request is no
+                                                               ///< longer available, and should not be used.
+                                                               ///< this is mostly for debugging purposes.
+} unlang_child_request_state_t;
+
+extern fr_table_num_ordered_t const unlang_child_states_table[];
+extern size_t unlang_child_states_table_len;
+
+/** Each child has a state, a number, a request, and a count of their siblings
+ */
+typedef struct {
+       char const                      *name;                  //!< Cache the request name.
+       int                             num;                    //!< The child number.
+       request_t                       *request;               //!< Child request.  The actual request the child will run.
+
+       unlang_child_request_state_t    state;                  //!< State of the child.
+
+       unsigned int                    *sibling_count;         //!< Number of siblings.
+                                                               ///< as a child completes, it decrements this number.
+                                                               ///< once it reaches zero, the parent is signalled
+                                                               ///< to resume.
+
+       struct {
+               void const              *session_unique_ptr;    //!< Session unique ptr identifier.  If not NULL, the child's
+                                                               ///< session data will be stored in the parent, and be restored
+                                                               ///< during a later request.
+               bool                    free_child;
+       } config;
+
+       struct {
+               rlm_rcode_t             rcode;                  //!< Where to store the result of the child.
+               rlm_rcode_t             *p_result;              //!< If not NULL, write the rcode here too.
+               int                     priority;               //!< Priority of the highest frame on the stack of the child.
+       } result;
+} unlang_child_request_t;
+
+int            unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child,
+                                         rlm_rcode_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child);
+
+int            unlang_child_request_op_init(void);
+
+#ifdef __cplusplus
+}
+#endif
index 01e9d52393b025c20967e3d7d001d3af9bf9bfad..254b92973add5fd7179c44c2a995735385e0ff8e 100644 (file)
@@ -302,7 +302,7 @@ static void unlang_dump(unlang_t *instruction, int depth)
        for (c = instruction; c != NULL; c = c->next) {
                switch (c->type) {
                case UNLANG_TYPE_NULL:
-               case UNLANG_TYPE_SUBREQUEST_CHILD:
+               case UNLANG_TYPE_CHILD_REQUEST:
                case UNLANG_TYPE_MAX:
                        fr_assert(0);
                        break;
index 19ba948f0609391fed3cd9962b8ef9e51c5538c3..3af377b157d5957ba4b3b1dfbae86a800f8ab5f4 100644 (file)
  *
  * @copyright 2006-2016 The FreeRADIUS server project
  */
+
 RCSID("$Id$")
 
+#include <freeradius-devel/unlang/interpret.h>
+#include <freeradius-devel/util/timer.h>
 #include <freeradius-devel/server/base.h>
 #include <freeradius-devel/server/modpriv.h>
 #include <freeradius-devel/unlang/xlat_func.h>
 
 #include "interpret_priv.h"
 #include "module_priv.h"
-#include "parallel_priv.h"
 
 
 /** The default interpreter instance for this thread
@@ -396,9 +398,7 @@ unlang_frame_action_t result_calculate(request_t *request, unlang_stack_frame_t
                         *      frame is cleaned up.
                         */
                        if (fr_time_delta_ispos(instruction->actions.retry.mrd)) {
-                               retry->timeout = fr_time_add(fr_time(), instruction->actions.retry.mrd);
-
-                               if (fr_timer_at(retry, unlang_interpret_event_list(request)->tl, &retry->ev, retry->timeout,
+                               if (fr_timer_in(retry, unlang_interpret_event_list(request)->tl, &retry->ev, instruction->actions.retry.mrd,
                                                false, instruction_retry_handler, request) < 0) {
                                        RPEDEBUG("Failed inserting retry event");
                                        *result = RLM_MODULE_FAIL;
@@ -1028,6 +1028,11 @@ void unlang_interpret_request_done(request_t *request)
        }
 }
 
+/** Tell the interpreter to detach the request
+ *
+ * This function should not be called directly use unlang_interpret_signal(request, FR_SIGNAL_DETACH) instead.
+ * This will ensure all frames on the request's stack receive the detach signal.
+ */
 static inline CC_HINT(always_inline)
 void unlang_interpret_request_detach(request_t *request)
 {
@@ -1260,9 +1265,7 @@ int unlang_interpret_set_timeout(request_t *request, fr_time_delta_t timeout)
        retry->state = FR_RETRY_CONTINUE;
        retry->count = 1;
 
-       retry->timeout = fr_time_add(fr_time(), timeout);
-
-       return fr_timer_at(retry, unlang_interpret_event_list(request)->tl, &retry->ev, retry->timeout,
+       return fr_timer_in(retry, unlang_interpret_event_list(request)->tl, &retry->ev, timeout,
                           false, instruction_timeout_handler, request);
 }
 
@@ -1513,7 +1516,7 @@ static xlat_action_t unlang_cancel_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
                 *      This can be useful for doing stacked
                 *      cancellations in policy.
                 */
-               vb->vb_time_delta = fr_time_sub(when, fr_time());
+               vb->vb_time_delta = fr_time_sub(when, unlang_interpret_event_list(request)->tl->time());
                fr_dcursor_insert(out, vb);
        }
 
index f7f6345f0bcabeed2d3c608cc5053dddc85b3bd1..c5da642067f02ed5ac8a36ac6e822f968d3577af 100644 (file)
@@ -68,7 +68,7 @@ static void _request_done_external(request_t *request, UNUSED rlm_rcode_t rcode,
                              "Request %s is marked as yielded at end of processing", request->name);
        }
 
-       RDEBUG3("Synchronous done external request");
+       RDEBUG3("Done synchronous external request");
 }
 
 /** Internal request (i.e. one generated by the interpreter) is now complete
@@ -224,7 +224,7 @@ rlm_rcode_t unlang_interpret_synchronous(fr_event_list_t *el, request_t *request
                 *      well.
                 */
                DEBUG4("Gathering events - %s", dont_wait_for_event ? "Will not wait" : "will wait");
-               num_events = fr_event_corral(el, fr_time(), !dont_wait_for_event);
+               num_events = fr_event_corral(el, el->tl->time(), !dont_wait_for_event);
                if (num_events < 0) {
                        RPERROR("fr_event_corral");
                        break;
index 7c5c891109d2251330139fdf781f85063bd6e581..c8efa28e7637bb174298605681b77f810b9c4e87 100644 (file)
  *
  * @copyright 2006-2019 The FreeRADIUS server project
  */
+
+
 RCSID("$Id$")
 
-#include "function.h"
+#include <freeradius-devel/server/rcode.h>
+#include <freeradius-devel/server/signal.h>
+#include <freeradius-devel/server/state.h>
+#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/util/table.h>
+
+#include "action.h"
+#include "interpret.h"
+#include "subrequest.h"
 #include "interpret_priv.h"
-#include "module_priv.h"
+#include "unlang_priv.h"
 #include "parallel_priv.h"
-#include "subrequest_priv.h"
-
+#include "child_request_priv.h"
 
 /** Cancel a specific child
  *
+ * For most states we just change the current state to CANCELLED. For the RUNNABLE state
+ * we need to signal the child to cancel itself.
+ *
+ * We don't free any requests here, we just mark them up so their rcodes are ignored when
+ * the parent is resumed, the parent then frees the child, once we're sure its done being
+ * run through the intepreter.
  */
-static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, int i)
+static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, unlang_child_request_t *cr)
 {
-       request_t *child = state->children[i].request;
+       request_t *child = cr->request;
        request_t *request = child->parent;     /* For debug messages */
+       unlang_child_request_state_t child_state = cr->state;
 
-       switch (state->children[i].state) {
+       switch (cr->state) {
        case CHILD_INIT:
-               state->children[i].state = CHILD_CANCELLED;
-               fr_assert(!state->children[i].request);
+               cr->state = CHILD_CANCELLED;
+               fr_assert(!cr->request);
                break;
 
        case CHILD_EXITED:
-               state->children[i].state = CHILD_CANCELLED;
-               TALLOC_FREE(state->children[i].request);
+               cr->state = CHILD_CANCELLED;    /* Don't process its return code */
                break;
 
        case CHILD_RUNNABLE:    /* Don't check runnable_id, may be yielded */
@@ -61,140 +76,72 @@ static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_pa
                unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
 
                /*
-                *      Free it.
+                *      We don't free the request here, we wait
+                *      until it signals us that it's done.
                 */
-               TALLOC_FREE(state->children[i].request);
                break;
 
        case CHILD_DONE:
-               fr_assert(!fr_heap_entry_inserted(child->runnable));
-
-               /*
-                *      Completed children just get freed
-                */
-               TALLOC_FREE(state->children[i].request);
+               cr->state = CHILD_CANCELLED;
                break;
 
-       case CHILD_DETACHED:
+       case CHILD_DETACHED:    /* Can't signal detached requests*/
+               fr_assert(!cr->request);
+               return;
+
        case CHILD_CANCELLED:
+               break;
+
+       case CHILD_FREED:
                return;
        }
 
-       RDEBUG3("parallel - child %s (%d/%d) CANCELLED",
-               state->children[i].name,
-               i + 1, state->num_children);
-       state->children[i].state = CHILD_CANCELLED;
+       RDEBUG3("parallel - child %s (%d/%d) CANCELLED, previously %s",
+               cr->name, cr->num, state->num_children,
+               fr_table_str_by_value(unlang_child_states_table, child_state, "<INVALID>"));
 }
 
-#if 0
-/** Cancel all the child's siblings
+/** Send a signal from parent request to all of it's children
  *
- * Siblings will be excluded from final result calculation for the parallel section.
  */
-static void unlang_parallel_cancel_siblings(request_t *request)
+static void unlang_parallel_signal(UNUSED request_t *request,
+                                  unlang_stack_frame_t *frame, fr_signal_t action)
 {
-       unlang_stack_t          *stack = request->parent->stack;
-       unlang_stack_frame_t    *frame = &stack->frame[stack->depth];
        unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-       int i;
-
-       for (i = 0; i < state->num_children; i++) {
-               if (state->children[i].request == request) continue;    /* Don't cancel this one */
-
-               unlang_parallel_cancel_child(state, i);
-       }
-}
-#endif
-
-/** Signal handler to deal with UNLANG_ACTION_DETACH
- *
- * When a request detaches we need
- */
-static void unlang_parallel_child_signal(request_t *request, UNUSED fr_signal_t action, void *uctx)
-{
-       unlang_parallel_child_t         *child = uctx;
-       unlang_stack_frame_t            *frame;
-       unlang_parallel_state_t         *state;
-
-       frame = frame_current(request->parent);
-       state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
-       RDEBUG3("parallel - child %s (%d/%d) DETACHED",
-               request->name,
-               child->num + 1, state->num_children);
-
-       child->state = CHILD_DETACHED;
-       child->request = NULL;
-       state->num_complete++;
+       unsigned int    i;
 
        /*
-        *      All children exited, resume the parent
+        *      Signal any runnable children to get them to exit
         */
-       if (state->num_complete == state->num_children) {
-               RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
-               unlang_interpret_mark_runnable(request->parent);
-       }
-
-       return;
-}
+       if (action == FR_SIGNAL_CANCEL) {
+               for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, &state->children[i]);
 
-/** When the chld is done, tell the parent that we've exited.
- *
- */
-static unlang_action_t unlang_parallel_child_done(UNUSED rlm_rcode_t *p_result, UNUSED int *p_priority, request_t *request, void *uctx)
-{
-       unlang_parallel_child_t *child = uctx;
+               return;
+       }
 
        /*
-        *      If we have a parent, then we're running synchronously
-        *      with it.  Tell the parent that we've exited, and it
-        *      can continue.
-        *
-        *      Otherwise we're a detached child, and we don't tell
-        *      the parent anything.  Because we have that kind of
-        *      relationship.
+        *      Signal all of the runnable/running children.
         */
-       if (child->state == CHILD_RUNNABLE) {
-               /*
-                *      Reach into the parent to get the unlang_parallel_state_t
-                *      for the whole parallel block.
-                */
-               unlang_stack_frame_t            *frame = frame_current(request->parent);
-               unlang_parallel_state_t         *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
-               RDEBUG3("parallel - child %s (%d/%d) EXITED",
-                       request->name,
-                       child->num + 1, state->num_children);
-
-               child->state = CHILD_EXITED;
-               state->num_complete++;
+       for (i = 0; i < state->num_children; i++) {
+               if (state->children[i].state != CHILD_RUNNABLE) continue;
 
-               /*
-                *      All children exited, resume the parent
-                */
-               if (state->num_complete == state->num_children) {
-                       RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
-                       unlang_interpret_mark_runnable(request->parent);
-               }
+               unlang_interpret_signal(state->children[i].request, action);
        }
-
-       /*
-        *      Don't change frame->result, it's the result of the child.
-        */
-       return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
+
 static unlang_action_t unlang_parallel_resume(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
 {
        unlang_parallel_state_t         *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
-       int                             i, priority;
+       unsigned int                    i;
+       int                             priority;
        rlm_rcode_t                     result;
 
+       fr_assert(state->num_runnable == 0);
+
        for (i = 0; i < state->num_children; i++) {
-               if (!state->children[i].request) continue;
+               if (state->children[i].state != CHILD_EXITED) continue;
 
-               fr_assert(state->children[i].state == CHILD_EXITED);
                REQUEST_VERIFY(state->children[i].request);
 
                RDEBUG3("parallel - child %s (%d/%d) DONE",
@@ -239,41 +186,61 @@ static unlang_action_t unlang_parallel_resume(rlm_rcode_t *p_result, request_t *
        for (i = 0; i < state->num_children; i++) {
                if (!state->children[i].request) continue;
 
-               fr_assert(!fr_heap_entry_inserted(state->children[i].request->runnable));
-               TALLOC_FREE(state->children[i].request);
+               fr_assert(!unlang_request_is_scheduled(state->children[i].request));
+
+               unlang_subrequest_detach_and_free(&state->children[i].request);
+
+               state->children[i].state = CHILD_FREED;
        }
 
        *p_result = state->result;
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-/** Run one or more sub-sections from the parallel section.
- *
- */
-static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+static unlang_action_t unlang_parallel(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
 {
-       unlang_parallel_state_t         *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-       request_t                       *child;
-       int                             i;
+       unlang_t const                  *instruction;
+       unlang_group_t                  *g;
+       unlang_parallel_t               *gext;
+       unlang_parallel_state_t         *state;
+
+       int                     i;
+
+       g = unlang_generic_to_group(frame->instruction);
+       if (!g->num_children) {
+               *p_result = RLM_MODULE_NOOP;
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
+
+       gext = unlang_group_to_parallel(g);
 
        /*
-        *      If the children should be created detached, we return
-        *      "noop".  This function then creates the children,
-        *      detaches them, and returns.
+        *      Allocate an array for the children.
         */
-       if (state->detach) {
-               state->priority = 0;
-               state->result = RLM_MODULE_NOOP;
+       MEM(frame->state = state = _talloc_zero_pooled_object(request,
+                                                             sizeof(unlang_parallel_state_t) +
+                                                             (sizeof(state->children[0]) * g->num_children),
+                                                             "unlang_parallel_state_t",
+                                                             g->num_children,
+                                                             (talloc_array_length(request->name) * 2)));
+       if (!state) {
+               *p_result = RLM_MODULE_FAIL;
+               return UNLANG_ACTION_CALCULATE_RESULT;
        }
 
+       (void) talloc_set_type(state, unlang_parallel_state_t);
+       state->result = RLM_MODULE_FAIL;
+       state->priority = -1;                           /* as-yet unset */
+       state->detach = gext->detach;
+       state->clone = gext->clone;
+       state->num_children = g->num_children;
+
        /*
-        *      Loop over all the children.
-        *
-        *      We always service the parallel section from top to
-        *      bottom, and we always service all of it.
+        *      Initialize all of the children.
         */
-       for (i = 0; i < state->num_children; i++) {
-               fr_assert(state->children[i].instruction != NULL);
+       for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
+               request_t                       *child;
+
                child = unlang_io_subrequest_alloc(request,
                                                   request->proto_dict, state->detach);
                child->packet->code = request->packet->code;
@@ -284,11 +251,11 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
 
                if (state->clone) {
                        /*
-                        *      Note that we do NOT copy the
-                        *      Session-State list!  That
-                        *      contains state information for
-                        *      the parent.
-                        */
+                             Note that we do NOT copy the
+                             Session-State list!  That
+                             contains state information for
+                             the parent.
+                       */
                        if ((fr_pair_list_copy(child->request_ctx,
                                               &child->request_pairs,
                                               &request->request_pairs) < 0) ||
@@ -298,26 +265,16 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
                            (fr_pair_list_copy(child->control_ctx,
                                               &child->control_pairs,
                                               &request->control_pairs) < 0)) {
-                               REDEBUG("failed copying lists to clone");
+                               REDEBUG("failed copying lists to child");
                        error:
+
                                /*
-                                *      Detached children which have
-                                *      already been created are
-                                *      allowed to continue.
+                                *      Remove all previously
+                                *      spawned children.
                                 */
-                               if (!state->detach) {
-                                       /*
-                                        *      Remove the current child
-                                        *
-                                        *      Should also free detached children
-                                        */
-                                       unlang_interpret_request_done(child);
-
-                                       /*
-                                        *      Remove all previously
-                                        *      spawned children.
-                                        */
-                                       for (--i; i >= 0; i--) unlang_interpret_request_done(child);
+                               for (--i; i >= 0; i--) {
+                                       unlang_subrequest_detach_and_free(&state->children[i].request);
+                                       state->children[i].state = CHILD_FREED;
                                }
 
                                RETURN_MODULE_FAIL;
@@ -325,77 +282,93 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
                }
 
                /*
-                *      Child starts detached, the parent knows
-                *      and can exit immediately once all
-                *      the children are initialised.
+                *      Initialise our frame state, and push the first
+                *      instruction onto the child's stack.
+                *
+                *      This instruction will mark the parent as runnable
+                *      when it is executed.
+                *
+                *      We only do this if the requests aren't detached.
+                *      If they are detached, this repeat function would
+                *      be immediately disabled, so no point...
+                */
+               if (!state->detach) {
+                       if (unlang_child_request_init(state, &state->children[i], child, NULL, &state->num_runnable,
+                                                     frame_current(request)->instruction, false) < 0) goto error;
+                       fr_assert(state->children[i].state == CHILD_INIT);
+               } else {
+                       state->children[i].num = i;
+                       state->children[i].request = child;
+               }
+
+               /*
+                *      Push the first instruction for the child to run,
+                *      which in case of parallel, is the child's
+                *      subsection within the parallel block.
                 */
-               if (state->detach) {
+               if (unlang_interpret_push(child,
+                                         instruction, RLM_MODULE_FAIL,
+                                         UNLANG_NEXT_STOP,
+                                         state->detach ? UNLANG_TOP_FRAME : UNLANG_SUB_FRAME) < 0) goto error;
+       }
+
+       /*
+        *      Now we're sure all the children are initialised
+        *      start them running.
+        */
+       if (state->detach) {
+               for (i = 0; i < (int)state->num_children; i++) {
                        if (RDEBUG_ENABLED3) {
                                request_t *parent = request;
 
-                               request = child;
+                               request = state->children[i].request;
                                RDEBUG3("parallel - child %s (%d/%d) DETACHED",
                                        request->name,
                                        i + 1, state->num_children);
                                request = parent;
                        }
 
-                       state->children[i].state = CHILD_DETACHED;
+                       /*
+                        *      Adds to the runnable queue
+                        */
+                       interpret_child_init(state->children[i].request);
 
                        /*
-                        *      Detach the child, and insert
-                        *      it into the backlog.
+                        *      Converts to a detached request
                         */
-                       if ((unlang_subrequest_lifetime_set(child) < 0) || (request_detach(child) < 0)) {
-                               request = child;
-                               RPEDEBUG("Failed detaching request");
-                               talloc_free(child);
+                       unlang_interpret_signal(state->children[i].request, FR_SIGNAL_DETACH);
+               }
 
-                               RETURN_MODULE_FAIL;
-                       }
                /*
-                *      If the children don't start detached
-                *      push a function onto the stack to
-                *      notify the parent when the child is
-                *      done.
+                *      We are now done, all the children are detached
+                *      so we don't need to wait around for them to complete.
                 */
-               } else {
-                       if (unlang_function_push(child,
-                                                NULL,
-                                                unlang_parallel_child_done,
-                                                unlang_parallel_child_signal,
-                                                ~FR_SIGNAL_DETACH,
-                                                UNLANG_TOP_FRAME,
-                                                &state->children[i]) < 0) goto error;
-
-                       state->children[i].num = i;
-                       state->children[i].name = talloc_bstrdup(state, child->name);
-                       state->children[i].request = child;
-                       state->children[i].state = CHILD_RUNNABLE;
+               return UNLANG_ACTION_CALCULATE_RESULT;
+       }
 
+       for (i = 0; i < (int)state->num_children; i++) {
+               /*
+                *      Ensure we restore the session state information
+                *      into the child.
+                */
+               if (state->children[i].config.session_unique_ptr) {
+                       fr_state_restore_to_child(state->children[i].request,
+                                                 state->children[i].config.session_unique_ptr,
+                                                 state->children[i].num);
                }
-               interpret_child_init(child);
 
                /*
-                *      Push the first instruction for
-                *      the child to run.
+                *      Ensures the child is setup correctly and adds
+                *      it into the runnable queue of whatever owns
+                *      the interpreter.
                 */
-               if (unlang_interpret_push(child,
-                                         state->children[i].instruction, RLM_MODULE_FAIL,
-                                         UNLANG_NEXT_STOP,
-                                         state->detach ? UNLANG_TOP_FRAME : UNLANG_SUB_FRAME) < 0) goto error;
+               interpret_child_init(state->children[i].request);
+               state->children[i].state = CHILD_RUNNABLE;
        }
 
        /*
-        *      If all children start detached,
-        *      then we're done.
-        */
-       if (state->detach) return UNLANG_ACTION_CALCULATE_RESULT;
-
-       /*
-        *      Don't call this function again when
-        *      the parent resumes, instead call
-        *      a function to process the results
+        *      Don't call this function again when the parent resumes,
+        *      instead call a function to process the results
         *      of the children.
         */
        frame_repeat(frame, unlang_parallel_resume);
@@ -403,89 +376,13 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
        /*
         *      Yield to the children
         *
-        *      They scamper off to play on their
-        *      own when they're all done, the last
-        *      one tells the parent, so it can resume,
-        *      and gather up all the results.
+        *      They scamper off to play on their own when they're all done,
+        *      the last one tells the parent, so it can resume,
+        *      and gather up the results, and mercilessly reap the children.
         */
        return UNLANG_ACTION_YIELD;
 }
 
-/** Send a signal from parent request to all of it's children
- *
- */
-static void unlang_parallel_signal(UNUSED request_t *request,
-                                  unlang_stack_frame_t *frame, fr_signal_t action)
-{
-       unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-       int                     i;
-
-       if (action == FR_SIGNAL_CANCEL) {
-               for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, i);
-
-               return;
-       }
-
-       /*
-        *      Signal all of the runnable/running children.
-        */
-       for (i = 0; i < state->num_children; i++) {
-               if (state->children[i].state != CHILD_RUNNABLE) continue;
-
-               unlang_interpret_signal(state->children[i].request, action);
-       }
-}
-
-static unlang_action_t unlang_parallel(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
-       unlang_t const                  *instruction;
-       unlang_group_t                  *g;
-       unlang_parallel_t               *gext;
-       unlang_parallel_state_t         *state;
-
-       int                             i;
-
-       g = unlang_generic_to_group(frame->instruction);
-       if (!g->num_children) {
-               *p_result = RLM_MODULE_NOOP;
-               return UNLANG_ACTION_CALCULATE_RESULT;
-       }
-
-       gext = unlang_group_to_parallel(g);
-
-       /*
-        *      Allocate an array for the children.
-        */
-       MEM(frame->state = state = _talloc_zero_pooled_object(request,
-                                                             sizeof(unlang_parallel_state_t) +
-                                                             (sizeof(state->children[0]) * g->num_children),
-                                                             "unlang_parallel_state_t",
-                                                             g->num_children,
-                                                             (talloc_array_length(request->name) * 2)));
-       if (!state) {
-               *p_result = RLM_MODULE_FAIL;
-               return UNLANG_ACTION_CALCULATE_RESULT;
-       }
-
-       (void) talloc_set_type(state, unlang_parallel_state_t);
-       state->result = RLM_MODULE_FAIL;
-       state->priority = -1;                           /* as-yet unset */
-       state->detach = gext->detach;
-       state->clone = gext->clone;
-       state->num_children = g->num_children;
-
-       /*
-        *      Initialize all of the children.
-        */
-       for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
-               state->children[i].state = CHILD_INIT;
-               state->children[i].instruction = instruction;
-       }
-
-       frame->process = unlang_parallel_process;
-       return unlang_parallel_process(p_result, request, frame);
-}
-
 void unlang_parallel_init(void)
 {
        unlang_register(UNLANG_TYPE_PARALLEL,
@@ -493,6 +390,6 @@ void unlang_parallel_init(void)
                                .name = "parallel",
                                .interpret = unlang_parallel,
                                .signal = unlang_parallel_signal,
-                               .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET
+                               .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_NO_CANCEL
                           });
 }
index 06c615a810e7f062e57a6e2d71aafcb206e264b6..a980f5b2efed4fc9f0dc987f853568dd3874abfb 100644 (file)
  *
  * @copyright 2006-2019 The FreeRADIUS server project
  */
+#include "child_request_priv.h"
 #include "unlang_priv.h"
 
 #ifdef __cplusplus
 extern "C" {
 #endif
 
-/** Parallel child states
- *
- */
-typedef enum {
-       CHILD_INIT = 0,                                 //!< Initial state.
-       CHILD_RUNNABLE,                                 //!< Running/runnable.
-       CHILD_EXITED,                                   //!< Child has exited
-       CHILD_DETACHED,                                 //!< Child has detached.
-       CHILD_CANCELLED,                                //!< Child was cancelled.
-       CHILD_DONE                                      //!< The child has completed.
-} unlang_parallel_child_state_t;
-
-/** Each parallel child has a state, and an associated request
- *
- */
-typedef struct {
-       int                             num;            //!< The child number.
-
-       unlang_parallel_child_state_t   state;          //!< State of the child.
-       request_t                       *request;       //!< Child request.
-       char                            *name;          //!< Cache the request name.
-       unlang_t const                  *instruction;   //!< broken out of g->children
-} unlang_parallel_child_t;
-
 typedef struct {
        rlm_rcode_t                     result;
        int                             priority;
 
-       int                             num_children;   //!< How many children are executing.
-       int                             num_complete;   //!< How many children are complete.
+       unsigned int                    num_children;   //!< How many children are executing.
+       unsigned int                    num_runnable;   //!< How many children are complete.
 
        bool                            detach;         //!< are we creating the child detached
        bool                            clone;          //!< are the children cloned
 
-       unlang_parallel_child_t         children[];     //!< Array of children.
+       unlang_t                        *instruction;   //!< The instruction the children should
+                                                       ///< start executing.
+
+       unlang_child_request_t          children[];     //!< Array of children.
 } unlang_parallel_state_t;
 
 typedef struct {
index efee68db4f815c2d977805bb6b233bf2d40a6082..06fb3ba4fa974b3569dfec812b2b9494c65490ef 100644 (file)
  *
  * @copyright 2006-2019 The FreeRADIUS server project
  */
+
 RCSID("$Id$")
 
 #include <freeradius-devel/server/state.h>
 #include <freeradius-devel/server/tmpl_dcursor.h>
+#include <freeradius-devel/server/request.h>
 #include <freeradius-devel/unlang/action.h>
 #include "unlang_priv.h"
 #include "interpret_priv.h"
 #include "subrequest_priv.h"
-#include "subrequest_child_priv.h"
+#include "child_request_priv.h"
 
 /** Send a signal from parent request to subrequest
  *
  */
-static void unlang_subrequest_signal_child(UNUSED request_t *request, unlang_stack_frame_t *frame,
-                                          fr_signal_t action)
+static void unlang_subrequest_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
 {
-       unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-       request_t                       *child = talloc_get_type_abort(state->child, request_t);
+       unlang_child_request_t          *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+       request_t                       *child = talloc_get_type_abort(cr->request, request_t);
+
+       switch (cr->state) {
+       case CHILD_DETACHED:
+               RDEBUG3("subrequest detached during its execution - Not sending signal to child");
+               return;
+
+       case CHILD_CANCELLED:
+               RDEBUG3("subrequest is cancelled - Not sending signal to child");
+               return;
+
+       default:
+               break;
+       }
 
        /*
         *      Parent should never receive a detach
@@ -84,15 +98,14 @@ static unlang_action_t unlang_subrequest_parent_resume(rlm_rcode_t *p_result, re
                                                       unlang_stack_frame_t *frame)
 {
        unlang_group_t                          *g = unlang_generic_to_group(frame->instruction);
-       unlang_frame_state_subrequest_t         *state = talloc_get_type_abort(frame->state,
-                                                                              unlang_frame_state_subrequest_t);
-       request_t                               *child = state->child;
+       unlang_child_request_t                  *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+       request_t                               *child = cr->request;
        unlang_subrequest_t                     *gext;
 
        /*
         *      Child detached
         */
-       if (!state->child) {
+       if (cr->state == CHILD_DETACHED) {
                RDEBUG3("subrequest detached during its execution - Not updating rcode or reply attributes");
 
                /*
@@ -103,7 +116,11 @@ static unlang_action_t unlang_subrequest_parent_resume(rlm_rcode_t *p_result, re
                return UNLANG_ACTION_EXECUTE_NEXT;
        }
 
-       RDEBUG3("subrequest complete");
+       RDEBUG3("subrequest completeed with rcode %s",
+               fr_table_str_by_value(mod_rcode_table, cr->result.rcode, "<invalid>"));
+
+       *p_result = cr->result.rcode;
+       frame->priority = cr->result.priority;
 
        /*
         *      If there's a no destination tmpl, we're done.
@@ -143,16 +160,22 @@ static unlang_action_t unlang_subrequest_parent_resume(rlm_rcode_t *p_result, re
        return UNLANG_ACTION_CALCULATE_RESULT;
 }
 
-static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, request_t *request,
-                                                    unlang_stack_frame_t *frame)
+/** Allocates a new subrequest and initialises it
+ *
+ */
+static unlang_action_t unlang_subrequest_init(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
 {
-       unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-       request_t                       *child;
-       rlm_rcode_t                     rcode;
-       fr_pair_t                       *vp;
+       unlang_child_request_t  *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+       request_t               *child;
+       fr_pair_t               *vp;
+
+       unlang_group_t          *g;
+       unlang_subrequest_t     *gext;
 
-       unlang_group_t                  *g;
-       unlang_subrequest_t             *gext;
+       /*
+        *      This should only be set for manually pushed subrequests
+        */
+       fr_assert(!cr->config.free_child);
 
        /*
         *      Initialize the state
@@ -164,20 +187,11 @@ static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, requ
        }
 
        gext = unlang_group_to_subrequest(g);
-       child = state->child = unlang_io_subrequest_alloc(request, gext->dict, UNLANG_DETACHABLE);
+       child = unlang_io_subrequest_alloc(request, gext->dict, UNLANG_DETACHABLE);
        if (!child) {
        fail:
-               rcode = RLM_MODULE_FAIL;
-
-               /*
-                *      Pass the result back to the module
-                *      that created the subrequest, or
-                *      use it to modify the current section
-                *      rcode.
-                */
-               if (state->p_result) *state->p_result = rcode;
-
-               *p_result = rcode;
+               *p_result = cr->result.rcode = RLM_MODULE_FAIL;
+               if (cr->result.p_result) *cr->result.p_result = *p_result;
                return UNLANG_ACTION_CALCULATE_RESULT;
        }
        /*
@@ -239,8 +253,11 @@ static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, requ
        /*
         *      Setup the child so it'll inform us when
         *      it resumes, or if it detaches.
+        *
+        *      frame->instruction should be consistent
+        *      as it's allocated by the unlang compiler.
         */
-       if (unlang_subrequest_child_push_resume(child, state) < 0) goto fail;
+       if (unlang_child_request_init(cr, cr, child, NULL, NULL, frame->instruction, false) < 0) goto fail;
 
        /*
         *      Push the first instruction the child's
@@ -249,19 +266,18 @@ static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, requ
        if (unlang_interpret_push(child, g->children, frame->result,
                                  UNLANG_NEXT_SIBLING, UNLANG_SUB_FRAME) < 0) goto fail;
 
-       state->p_result = p_result;
-       state->detachable = true;
-
        /*
-        *      Store/restore session information in the subrequest
-        *      keyed off the exact subrequest keyword.
+        *      Finally, setup the function that will be
+        *      called when the child indicates the
+        *      parent should be resumed.
         */
-       state->session.enable = true;
-       state->session.unique_ptr = frame->instruction;
-       state->session.unique_int = 0;
-
        frame_repeat(frame, unlang_subrequest_parent_resume);
 
+       /*
+        *      This is a common function, either pushed
+        *      onto the parent's stack, or called directly
+        *      from the subrequest instruction..
+        */
        return unlang_subrequest_child_run(p_result, request, frame);   /* returns UNLANG_ACTION_YIELD */
 }
 
@@ -289,6 +305,211 @@ request_t *unlang_subrequest_alloc(request_t *parent, fr_dict_t const *namespace
        return unlang_io_subrequest_alloc(parent, namespace, UNLANG_NORMAL_CHILD);
 }
 
+
+/** Function to run in the context of the parent on resumption
+ *
+ * @note Only executes if unlang_subrequest_child_push was called, not with the normal subrequest keyword.
+ */
+static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, UNUSED request_t *request,
+                                                   unlang_stack_frame_t *frame)
+{
+       unlang_child_request_t          *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+
+       if (cr->result.rcode == RLM_MODULE_NOT_SET) {
+               *p_result = cr->result.rcode = RLM_MODULE_NOOP;
+       }
+
+       if (cr->result.p_result) *cr->result.p_result = cr->result.rcode;
+       cr->result.priority = frame->priority;
+
+       /*
+        *      We can free the child here as we're its parent
+        */
+       if (cr->config.free_child) {
+               if (request_is_detachable(cr->request)) {
+                       unlang_subrequest_detach_and_free(&cr->request);
+               } else {
+                       TALLOC_FREE(cr->request);
+               }
+       }
+
+       return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Function called by the unlang interpreter, or manually to start the child running
+ *
+ * The reason why we do this on the unlang stack is so that _this_ frame
+ * is marked as resumable in the parent, not whatever frame was previously
+ * being processed by the interpreter when the parent was called.
+ *
+ * i.e. after calling unlang_subrequest_child_push, the code in the parent
+ * can call UNLANG_ACTION_PUSHED_CHILD, which will result in _this_ frame
+ * being executed, and _this_ frame can yield.
+ *
+ * @note Called from the parent to start a child running.
+ */
+unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
+                                           unlang_stack_frame_t *frame)
+{
+       unlang_child_request_t          *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+       request_t                       *child = cr->request;
+
+       /*
+        *      No parent means this is a pre-detached child
+        *      so the parent should continue executing.
+        */
+       if (!child || !child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
+
+
+       /*
+        *      Ensure we restore the session state information
+        *      into the child.
+        */
+       if (cr->config.session_unique_ptr) fr_state_restore_to_child(child,
+                                                                    cr->config.session_unique_ptr,
+                                                                    cr->num);
+       /*
+        *      Ensures the child is setup correctly and adds
+        *      it into the runnable queue of whatever owns
+        *      the interpreter.
+        */
+       interpret_child_init(child);
+
+       /*
+        *      This function is being called by something
+        *      other than the subrequest keyword.
+        *
+        *      Set a different resumption function that
+        *      just writes the final rcode out.
+        */
+       if (frame->process == unlang_subrequest_child_run) {
+               frame_repeat(frame, unlang_subrequest_child_done);
+       }
+
+       cr->state = CHILD_RUNNABLE;
+
+       return UNLANG_ACTION_YIELD;
+}
+
+/** Push a pre-existing child back onto the stack as a subrequest
+ *
+ * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
+ * that calls it.
+ *
+ * After the child is no longer required it *MUST* be freed with #unlang_subrequest_detach_and_free.
+ * It's not enough to free it with talloc_free.
+ *
+ * This function should be called _before_ pushing any additional frames onto the child's
+ * stack for it to execute.
+ *
+ * The parent should return UNLANG_ACTION_PUSHED_CHILD, when it's done setting up the
+ * child request.  It should NOT return UNLANG_ACTION_YIELD.
+ *
+ * @param[in] p_result                 Where to write the result of the subrequest.
+ * @param[in] child                    to push.
+ * @param[in] unique_session_ptr       Unique identifier for child's session data.
+ * @param[in] free_child               automatically free the child when it's finished executing.
+ *                                     This is useful if extracting the result from the child is
+ *                                     done using the child's stack, and so the parent never needs
+ *                                     to access it.
+ * @param[in] top_frame                        Set to UNLANG_TOP_FRAME if the interpreter should return.
+ *                                     Set to UNLANG_SUB_FRAME if the interprer should continue.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+
+int unlang_subrequest_child_push(request_t *child,
+                                rlm_rcode_t *p_result, void const *unique_session_ptr, bool free_child, bool top_frame)
+{
+       unlang_child_request_t  *cr;
+       unlang_stack_frame_t    *frame;
+
+       static unlang_t subrequest_instruction = {
+               .type = UNLANG_TYPE_SUBREQUEST,
+               .name = "subrequest",
+               .debug_name = "subrequest",
+               .actions = {
+                       .actions = {
+                               [RLM_MODULE_REJECT]     = 0,
+                               [RLM_MODULE_FAIL]       = 0,
+                               [RLM_MODULE_OK]         = 0,
+                               [RLM_MODULE_HANDLED]    = 0,
+                               [RLM_MODULE_INVALID]    = 0,
+                               [RLM_MODULE_DISALLOW]   = 0,
+                               [RLM_MODULE_NOTFOUND]   = 0,
+                               [RLM_MODULE_NOOP]       = 0,
+                               [RLM_MODULE_UPDATED]    = 0
+                       },
+                       .retry = RETRY_INIT,
+               }
+       };
+
+       fr_assert_msg(free_child || child->parent, "Child's request pointer must not be NULL when calling subrequest_child_push");
+
+       if (!fr_cond_assert_msg(stack_depth_current(child) == 0,
+                               "Child stack depth must be 0 (not %d), when calling subrequest_child_push",
+                               stack_depth_current(child))) return -1;
+
+       /*
+        *      Push a new subrequest frame onto the stack
+        *      of the parent.
+        *
+        *      This allocates memory for the frame state
+        *      which we fill in below.
+        *
+        *      This frame executes once the subrequest has
+        *      completed.
+        */
+       if (unlang_interpret_push(child->parent, &subrequest_instruction,
+                                 RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, top_frame) < 0) {
+               return -1;
+       }
+
+       frame = frame_current(child->parent);
+       frame->process = unlang_subrequest_child_run;
+
+       /*
+        *      Setup the state for the subrequest
+        */
+       cr = talloc_get_type_abort(frame_current(child->parent)->state, unlang_child_request_t);
+
+       /*
+        *      Initialise our frame state, and push the first
+        *      instruction onto the child's stack.
+        *
+        *      This instruction will mark the parent as runnable
+        *      when it executed.
+        */
+       if (unlang_child_request_init(cr, cr, child, p_result, NULL, unique_session_ptr, free_child) < 0) return -1;
+
+       return 0;
+}
+
+/** Add a child request to the runnable queue
+ *
+ * @param[in] request          to add to the runnable queue.
+ * @return
+ *     - 0 on success.
+ *     - -1 on failure.
+ */
+int unlang_subrequest_child_push_and_detach(request_t *request)
+{
+       /*
+        *      Ensures the child is setup correctly and adds
+        *      it into the runnable queue of whatever owns
+        *      the interpreter.
+        */
+       interpret_child_init(request);
+
+       if (request_detach(request) < 0) {
+               RPEDEBUG("Failed detaching request");
+               return -1;
+       }
+
+       return 0;
+}
+
 /** Initialise subrequest ops
  *
  */
@@ -297,8 +518,8 @@ int unlang_subrequest_op_init(void)
        unlang_register(UNLANG_TYPE_SUBREQUEST,
                        &(unlang_op_t){
                                .name = "subrequest",
-                               .interpret = unlang_subrequest_parent_init,
-                               .signal = unlang_subrequest_signal_child,
+                               .interpret = unlang_subrequest_init,
+                               .signal = unlang_subrequest_signal,
                                /*
                                 *      Frame can't be cancelled, because children need to
                                 *      write out status to the parent.  If we don't do this,
@@ -311,16 +532,11 @@ int unlang_subrequest_op_init(void)
                                 *      guaranteed the parent still exists.
                                 */
                                .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_NO_CANCEL,
-                               .frame_state_size = sizeof(unlang_frame_state_subrequest_t),
-                               .frame_state_type = "unlang_frame_state_subrequest_t",
+                               .frame_state_size = sizeof(unlang_child_request_t),
+                               .frame_state_type = "unlang_child_request_t",
                        });
 
-       if (unlang_subrequest_child_op_init() < 0) return -1;
+       if (unlang_child_request_op_init() < 0) return -1;
 
        return 0;
 }
-
-void unlang_subrequest_op_free(void)
-{
-       unlang_subrequest_child_op_free();
-}
index 01ca28284443e0ed2f09d4ce9301704a462e36ae..120d0f5849f9a54d403ee934cf7973754cb56dee 100644 (file)
@@ -23,6 +23,7 @@
  * @copyright 2019 The FreeRADIUS server project
  * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
  */
+
 #ifdef __cplusplus
 extern "C" {
 #endif
@@ -41,11 +42,8 @@ request_t    *unlang_subrequest_alloc(request_t *parent, fr_dict_t const *namespace
 
 void           unlang_subrequest_detach_and_free(request_t **child);
 
-int            unlang_subrequest_lifetime_set(request_t *request);
-
-int            unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
-                                            unlang_subrequest_session_t const *session,
-                                            bool free_child, bool top_frame);
+int            unlang_subrequest_child_push(request_t *child,
+                                            rlm_rcode_t *p_result, void const *unique_session_ptr, bool free_child, bool top_frame);
 
 int            unlang_subrequest_child_push_and_detach(request_t *child);
 
diff --git a/src/lib/unlang/subrequest_child.c b/src/lib/unlang/subrequest_child.c
deleted file mode 100644 (file)
index a568f68..0000000
+++ /dev/null
@@ -1,500 +0,0 @@
-/*
- *   This program is free software; you can redistribute it and/or modify
- *   it under the terms of the GNU General Public License as published by
- *   the Free Software Foundation; either version 2 of the License, or
- *   (at your option) any later version.
- *
- *   This program is distributed in the hope that it will be useful,
- *   but WITHOUT ANY WARRANTY; without even the implied warranty of
- *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *   GNU General Public License for more details.
- *
- *   You should have received a copy of the GNU General Public License
- *   along with this program; if not, write to the Free Software
- *   Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- *
- * @file unlang/subrequest.c
- * @brief Unlang "subrequest" and "detach" keyword evaluation.
- *
- * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- */
-#include <freeradius-devel/server/state.h>
-#include "interpret_priv.h"
-#include "subrequest_priv.h"
-#include "subrequest_child_priv.h"
-
-typedef struct {
-       unlang_frame_state_subrequest_t *parent_state;
-} unlang_frame_state_subrequest_child_t;
-
-/** Holds a synthesised instruction that we insert into the parent request
- *
- */
-static unlang_subrequest_t     *subrequest_instruction;
-
-static fr_dict_t const *dict_freeradius;
-
-extern fr_dict_autoload_t subrequest_dict[];
-fr_dict_autoload_t subrequest_dict[] = {
-       { .out = &dict_freeradius, .proto = "freeradius" },
-       { NULL }
-};
-
-static fr_dict_attr_t const *request_attr_request_lifetime;
-
-extern fr_dict_attr_autoload_t subrequest_dict_attr[];
-fr_dict_attr_autoload_t subrequest_dict_attr[] = {
-       { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
-       { NULL }
-};
-
-
-static unlang_subrequest_t subrequest_child_instruction = {
-       .group = {
-               .self = {
-                       .type = UNLANG_TYPE_SUBREQUEST_CHILD,
-                       .name = "subrequest-child",
-                       .debug_name = "subrequest-child",
-                       .actions = {
-                               .actions = {
-                                       [RLM_MODULE_REJECT]     = 0,
-                                       [RLM_MODULE_FAIL]       = 0,
-                                       [RLM_MODULE_OK]         = 0,
-                                       [RLM_MODULE_HANDLED]    = 0,
-                                       [RLM_MODULE_INVALID]    = 0,
-                                       [RLM_MODULE_DISALLOW]   = 0,
-                                       [RLM_MODULE_NOTFOUND]   = 0,
-                                       [RLM_MODULE_NOOP]       = 0,
-                                       [RLM_MODULE_UPDATED]    = 0
-                               },
-                               .retry = RETRY_INIT,
-                       },
-               }
-       }
-};
-
-/** Event handler to free a detached child
- *
- */
-static void unlang_detached_max_request_time(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
-{
-       request_t *request = talloc_get_type_abort(uctx, request_t);
-
-       RDEBUG("Reached Request-Lifetime.  Forcibly stopping request");
-
-       unlang_interpret_signal(request, FR_SIGNAL_CANCEL);     /* Request should now be freed */
-}
-
-/** Initialize a detached child
- *
- *  Detach it from the parent, set up it's lifetime, and mark it as
- *  runnable.
- */
-int unlang_subrequest_lifetime_set(request_t *request)
-{
-       fr_pair_t               *vp;
-
-       /*
-        *      Set Request Lifetime
-        */
-       vp = fr_pair_find_by_da(&request->control_pairs, NULL, request_attr_request_lifetime);
-       if (!vp || (vp->vp_uint32 > 0)) {
-               fr_time_delta_t when = fr_time_delta_wrap(0);
-               fr_timer_t **ev_p;
-
-               if (!vp) {
-                       when = fr_time_delta_add(when, fr_time_delta_from_sec(30)); /* default to 30s if not set */
-
-               } else if (vp->vp_uint32 > 3600) {
-                       RWDEBUG("Request-Timeout can be no more than 3600 seconds");
-                       when = fr_time_delta_add(when, fr_time_delta_from_sec(3600));
-
-               } else if (vp->vp_uint32 < 5) {
-                       RWDEBUG("Request-Timeout can be no less than 5 seconds");
-                       when = fr_time_delta_add(when ,fr_time_delta_from_sec(5));
-
-               } else {
-                       when = fr_time_delta_from_sec(vp->vp_uint32);
-               }
-
-               ev_p = talloc_size(request, sizeof(*ev_p));
-               memset(ev_p, 0, sizeof(*ev_p));
-
-               if (fr_timer_in(request, unlang_interpret_event_list(request)->tl, ev_p, when,
-                               false, unlang_detached_max_request_time, request) < 0) {
-                       talloc_free(ev_p);
-                       return -1;
-               }
-       }
-
-       return 0;
-}
-
-/** Process a detach signal in the child
- *
- * This processes any detach signals the child receives
- * The child doesn't actually do the detaching
- */
-static void unlang_subrequest_child_signal(request_t *request, UNUSED unlang_stack_frame_t *frame, fr_signal_t action)
-{
-       unlang_frame_state_subrequest_child_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_child_t);
-       unlang_frame_state_subrequest_t *parent_state;
-
-       /*
-        *      We're already detached so we don't
-        *      need to notify the parent we're
-        *      waking up, and we don't need to detach
-        *      again...
-        */
-       if (!request->parent) return;
-
-       parent_state = talloc_get_type_abort(state->parent_state, unlang_frame_state_subrequest_t);
-
-       /*
-        *      Ignore signals which aren't detach, and ar
-        *      and ignore the signal if we have no parent.
-        */
-       switch (action) {
-       case FR_SIGNAL_DETACH:
-               /*
-                *      Place child's state back inside the parent
-                */
-               if (parent_state->session.enable) fr_state_store_in_parent(request,
-                                                                          parent_state->session.unique_ptr,
-                                                                          parent_state->session.unique_int);
-
-               if (!fr_cond_assert(unlang_subrequest_lifetime_set(request) == 0)) {
-                       REDEBUG("Child could not be detached");
-                       return;
-               }
-
-               RDEBUG3("Removing subrequest from parent, and marking parent as runnable");
-
-               /*
-                *      Indicate to the parent there's no longer a child
-                */
-               parent_state->child = NULL;
-
-               /*
-                *      Tell the parent to resume
-                */
-               unlang_interpret_mark_runnable(request->parent);
-               break;
-
-       /*
-        *      This frame is not cancellable, so FR_SIGNAL_CANCEL
-        *      does nothing.  If the child is cancelled in its
-        *      entirety, then its stack will unwind up to this point
-        *      and unlang_subrequest_child_done will mark the
-        *      parent as runnable.  We don't need to do anything here.
-        */
-       default:
-               return;
-       }
-}
-
-/** When the child is done, tell the parent that we've exited.
- *
- * This is pushed as a frame at the top of the child's stack, so when
- * the child is done executing, it runs this to inform the parent
- * that its done.
- */
-static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
-       unlang_frame_state_subrequest_child_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_child_t);
-       unlang_frame_state_subrequest_t *parent_state;
-
-       /*
-        *      Child was detached, nothing more to do.
-        *
-        *      This frame was left on the stack when the child
-        *      detached.  It's normally meant to trigger a
-        *      resume in the parent, but as there is no parent
-        *      it just becomes a noop and gets popped.
-        *
-        *      This is cheaper/less complex then rooting it
-        *      out at detach time and unsetting the repeat
-        *      function.
-        */
-       if (!request->parent) return UNLANG_ACTION_CALCULATE_RESULT;
-
-       /*
-        *      'state' in this context, is the frame state
-        *      in the parent request, so we cannot examine it
-        *      until AFTER we've determined there is still
-        *      a parent, else the memory could've been freed.
-        *
-        *      i.e. don't move the get_type_abort call onto
-        *      the same line as the state variable declaration.
-        */
-       parent_state = talloc_get_type_abort(state->parent_state, unlang_frame_state_subrequest_t);
-
-       /*
-        *      Place child state back inside the parent
-        */
-       if (parent_state->session.enable) fr_state_store_in_parent(request,
-                                                                  parent_state->session.unique_ptr,
-                                                                  parent_state->session.unique_int);
-       /*
-        *      Record the child's result
-        */
-       if (parent_state->p_result) *parent_state->p_result = *p_result;
-
-       /*
-        *      Resume the parent
-        */
-       unlang_interpret_mark_runnable(request->parent);
-
-       return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Push a resumption frame onto a child's stack
- *
- * This is necessary so that the child informs its parent when it's done/detached
- * and so that the child responds to detach signals.
- */
-int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *parent_state)
-{
-       int ret;
-       unlang_frame_state_subrequest_child_t *state;
-       unlang_stack_frame_t *frame;
-
-       /* Sets up the frame for us to use immediately */
-       ret = unlang_interpret_push_instruction(child, &subrequest_child_instruction, RLM_MODULE_NOOP, true);
-       if (ret < 0) return -1;
-
-       frame = frame_current(child);
-       state = frame->state;
-       state->parent_state = parent_state;
-       repeatable_set(frame);  /* Run this on the way back up */
-
-       return 0;
-}
-
-/** Function to run in the context of the parent on resumption
- *
- */
-static unlang_action_t unlang_subrequest_calculate_result(rlm_rcode_t *p_result, UNUSED request_t *request,
-                                                         unlang_stack_frame_t *frame)
-{
-       unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-
-       if (*p_result == RLM_MODULE_NOT_SET) *p_result = RLM_MODULE_NOOP;
-
-       if (state->free_child) unlang_subrequest_detach_and_free(&state->child);
-
-       return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Function called by the unlang interpreter to start the child running
- *
- * The reason why we do this on the unlang stack is so that _this_ frame
- * is marked as resumable in the parent, not whatever frame was previously
- * being processed by the interpreter when the parent was called.
- *
- * i.e. after calling unlang_subrequest_child_push, the code in the parent
- * can call UNLANG_ACTION_PUSHED_CHILD, which will result in _this_ frame
- * being executed, and _this_ frame can yield.
- *
- * @note Called from the parent to start a child running.
- */
-unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
-                                           unlang_stack_frame_t *frame)
-{
-       unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-       request_t                       *child = state->child;
-
-       /*
-        *      No parent means this is a pre-detached child
-        *      so the parent should continue executing.
-        */
-       if (!child || !child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
-
-
-       /*
-        *      Ensure we restore the session state information
-        *      into the child.
-        */
-       if (state->session.enable) fr_state_restore_to_child(child,
-                                                            state->session.unique_ptr,
-                                                            state->session.unique_int);
-       /*
-        *      Ensures the child is setup correctly and adds
-        *      it into the runnable queue of whatever owns
-        *      the interpreter.
-        */
-       interpret_child_init(child);
-
-       /*
-        *      Don't run this function again on resumption
-        */
-       if (frame->process == unlang_subrequest_child_run) frame->process = unlang_subrequest_calculate_result;
-       repeatable_set(frame);
-
-       return UNLANG_ACTION_YIELD;
-}
-
-/** Push a pre-existing child back onto the stack as a subrequest
- *
- * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
- * that calls it.
- *
- * After the child is no longer required it *MUST* be freed with #unlang_subrequest_detach_and_free.
- * It's not enough to free it with talloc_free.
- *
- * This function should be called _before_ pushing any additional frames onto the child's
- * stack for it to execute.
- *
- * The parent should return UNLANG_ACTION_PUSHED_CHILD, when it's done setting up the
- * child request.  It should NOT return UNLANG_ACTION_YIELD.
- *
- * @param[in] out              Where to write the result of the subrequest.
- * @param[in] child            to push.
- * @param[in] session          control values.  Whether we restore/store session info.
- * @param[in] free_child       automatically free the child when it's finished executing.
- *                             This is useful if extracting the result from the child is
- *                             done using the child's stack, and so the parent never needs
- *                             to access it.
- * @param[in] top_frame                Set to UNLANG_TOP_FRAME if the interpreter should return.
- *                             Set to UNLANG_SUB_FRAME if the interprer should continue.
- * @return
- *     - 0 on success.
- *     - -1 on failure.
- */
-int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
-                                unlang_subrequest_session_t const *session,
-                                bool free_child, bool top_frame)
-{
-       unlang_frame_state_subrequest_t *state;
-       unlang_stack_frame_t            *frame;
-
-       fr_assert_msg(child->parent, "Child's request pointer must not be NULL when calling subrequest_child_push");
-
-       /*
-        *      Push a new subrequest frame onto the stack
-        *
-        *      This allocates memory for the frame state
-        *      which we fill in below.
-        */
-       if (unlang_interpret_push(child->parent, &subrequest_instruction->group.self,
-                                 RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, top_frame) < 0) {
-               return -1;
-       }
-
-       frame = frame_current(child->parent);
-       frame->process = unlang_subrequest_child_run;
-
-       /*
-        *      Setup the state for the subrequest
-        */
-       state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t);
-       state->p_result = out;
-       state->child = child;
-       state->session = *session;
-       state->free_child = free_child;
-
-       if (!fr_cond_assert_msg(stack_depth_current(child) == 0,
-                               "Child stack depth must be 0 (not %d), when calling subrequest_child_push",
-                               stack_depth_current(child))) return -1;
-
-       /*
-        *      Push a resumption frame onto the stack
-        *      so the child calls its parent when it's
-        *      complete.
-        */
-       if (unlang_subrequest_child_push_resume(child, state) < 0) return -1;
-
-       return 0;
-}
-
-int unlang_subrequest_child_push_and_detach(request_t *request)
-{
-       /*
-        *      Ensures the child is setup correctly and adds
-        *      it into the runnable queue of whatever owns
-        *      the interpreter.
-        */
-       interpret_child_init(request);
-
-       if ((unlang_subrequest_lifetime_set(request) < 0) || (request_detach(request) < 0)) {
-               RPEDEBUG("Failed detaching request");
-               return -1;
-       }
-
-       return 0;
-}
-
-int unlang_subrequest_child_op_init(void)
-{
-       if (fr_dict_autoload(subrequest_dict) < 0) {
-               PERROR("%s", __FUNCTION__);
-               return -1;
-       }
-       if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) {
-               PERROR("%s", __FUNCTION__);
-               return -1;
-       }
-
-       /*
-        *      Needs to be dynamically allocated
-        *      so that talloc_get_type works
-        *      correctly.
-        */
-       MEM(subrequest_instruction = talloc(NULL, unlang_subrequest_t));
-       *subrequest_instruction = (unlang_subrequest_t){
-               .group = {
-                       .self = {
-                               .type = UNLANG_TYPE_SUBREQUEST,
-                               .name = "subrequest",
-                               .debug_name = "subrequest",
-                               .actions = {
-                                       .actions = {
-                                               [RLM_MODULE_REJECT]     = 0,
-                                               [RLM_MODULE_FAIL]       = 0,
-                                               [RLM_MODULE_OK]         = 0,
-                                               [RLM_MODULE_HANDLED]    = 0,
-                                               [RLM_MODULE_INVALID]    = 0,
-                                               [RLM_MODULE_DISALLOW]   = 0,
-                                               [RLM_MODULE_NOTFOUND]   = 0,
-                                               [RLM_MODULE_NOOP]       = 0,
-                                               [RLM_MODULE_UPDATED]    = 0
-                                       },
-                                       .retry = RETRY_INIT,
-                               },
-                       }
-               }
-       };
-
-       unlang_register(UNLANG_TYPE_SUBREQUEST_CHILD,
-                       &(unlang_op_t){
-                               .name = "subrequest-child",
-                               .interpret = unlang_subrequest_child_done,
-                               .signal = unlang_subrequest_child_signal,
-                               /*
-                                *      Frame can't be cancelled, because children need to
-                                *      write out status to the parent.  If we don't do this,
-                                *      then all children must be detachable and must detach
-                                *      so they don't try and write out status to a "done"
-                                *      parent.
-                                *
-                                *      It's easier to allow the child/parent relationship
-                                *      to end normally so that non-detachable requests are
-                                *      guaranteed the parent still exists.
-                                */
-                               .flag = UNLANG_OP_FLAG_NO_CANCEL,
-                               .frame_state_size = sizeof(unlang_frame_state_subrequest_child_t),
-                               .frame_state_type = "unlang_frame_state_subrequest_child_t"
-                       });
-
-       return 0;
-}
-
-void unlang_subrequest_child_op_free(void)
-{
-       fr_dict_autofree(subrequest_dict);
-       talloc_free(subrequest_instruction);
-}
diff --git a/src/lib/unlang/subrequest_child_priv.h b/src/lib/unlang/subrequest_child_priv.h
deleted file mode 100644 (file)
index e7eeda8..0000000
+++ /dev/null
@@ -1,42 +0,0 @@
-#pragma once
-/*
- *  This program is free software; you can redistribute it and/or modify
- *  it under the terms of the GNU General Public License as published by
- *  the Free Software Foundation; either version 2, or (at your option)
- *  any later version.
- *
- *  This program is distributed in the hope that it will be useful,
- *  but WITHOUT ANY WARRANTY; without even the implied warranty of
- *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
- *  GNU General Public License for more details.
- *
- *  You should have received a copy of the GNU General Public License
- *  along with this program; if not, write to the Free Software Foundation,
- *  Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-/**
- * $Id$
- *
- * @file unlang/subrequest_child_priv.h
- *
- * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- */
-#include <freeradius-devel/server/request.h>
-#include "subrequest_priv.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-unlang_action_t unlang_subrequest_child_run(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame);
-
-int    unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state);
-
-int    unlang_subrequest_child_op_init(void);
-
-void   unlang_subrequest_child_op_free(void);
-
-#ifdef __cplusplus
-}
-#endif
index a01fe09aaecb05203bd48590e57640091130900d..12944514c676c6c8eafa200eaa7abb0fb4b9fa3c 100644 (file)
@@ -45,18 +45,6 @@ typedef struct {
                                                        ///< if the packet-type is static.
 } unlang_subrequest_t;
 
-/** Parameters for initialising the subrequest (parent's frame state)
- *
- */
-typedef struct {
-       rlm_rcode_t                     *p_result;                      //!< Where to store the result.
-       request_t                       *child;                         //!< Pre-allocated child request.
-       bool                            free_child;                     //!< Whether we should free the child after
-                                                                       ///< it completes.
-       bool                            detachable;                     //!< Whether the request can be detached.
-       unlang_subrequest_session_t     session;                        //!< Session configuration.
-} unlang_frame_state_subrequest_t;
-
 /** Cast a group structure to the subrequest keyword extension
  *
  */
@@ -73,6 +61,9 @@ static inline unlang_group_t *unlang_subrequest_to_group(unlang_subrequest_t *su
        return (unlang_group_t *)subrequest;
 }
 
+unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
+                                           unlang_stack_frame_t *frame);
+
 int unlang_subrequest_detach_child(request_t *request);
 
 #ifdef __cplusplus
index 3402ed3a8cfe3d9ab906bf0e7894ae0b0a56cc7e..3812bc4deb5ceb592db9e7b524afb1f130479f62 100644 (file)
@@ -28,6 +28,7 @@
 #include <freeradius-devel/server/cf_util.h> /* Need CONF_* definitions */
 #include <freeradius-devel/server/map_proc.h>
 #include <freeradius-devel/server/modpriv.h>
+#include <freeradius-devel/server/time_tracking.h>
 #include <freeradius-devel/util/debug.h>
 #include <freeradius-devel/unlang/base.h>
 #include <freeradius-devel/io/listen.h>
@@ -63,7 +64,7 @@ typedef enum {
        UNLANG_TYPE_MAP,                        //!< Mapping section (like #UNLANG_TYPE_UPDATE, but uses
                                                //!< values from a #map_proc_t call).
        UNLANG_TYPE_SUBREQUEST,                 //!< create a child subrequest
-       UNLANG_TYPE_SUBREQUEST_CHILD,           //!< a frame at the top of a child's request stack used to signal the
+       UNLANG_TYPE_CHILD_REQUEST,              //!< a frame at the top of a child's request stack used to signal the
                                                ///< parent when the child is complete.
        UNLANG_TYPE_DETACH,                     //!< detach a child
        UNLANG_TYPE_CALL,                       //!< call another virtual server
@@ -290,7 +291,6 @@ typedef struct {
        request_t               *request;
        int                     depth;                          //!< of this retry structure
        fr_retry_state_t        state;
-       fr_time_t               timeout;
        uint32_t                count;
        fr_timer_t              *ev;
 } unlang_retry_t;
@@ -758,8 +758,6 @@ void                unlang_parallel_init(void);
 
 int            unlang_subrequest_op_init(void);
 
-void           unlang_subrequest_op_free(void);
-
 void           unlang_detach_init(void);
 
 void           unlang_switch_init(void);
index 1b14d305a60f4523ba374b51deb7ee0bcbbecf27..52e6910521f9c3451063a30d99e5e71074343da2 100644 (file)
@@ -826,8 +826,8 @@ static unlang_action_t eap_method_select(rlm_rcode_t *p_result, module_ctx_t con
         *      This must be done before pushing frames onto
         *      the child's stack.
         */
-       if (unlang_subrequest_child_push(&eap_session->submodule_rcode, eap_session->subrequest,
-                                        &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session },
+       if (unlang_subrequest_child_push(eap_session->subrequest, &eap_session->submodule_rcode,
+                                        eap_session,
                                         false, UNLANG_SUB_FRAME) < 0) {
        child_fail:
                unlang_interpet_frame_discard(request); /* Ensure the yield frame doesn't stick around */
index 07405f9cf8465f7a84cdcecd3a677ddd5d2529d9..e34d305a9a174d0e2b010a48c54aa926c628db8f 100644 (file)
@@ -579,8 +579,8 @@ unlang_action_t eap_peap_process(rlm_rcode_t *p_result, request_t *request,
                RDEBUG2("No tunnel username (SSL resumption?)");
        }
 
-       if (unlang_subrequest_child_push(&eap_session->submodule_rcode, child,
-                                        &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = child },
+       if (unlang_subrequest_child_push(child, &eap_session->submodule_rcode,
+                                        child,
                                         false, UNLANG_SUB_FRAME) < 0) goto finish;
        if (unlang_function_push(child, NULL, process_reply, NULL, 0,
                                 UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
index bacd7323336cccf3f26da2f5bcd925fadf946d20..7464e48105fd99a630aa8a095dd47bc926067e32 100644 (file)
@@ -1,3 +1,6 @@
+#
+#  PRE: subrequest
+#
 parallel {
        group {
                parent.control += {
diff --git a/src/tests/keywords/parallel-child-timeout b/src/tests/keywords/parallel-child-timeout
new file mode 100644 (file)
index 0000000..818d9d5
--- /dev/null
@@ -0,0 +1,23 @@
+#
+#  PRE: parallel
+#
+try {
+       parallel {
+               timeout 1s {
+                       %time.advance(2s)
+                       parent.control.NAS-Port += 1
+               }
+               # Ensure a timeout in one child does not affect the other
+               group {
+                       parent.control.NAS-Port += 1
+                       ok
+               }
+       }
+}
+catch timeout {
+       if (!(%{control.NAS-Port[#]} == 1)) {
+               test_fail
+       }
+
+       success
+}
diff --git a/src/tests/keywords/parallel-parent-timeout b/src/tests/keywords/parallel-parent-timeout
new file mode 100644 (file)
index 0000000..1b4d624
--- /dev/null
@@ -0,0 +1,29 @@
+#
+#  PRE: parallel
+#
+try {
+       timeout 1s {
+               parallel {
+                       # This child should get cancelled by the parent
+                       group {
+                               delay_10s
+                               parent.control.NAS-Port += 1
+                               ok
+                       }
+                       # This child should complete
+                       group {
+                               parent.control.NAS-Port += 1
+                               # Trigger the parent's timeout section so we don't have to wait
+                               %time.advance(2s)
+                               ok
+                       }
+               }
+       }
+}
+catch timeout {
+       if (!(%{control.NAS-Port[#]} == 1)) {
+               test_fail
+       }
+
+       success
+}
index 06473df9269c7d6e20162704cbfa1c7f7302f4c7..b5a1398cfd4f65c0d27778e12768a42874c0e799 100644 (file)
@@ -4,13 +4,13 @@ subrequest ::Access-Request {
        ok {
                ok = 10
        }
-       noop {
+       updated {
                fail = 1
        }
 }
 
 if (ok) {
        success
-} elsif (noop) {
+} else {
        test_fail
 }
diff --git a/src/tests/keywords/subrequest-timeout-parent b/src/tests/keywords/subrequest-timeout-parent
new file mode 100644 (file)
index 0000000..db50fbe
--- /dev/null
@@ -0,0 +1,19 @@
+#
+#  PRE: subrequest try timeout
+#
+try {
+       timeout 100ms {
+               subrequest ::Access-Request {
+                       %time.advance(500ms)    # Smoke test, see if things explode
+                       parent.control.User-Name := 'bob'
+                       success
+               }
+       }
+}
+catch timeout {
+       if (control.User-Name == 'bob') {
+               test_fail
+       } else {
+               success
+       }
+}