]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Execute process modules using the unlang stack
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 31 Mar 2021 15:06:23 +0000 (16:06 +0100)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 31 Mar 2021 15:06:23 +0000 (16:06 +0100)
17 files changed:
src/bin/unit_test_module.c
src/lib/io/worker.c
src/lib/server/module.c
src/lib/server/virtual_servers.c
src/lib/tls/cache.c
src/lib/unlang/all.mk
src/lib/unlang/call.c
src/lib/unlang/interpret.c
src/lib/unlang/interpret.h
src/lib/unlang/interpret_priv.h [new file with mode: 0644]
src/lib/unlang/interpret_synchronous.c [new file with mode: 0644]
src/lib/unlang/io.c
src/lib/unlang/parallel.c
src/lib/unlang/subrequest.c
src/lib/unlang/unlang_priv.h
src/modules/proto_bfd/proto_bfd.c
src/modules/proto_ldap_sync/proto_ldap_sync.c

index 2edadc0a9e72688fa94552268fed0a25f433198c..4cff9e1559f9569b1be0d865f078f6460299b255 100644 (file)
@@ -36,6 +36,7 @@ RCSID("$Id$")
 #include <freeradius-devel/tls/base.h>
 
 #include <freeradius-devel/unlang/base.h>
+#include <freeradius-devel/unlang/call.h>
 
 #include <freeradius-devel/protocol/freeradius/freeradius.internal.h>
 #include <freeradius-devel/radius/radius.h>
@@ -457,87 +458,10 @@ static rlm_rcode_t mod_map_proc(UNUSED void *mod_inst, UNUSED void *proc_inst, U
        return RLM_MODULE_FAIL;
 }
 
-static void request_run(fr_event_list_t *el, request_t *request)
+static void request_run(request_t *request)
 {
-       rlm_rcode_t     rcode;
-       module_method_t process;
-       void            *inst;
-       fr_dict_enum_t  *dv;
-       fr_heap_t       *backlog;
-       /*
-        *      Record the number of FDs and timers
-        *      inserted by modules before we start
-        *      running requests.
-        */
-       uint64_t        base_timers = fr_event_list_num_timers(el);
-       uint64_t        base_fds = fr_event_list_num_fds(el);
-
-       dv = fr_dict_enum_by_value(attr_packet_type, fr_box_uint32(request->packet->code));
-       if (!dv) return;
-
-       if (virtual_server_get_process_by_name(request->server_cs, dv->name, &process, &inst) < 0) {
-               REDEBUG("Cannot run virtual server '%s' - %s", cf_section_name2(request->server_cs), fr_strerror());
-               return;
-       }
-
-       MEM(backlog = fr_heap_talloc_alloc(request, fr_pointer_cmp, request_t, runnable_id));
-       request->backlog = backlog;
-       request->el = el;
-
-       /*
-        *      Don't check unlang_action_t, this may have
-        *      added detached children, in which case
-        *      we need to run them...
-        */
-       (void)process(&rcode, &(module_ctx_t){ .instance = inst }, request);
-
-       while ((fr_event_list_num_timers(el) > base_timers) ||
-              (fr_event_list_num_fds(el) > base_fds) ||
-              (fr_heap_num_elements(backlog) > 0)) {
-               request_t       *runnable;
-               bool            wait_for_event;
-               int             num_events;
-
-               /*
-                *      Check if we need to wait for I/O
-                */
-               wait_for_event = (fr_heap_num_elements(backlog) == 0);
-
-               num_events = fr_event_corral(el, fr_time(), wait_for_event);
-               if (num_events < 0) {
-                       PERROR("Failed retrieving events");
-                       break;
-               }
-
-               /*
-                *      Service outstanding events.
-                */
-               if (num_events > 0) fr_event_service(el);
-
-               /*
-                *      If there's requests in the backlog
-                *      run all of them.
-                */
-               while ((runnable = fr_heap_pop(backlog))) {
-                       if (runnable->async->process(&rcode,
-                                                    &(module_ctx_t){ .instance = inst },
-                                                    runnable) != UNLANG_ACTION_YIELD) {
-                               /*
-                                *      It's a detached child, and
-                                *      it's done, free it!
-                                */
-                               if ((runnable != request) && !runnable->parent) talloc_free(runnable);
-                       }
-               }
-       };
-
-       DEBUG2("No more requests remaining");
-
-       /*
-        *      We do NOT run detached child requests.  We just ignore
-        *      them.
-        */
-       talloc_free(backlog);
+       virtual_server_entry_point_set(request);
+       (void)unlang_interpret_synchronous(request);
 }
 
 static request_t *request_clone(request_t *old)
@@ -962,7 +886,7 @@ int main(int argc, char *argv[])
        }
 
        if (count == 1) {
-               request_run(el, request);
+               request_run(request);
        } else {
                int i;
                request_t *old = request_clone(request);
@@ -970,7 +894,7 @@ int main(int argc, char *argv[])
 
                for (i = 0; i < count; i++) {
                        request = request_clone(old);
-                       request_run(el, request);
+                       request_run(request);
                        talloc_free(request);
                }
        }
index 39e134b728526dfa1cd3d3d81900e0f5ea2e3bac..ac8949910c646b17df3a26f8ece2933b9531ed91 100644 (file)
@@ -83,6 +83,8 @@ struct fr_worker_s {
        char const              *name;          //!< name of this worker
        fr_worker_config_t      config;         //!< external configuration
 
+       unlang_interpret_t      *intp;          //!< Worker's local interpreter.
+
        pthread_t               thread_id;      //!< my thread ID
 
        fr_log_t const          *log;           //!< log destination
@@ -508,9 +510,6 @@ finished:
        request->async->packet_ctx = NULL;
        request->async->listen = NULL;
 #endif
-
-       DEBUG3("Freeing request %s", request->name);
-       talloc_free(request);
 }
 
 
@@ -596,6 +595,9 @@ static void worker_max_request_time(UNUSED fr_event_list_t *el, UNUSED fr_time_t
                 *      unique.
                 */
                worker_send_reply(worker, request, 1, now);
+
+               DEBUG3("Freeing request %s", request->name);
+               talloc_free(request);
        }
 
        /*
@@ -717,6 +719,11 @@ static void worker_request_bootstrap(fr_worker_t *worker, fr_channel_data_t *cd,
 
        worker_request_init(worker, request, now);
 
+       /*
+        *      Associate our interpreter with the request
+        */
+       unlang_interpret_set(request, worker->intp);
+
        request->packet->timestamp = cd->request.recv_time; /* Legacy - Remove once everything looks at request->async */
 
        /*
@@ -733,7 +740,6 @@ static void worker_request_bootstrap(fr_worker_t *worker, fr_channel_data_t *cd,
 
        request->async->recv_time = cd->request.recv_time;
 
-
        request->async->listen = cd->listen;
        request->async->packet_ctx = cd->packet_ctx;
        listen = request->async->listen;
@@ -854,100 +860,6 @@ nak:
        worker_request_time_tracking_start(worker, request, now);
 }
 
-
-/** Run a request
- *
- *  Until it either yields, or is done.
- *
- *  This function is also responsible for sending replies, and
- *  cleaning up the request.
- *
- * @param[in] worker the worker
- * @param[in] start the current time
- */
-static inline CC_HINT(always_inline) void worker_run_request(fr_worker_t *worker, fr_time_t start)
-{
-       rlm_rcode_t final;
-       request_t *request;
-       fr_time_t now;
-
-       WORKER_VERIFY;
-
-       now = start;
-
-       /*
-        *      Busy-loop running requests for 0.1ms.  another
-        *      request.  This change means that the worker checks the
-        *      event loop fewer times per second, instead of after
-        *      every request.
-        */
-       while (((now - start) < (NSEC / 100000)) &&
-              ((request = fr_heap_pop(worker->runnable)) != NULL)) {
-               unlang_action_t action;
-
-               REQUEST_VERIFY(request);
-               fr_assert(request->runnable_id < 0);
-               fr_time_tracking_resume(&request->async->tracking, now);
-
-               fr_assert(request->parent == NULL);
-               fr_assert(request->async->process != NULL);
-
-               RDEBUG("Running request");
-
-               /*
-                *      For real requests, if the channel is gone,
-                *      just stop the request and free it.
-                */
-               if (request->async->channel && !fr_channel_active(request->async->channel)) {
-                       worker_stop_request(worker, request, now);
-                       talloc_free(request);
-                       return;
-               }
-
-               /*
-                *      Everything else, run the request.
-                */
-               action = request->async->process(&final, &(module_ctx_t){ .instance = request->async->process_inst }, request);
-
-               if (action == UNLANG_ACTION_YIELD) {
-                       now = fr_time();
-                       fr_time_tracking_yield(&request->async->tracking, now);
-
-               } else {
-                       /*
-                        *      If we're running a real request, then the final
-                        *      indentation MUST be zero.  Otherwise we skipped
-                        *      something!
-                        *
-                        *      Also check that the request is NOT marked as
-                        *      "yielded", but is in fact done.
-                        *
-                        *      @todo - check that the stack is at frame 0, otherwise
-                        *      more things have gone wrong.
-                        */
-                       fr_assert_msg(request->parent || (request->log.unlang_indent == 0),
-                                     "Request %s bad log indentation - expected 0 got %u", request->name, request->log.unlang_indent);
-                       fr_assert_msg(!unlang_interpret_is_resumable(request),
-                                     "Request %s is marked as yielded at end of processing", request->name);
-
-                       RDEBUG("Done request");
-
-                       /*
-                        *      Only real packets are in the dedup tree.  And even
-                        *      then, only some of the time.
-                        */
-                       if (request_is_external(request) && request->async->listen->track_duplicates) {
-                               (void) rbtree_delete_by_data(worker->dedup, request);
-                       }
-
-                       now = fr_time();
-                       worker_send_reply(worker, request, 0, now);
-                       now = fr_time(); /* send_reply might take a while */
-               }
-       }
-}
-
-
 /**
  *  Track a request_t in the "runnable" heap.
  */
@@ -1041,6 +953,167 @@ void fr_worker_destroy(fr_worker_t *worker)
        talloc_free(worker);
 }
 
+/** Internal request (i.e. one generated by the interpreter) is now complete
+ *
+ */
+static void _worker_request_internal_init(request_t *request, void *uctx)
+{
+       fr_time_t       *now = fr_time();
+       fr_worker_t     *worker = uctx;
+
+       worker_request_init(worker, request, now);
+
+       /*
+        *      Requests generated by the interpreter
+        *      are always marked up as internal.
+        */
+       fr_assert(request_is_internal(request));
+       worker_request_time_tracking_start(worker, request, now);
+}
+
+/** Internal request (i.e. one generated by the interpreter) is now complete
+ *
+ */
+static void _worker_request_internal_done(request_t *request, UNUSED rlm_rcode_t rcode, void *uctx)
+{
+       fr_time_t       *now = fr_time();
+       fr_worker_t     *worker = uctx;
+
+       fr_time_tracking_end(&worker->predicted, &request->async->tracking, now);
+
+       DEBUG3("Freeing request %s", request->name);
+       talloc_free(request);
+}
+
+/** External request is now complete
+ *
+ */
+static void _worker_request_external_done(request_t *request, UNUSED rlm_rcode_t rcode, void *uctx)
+{
+       fr_worker_t     *worker = uctx;
+
+       /*
+        *      Only real packets are in the dedup tree.  And even
+        *      then, only some of the time.
+        */
+       if (request->async->listen->track_duplicates) (void) rbtree_delete_by_data(worker->dedup, request);
+
+       /*
+        *      If we're running a real request, then the final
+        *      indentation MUST be zero.  Otherwise we skipped
+        *      something!
+        *
+        *      Also check that the request is NOT marked as
+        *      "yielded", but is in fact done.
+        *
+        *      @todo - check that the stack is at frame 0, otherwise
+        *      more things have gone wrong.
+        */
+       fr_assert_msg(request->parent || (request->log.unlang_indent == 0),
+                     "Request %s bad log indentation - expected 0 got %u", request->name, request->log.unlang_indent);
+       fr_assert_msg(!unlang_interpret_is_resumable(request),
+                     "Request %s is marked as yielded at end of processing", request->name);
+
+       RDEBUG("Done request");
+
+       worker_send_reply(worker, request, 0, fr_time());
+
+       DEBUG3("Freeing request %s", request->name);
+       talloc_free(request);
+}
+
+/** Request is now runnable
+ *
+ */
+static void _worker_request_runnable(request_t *request, void *uctx)
+{
+       fr_worker_t     *worker = uctx;
+
+       fr_heap_insert(worker->runnable, request);
+}
+
+/** Interpreter yielded request
+ *
+ */
+static void _worker_request_yield(request_t *request, UNUSED void *uctx)
+{
+       fr_time_tracking_yield(&request->async->tracking, fr_time());
+}
+
+/** Interpreter is starting to work on request again
+ *
+ */
+static void _worker_request_resume(request_t *request, UNUSED void *uctx)
+{
+       fr_time_tracking_resume(&request->async->tracking, fr_time());
+}
+
+/** Run a request
+ *
+ *  Until it either yields, or is done.
+ *
+ *  This function is also responsible for sending replies, and
+ *  cleaning up the request.
+ *
+ * @param[in] worker the worker
+ * @param[in] start the current time
+ */
+static inline CC_HINT(always_inline) void worker_run_request(fr_worker_t *worker, fr_time_t start)
+{
+       rlm_rcode_t     final;
+       request_t       *request;
+       fr_time_t       now;
+
+       WORKER_VERIFY;
+
+       now = start;
+
+       /*
+        *      Busy-loop running requests for 0.1ms.  another
+        *      request.  This change means that the worker checks the
+        *      event loop fewer times per second, instead of after
+        *      every request.
+        */
+       while (((now - start) < (NSEC / 100000)) &&
+              ((request = fr_heap_pop(worker->runnable)) != NULL)) {
+
+               REQUEST_VERIFY(request);
+               fr_assert(request->runnable_id < 0);
+
+               fr_assert(request->parent == NULL);
+               fr_assert(request->async->process != NULL);
+
+               RDEBUG("Running request");
+
+               /*
+                *      For real requests, if the channel is gone,
+                *      just stop the request and free it.
+                */
+               if (request->async->channel && !fr_channel_active(request->async->channel)) {
+                       worker_stop_request(worker, request, now);
+                       talloc_free(request);
+                       return;
+               }
+
+               /*
+                *      Old syle... should be removed
+                */
+               if (request->async->process) {
+                       request->async->process(&final,
+                                               &(module_ctx_t){
+                                                       .instance = request->async->process_inst
+                                               }, request);
+
+               /*
+                *      New style... just call the interpreter directly
+                */
+               } else {
+                       (void)unlang_interpret(request);
+               }
+
+               now = fr_time();
+       }
+}
 
 /** Create a worker
  *
@@ -1144,6 +1217,21 @@ nomem:
                goto fail;
        }
 
+       worker->intp = unlang_interpret_init(worker, el,
+                                            &(unlang_request_func_t){
+                                                       .init_internal = _worker_request_internal_init,
+                                                       .done_internal = _worker_request_internal_done,
+                                                       .done_external = _worker_request_external_done,
+                                                       .mark_runnable = _worker_request_runnable,
+                                                       .yield = _worker_request_yield,
+                                                       .resume = _worker_request_resume
+                                            },
+                                            worker);
+       if (!worker->intp){
+               fr_strerror_const("Failed initialising interpreter");
+               goto fail;
+       }
+
        thread_local_worker = worker;
 
        return worker;
index f548211bfa445346243bc61102d19fec72a6d27e..c352c0835ab9c5c55c91700d414682a1fff1a29b 100644 (file)
@@ -1158,7 +1158,13 @@ static int _module_thread_inst_array_free(module_thread_instance_t **array)
                ti = talloc_get_type_abort(array[i], module_thread_instance_t);
 
                DEBUG4("Worker cleaning up %s thread instance data (%p/%p)", ti->module->name, ti, ti->data);
-               if (ti->module->thread_detach) (void) ti->module->thread_detach(ti->el, ti->data);
+
+               /*
+                *      Check for ti->module is a hack
+                *      and should be removed along with
+                *      starting the instance number at 0
+                */
+               if (ti->module && ti->module->thread_detach) (void) ti->module->thread_detach(ti->el, ti->data);
 
                talloc_free(ti);
        }
@@ -1190,6 +1196,15 @@ int modules_thread_instantiate(TALLOC_CTX *ctx, fr_event_list_t *el)
                talloc_set_destructor(module_thread_inst_array, _module_thread_inst_array_free);
        }
 
+       /*
+        *      Index 0 is populated with a catchall entry
+        *      FIXME - This is only required so we can
+        *      fake out module instance data.  As soon
+        *      as we have multiple module lists this can
+        *      be removed.
+        */
+       MEM(module_thread_inst_array[0] = talloc_zero(module_thread_inst_array, module_thread_instance_t));
+
        for (instance = rbtree_iter_init_inorder(&iter, module_instance_name_tree);
             instance;
             instance = rbtree_iter_next_inorder(&iter)) {
index 21f70467819f4cc30a041cbc3cc303322a1d540a..b58e0ee44ddc90dee7e5d16f20e3279443d3a6ce 100644 (file)
@@ -507,6 +507,8 @@ int virtual_server_entry_point_set(request_t *request)
        if (!server) return -1;
 
        mi->name = server->process_module->name;
+       mi->module = (module_t *)server->process_module;
+       mi->number = 0; /* Hacky hack hack */
 
        if (unlikely(track && track->dynamic && server->dynamic_client_module)) {
                process = (fr_process_module_t const *) server->dynamic_client_module->module->common;
@@ -1415,7 +1417,11 @@ unlang_action_t process_authenticate(rlm_rcode_t *p_result, int auth_type, reque
        request->module = NULL;
        request->component = "authenticate";
 
-       rcode = unlang_interpret_section(request, cs, RLM_MODULE_REJECT);
+
+       if (unlang_interpret_push_section(request, cs, RLM_MODULE_REJECT, UNLANG_TOP_FRAME) < 0) {
+               RETURN_MODULE_FAIL;
+       }
+       rcode = unlang_interpret(request);
 
        request->component = component;
        request->module = module;
index f4a29662d951817cc7a2813dfe749315d1b2713e..e8e25232f44d60e441c7987f297aef8cd7910b68 100644 (file)
@@ -103,8 +103,10 @@ unlang_action_t fr_tls_cache_process(rlm_rcode_t *p_result, request_t *request,
         *      Run it through the appropriate virtual server.
         *      FIXME - This is still blocking.
         */
-       rcode = unlang_interpret_synchronous(request, action, RLM_MODULE_NOOP, true);
-
+       if (unlang_interpret_push_section(request, action, RLM_MODULE_NOOP, UNLANG_TOP_FRAME) < 0) {
+               RETURN_MODULE_FAIL;
+       }
+       rcode = unlang_interpret_synchronous(request);
        /*
         *      Restore the original status of the request.
         */
index 4ee493350892c5d8e65b5909efaf5317fe663b70..b4abc0a4d420ef727ff23c37ba00eef6ea1a8e7e 100644 (file)
@@ -9,6 +9,7 @@ SOURCES :=      base.c \
                function.c \
                group.c \
                interpret.c \
+               interpret_synchronous.c \
                io.c \
                load_balance.c \
                map.c \
index b9ff27b9215c21d12bd4417964998b7252711d55..30b104249e2d6b2a4cec29ee0b8c56af3f323aa5 100644 (file)
@@ -50,6 +50,8 @@ static unlang_action_t unlang_call_process(rlm_rcode_t *p_result, request_t *req
        ua = state->process(&rcode, &(module_ctx_t){ .instance = state->instance }, request);
        switch (ua) {
        case UNLANG_ACTION_YIELD:
+               break;
+
        case UNLANG_ACTION_STOP_PROCESSING:
        case UNLANG_ACTION_PUSHED_CHILD:        /* Wants to do more processing */
                return ua;
@@ -208,8 +210,8 @@ int unlang_call_push(request_t *request, CONF_SECTION *server_cs,
                .prev_request_state = request->request_state,
                .prev_server_cs = request->server_cs
        };
-       frame->process = unlang_call_process;   /* Skip the initialisation */
-       talloc_steal(frame, c);                 /* Bind our temporary unlang_call_t to the frame */
+       frame->process = unlang_call_process;           /* Skip the initialisation */
+       talloc_steal(frame->state, c);                  /* Bind our temporary unlang_call_t to the frame */
 
        return 0;
 }
index bdc39eaed904be5c692eb6c30eec6157a7e1e919..410e9dbecdbd5cdab92ef7935f4231aa9895109a 100644 (file)
@@ -18,7 +18,7 @@
  * $Id$
  *
  * @file unlang/interpret.c
- * @brief Execute compiled unlang structures using an iterative interpreter.
+ * @brief Execute compiled unlang structures using an iterative interpret.
  *
  * @copyright 2006-2016 The FreeRADIUS server project
  */
@@ -29,9 +29,10 @@ RCSID("$Id$")
 #include <freeradius-devel/server/cond.h>
 #include <freeradius-devel/unlang/xlat.h>
 
-#include "unlang_priv.h"
-#include "parallel_priv.h"
+#include "interpret_priv.h"
 #include "module_priv.h"
+#include "parallel_priv.h"
+#include "unlang_priv.h"
 
 static fr_table_num_ordered_t const unlang_action_table[] = {
        { L("unwind"),          UNLANG_ACTION_UNWIND },
@@ -107,7 +108,7 @@ static void stack_dump(request_t *request)
 #define DUMP_STACK
 #endif
 
-/** Different operations the interpreter can execute
+/** Different operations the interpret can execute
  */
 unlang_op_t unlang_ops[UNLANG_TYPE_MAX];
 
@@ -158,8 +159,8 @@ static inline void frame_state_init(unlang_stack_t *stack, unlang_stack_frame_t
  * @param[in] default_rcode    The default result.
  * @param[in] do_next_sibling  Whether to only execute the first node in the #unlang_t program
  *                             or to execute subsequent nodes.
- * @param[in] top_frame                Return out of the unlang interpreter when popping this frame.
- *                             Hands execution back to whatever called the interpreter.
+ * @param[in] top_frame                Return out of the unlang interpret when popping this frame.
+ *                             Hands execution back to whatever called the interpret.
  * @return
  *     - 0 on success.
  *     - -1 on call stack too deep.
@@ -182,7 +183,7 @@ int unlang_interpret_push(request_t *request, unlang_t const *instruction,
        /*
         *      This is not a cancellation point.
         *
-        *      If we cancel here bad things happen inside the interpreter.
+        *      If we cancel here bad things happen inside the interpret.
         */
        if (stack->depth >= (UNLANG_STACK_MAX - 1)) {
                RERROR("Call stack is too deep");
@@ -228,8 +229,9 @@ int unlang_interpret_push(request_t *request, unlang_t const *instruction,
  *     - UNLANG_FRAME_ACTION_NEXT      evaluate more instructions.
  *     - UNLANG_FRAME_ACTION_POP       the final result has been calculated for this frame.
  */
-static inline unlang_frame_action_t result_calculate(request_t *request, unlang_stack_frame_t *frame,
-                                                    rlm_rcode_t *result, int *priority)
+static inline CC_HINT(always_inline)
+unlang_frame_action_t result_calculate(request_t *request, unlang_stack_frame_t *frame,
+                                      rlm_rcode_t *result, int *priority)
 {
        unlang_t const  *instruction = frame->instruction;
        unlang_stack_t  *stack = request->stack;
@@ -346,7 +348,7 @@ static inline unlang_frame_action_t result_calculate(request_t *request, unlang_
 /** Cleanup any lingering frame state
  *
  */
-static inline void frame_cleanup(unlang_stack_frame_t *frame)
+static inline CC_HINT(always_inline) void frame_cleanup(unlang_stack_frame_t *frame)
 {
        /*
         *      Don't clear top_frame flag, bad things happen...
@@ -364,7 +366,7 @@ static inline void frame_cleanup(unlang_stack_frame_t *frame)
 /** Advance to the next sibling instruction
  *
  */
-static inline void frame_next(unlang_stack_t *stack, unlang_stack_frame_t *frame)
+static inline CC_HINT(always_inline) void frame_next(unlang_stack_t *stack, unlang_stack_frame_t *frame)
 {
        frame_cleanup(frame);
        frame->instruction = frame->next;
@@ -380,7 +382,7 @@ static inline void frame_next(unlang_stack_t *stack, unlang_stack_frame_t *frame
  *
  * @param[in] stack    frame to pop.
  */
-static inline void frame_pop(unlang_stack_t *stack)
+static inline CC_HINT(always_inline) void frame_pop(unlang_stack_t *stack)
 {
        unlang_stack_frame_t *frame;
 
@@ -506,7 +508,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
 
                /*
                 *      Yield control back to the scheduler, or whatever
-                *      called the interpreter.
+                *      called the interpret.
                 */
                case UNLANG_ACTION_YIELD:
                        *result = RLM_MODULE_YIELD;     /* Fixup rcode */
@@ -591,7 +593,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                                RDEBUG2("}");
                        }
                        break;
-               } /* switch over return code from the interpreter function */
+               } /* switch over return code from the interpret function */
 
                frame_next(stack, frame);
        }
@@ -604,17 +606,22 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
        return UNLANG_FRAME_ACTION_POP;
 }
 
-/*
- *     Interpret the various types of blocks.
+/** Run the interpreter for a current request
+ *
+ * @param[in] request          to run.  If this is an internal request
+ *                             the request may be freed by the interpreter.
+ * @return The final request rcode.
  */
-rlm_rcode_t unlang_interpret(request_t *request)
+CC_HINT(hot) rlm_rcode_t unlang_interpret(request_t *request)
 {
        unlang_frame_action_t   fa = UNLANG_FRAME_ACTION_NEXT;
+       rlm_rcode_t             rcode;
 
        /*
         *      We don't have a return code yet.
         */
        unlang_stack_t          *stack = request->stack;
+       unlang_interpret_t      *intp = stack->intp;
        unlang_stack_frame_t    *frame = &stack->frame[stack->depth];   /* Quiet static analysis */
 
        stack->priority = -1;   /* Reset */
@@ -625,8 +632,10 @@ rlm_rcode_t unlang_interpret(request_t *request)
 #endif
 
        fr_assert(!unlang_request_is_scheduled(request)); /* if we're running it, it can't be scheduled */
+       fr_assert_msg(intp, "request has no interpreter associated");
 
-       RDEBUG4("** [%i] %s - interpreter entered", stack->depth, __FUNCTION__);
+       RDEBUG4("** [%i] %s - interpret entered", stack->depth, __FUNCTION__);
+       if (is_yielded(frame)) intp->funcs.resume(request, intp->uctx);
 
        for (;;) {
                RDEBUG4("** [%i] %s - frame action %s", stack->depth, __FUNCTION__,
@@ -733,7 +742,8 @@ rlm_rcode_t unlang_interpret(request_t *request)
 
                case UNLANG_FRAME_ACTION_YIELD:
                        fr_assert(stack->result == RLM_MODULE_YIELD);
-                       RDEBUG4("** [%i] %s - interpreter yielding", stack->depth, __FUNCTION__);
+                       RDEBUG4("** [%i] %s - interpret yielding", stack->depth, __FUNCTION__);
+                       intp->funcs.yield(request, intp->uctx);
                        return stack->result;
                }
                break;
@@ -763,7 +773,7 @@ rlm_rcode_t unlang_interpret(request_t *request)
         *      We're at the top frame, return the result from the
         *      stack, and get rid of the top frame.
         */
-       RDEBUG4("** [%i] %s - interpreter exiting, returning %s", stack->depth, __FUNCTION__,
+       RDEBUG4("** [%i] %s - interpret exiting, returning %s", stack->depth, __FUNCTION__,
                fr_table_str_by_value(mod_rcode_table, frame->result, "<invalid>"));
 
        stack->result = frame->result;
@@ -771,7 +781,24 @@ rlm_rcode_t unlang_interpret(request_t *request)
        stack->depth--;
        DUMP_STACK;
 
-       return stack->result;
+       /*
+        *      Record this now as the done functions may free
+        *      the request.
+        */
+       rcode = stack->result;
+
+       /*
+        *      This usually means the request is complete in its
+        *      entirety.
+        */
+       if (stack->depth == 0) {
+               if (request_is_internal(request)) {
+                       intp->funcs.done_internal(request, stack->result, intp->uctx);
+               } else {
+                       intp->funcs.done_external(request, stack->result, intp->uctx);
+               }
+       }
+       return rcode;
 }
 
 static unlang_group_t empty_group = {
@@ -850,162 +877,6 @@ int unlang_interpret_push_instruction(request_t *request, void *instruction, rlm
        return 0;
 }
 
-/** Call a module, iteratively, with a local stack, rather than recursively
- *
- * What did Paul Graham say about Lisp...?
- */
-rlm_rcode_t unlang_interpret_section(request_t *request, CONF_SECTION *subcs, rlm_rcode_t default_rcode)
-{
-       /*
-        *      This pushes a new frame onto the stack, which is the
-        *      start of a new unlang section...
-        */
-       if (unlang_interpret_push_section(request, subcs, default_rcode, UNLANG_TOP_FRAME) < 0)return RLM_MODULE_FAIL;
-
-       return unlang_interpret(request);
-}
-
-/** Execute an unlang section synchronously
- *
- * Create a temporary event loop and swap it out for the one in the request.
- * Execute unlang operations until we receive a non-yield return code then return.
- *
- * @note The use cases for this are very limited.  If you need to use it, chances
- *     are what you're doing could be done better using one of the thread
- *     event loops.
- *
- * @param[in] request  The current request.
- * @param[in] cs       Section with compiled unlang associated with it.
- * @param[in] action   The default return code to use.
- * @param[in] child_el Use a child event list
- * @return One of the RLM_MODULE_* macros.
- */
-rlm_rcode_t unlang_interpret_synchronous(request_t *request, CONF_SECTION *cs, rlm_rcode_t action, bool child_el)
-{
-       fr_event_list_t *old_el, *el = NULL;
-       fr_heap_t       *backlog, *old_backlog;
-       rlm_rcode_t     rcode;
-       char const      *caller;
-       request_t       *sub_request = NULL;
-       bool            wait_for_event;
-       int             iterations = 0;
-
-       old_el = request->el;
-       if (child_el) {
-               /*
-                *      Don't talloc from the request
-                *      as we'll almost certainly leave holes in the memory pool.
-                */
-               el = fr_event_list_alloc(NULL, NULL, NULL);
-               if (!el) {
-                       RPERROR("Failed creating temporary event loop");
-                       fr_assert(0);           /* Cause debug builds to die */
-                       return RLM_MODULE_FAIL;
-               }
-       }
-
-       MEM(backlog = fr_heap_talloc_alloc(request, fr_pointer_cmp, request_t, runnable_id));
-       old_backlog = request->backlog;
-       caller = request->module;
-
-       request->backlog = backlog;
-
-       rcode = unlang_interpret_section(request, cs, action);
-       wait_for_event = (rcode == RLM_MODULE_YIELD);
-
-       while (true) {
-               rlm_rcode_t sub_rcode;
-               int num_events;
-
-               /*
-                *      Wait for a timer / IO event.  If there's a
-                *      failure, all kinds of bad things happen.  Oh
-                *      well.
-                */
-               DEBUG3("Gathering events - %s", wait_for_event ? "will wait" : "Will not wait");
-               num_events = fr_event_corral(request->el, fr_time(), wait_for_event);
-               if (num_events < 0) {
-                       RPERROR("Failed retrieving events");
-                       return RLM_MODULE_FAIL;
-               }
-
-               DEBUG3("%u event(s) pending%s",
-                      num_events == -1 ? 0 : num_events, num_events == -1 ? " - event loop exiting" : "");
-
-               /*
-                *      We were NOT waiting, AND there are no more
-                *      events to run, AND there are no more requests
-                *      to run.  We can exit the loop.
-                */
-               if (!wait_for_event && (num_events == 0) &&
-                   (fr_heap_num_elements(backlog) == 0)) {
-                       break;
-               }
-
-               /*
-                *      This function ends up pushing a
-                *      runnable request into the backlog, OR
-                *      setting new timers.
-                */
-               if (num_events > 0) {
-                       DEBUG4("Servicing event(s)");
-                       fr_event_service(request->el);
-               }
-
-               /*
-                *      If there are no runnable requests, then go
-                *      back to check the timers again.  Note that we
-                *      only wait if there are timer events left to
-                *      service.
-                *
-                *      If there WAS a timer event, but servicing that
-                *      timer event did not result in a runnable
-                *      request, THEN we're guaranteed that there is
-                *      still a timer event left.
-                */
-               sub_request = fr_heap_pop(backlog);
-               if (!sub_request) {
-                       wait_for_event = (num_events > 0);
-                       continue;
-               }
-
-               /*
-                *      Continue interpretation until there's nothing
-                *      in the backlog.  If this request YIELDs, then
-                *      do another loop around.
-                */
-               RDEBUG4(">>> interpreter (iteration %i)", ++iterations);
-               sub_rcode = unlang_interpret(sub_request);
-               RDEBUG4("<<< interpreter (iteration %i) - %s", iterations,
-                       fr_table_str_by_value(mod_rcode_table, sub_rcode, "<INVALID>"));
-               if (sub_rcode == RLM_MODULE_YIELD) {
-                       wait_for_event = true;
-                       continue;
-               }
-
-               /*
-                *      This request is done.  Clean up, and do a
-                *      non-blocking check for more events in the next
-                *      loop.
-                */
-               wait_for_event = false;
-               if (sub_request == request) {
-                       rcode = sub_rcode;
-               } else {
-                       RDEBUG4("Cleaning up subrequest");
-                       talloc_free(sub_request);
-               }
-       }
-
-       talloc_free(el);
-       talloc_free(backlog);
-       request->el = old_el;
-       request->backlog = old_backlog;
-       request->module = caller;
-
-       return rcode;
-}
-
 /** Allocate a new unlang stack
  *
  * @param[in] ctx      to allocate stack in.
@@ -1051,7 +922,7 @@ void *unlang_interpret_stack_alloc(TALLOC_CTX *ctx)
  * @param[in] action           to signal.
  * @param[in] limit            the frame at which to stop signaling.
  */
-static void frame_signal(request_t *request, fr_state_signal_t action, int limit)
+static inline CC_HINT(always_inline) void frame_signal(request_t *request, fr_state_signal_t action, int limit)
 {
        unlang_stack_frame_t    *frame;
        unlang_stack_t          *stack = request->stack;
@@ -1113,7 +984,7 @@ void unlang_interpret_signal(request_t *request, fr_state_signal_t action)
        }
 
        /*
-        *      Requests that haven't been run through the interpreter
+        *      Requests that haven't been run through the interpret
         *      yet should have a stack depth of zero, so we don't
         *      need to do anything.
         */
@@ -1140,12 +1011,22 @@ int unlang_interpret_stack_depth(request_t *request)
  */
 rlm_rcode_t unlang_interpret_stack_result(request_t *request)
 {
-
        unlang_stack_t          *stack = request->stack;
 
        return stack->result;
 }
 
+/** Return whether a request is currently scheduled
+ *
+ */
+bool unlang_request_is_scheduled(request_t const *request)
+{
+       unlang_stack_t          *stack = request->stack;
+       unlang_interpret_t      *intp = stack->intp;
+
+       return intp->funcs.scheduled(request, intp->uctx);
+}
+
 /** Check if a request as resumable.
  *
  * @param[in] request          The current request.
@@ -1174,8 +1055,11 @@ bool unlang_interpret_is_resumable(request_t *request)
 void unlang_interpret_mark_runnable(request_t *request)
 {
        unlang_stack_t                  *stack = request->stack;
+       unlang_interpret_t              *intp = stack->intp;
        unlang_stack_frame_t            *frame = &stack->frame[stack->depth];
 
+       bool                            scheduled = unlang_request_is_scheduled(request);
+
        /*
         *      The request hasn't yielded, OR it's already been
         *      marked as runnable.  Don't do anything.
@@ -1194,23 +1078,18 @@ void unlang_interpret_mark_runnable(request_t *request)
         *      Multiple child request may also mark a parent request
         *      runnable, before the parent request starts running.
         */
-       if (!is_yielded(frame) || unlang_request_is_scheduled(request)) {
+       if (!is_yielded(frame) || scheduled) {
                RDEBUG3("Not marking resumable due to%s%s",
                        !is_yielded(frame) ?
-                       " it not being yielded " : "", unlang_request_is_scheduled(request) ? " it already being scheduled" : "");
+                       " it not being yielded " : "", scheduled ? " it already being scheduled" : "");
                return;
        }
 
-       if (!unlang_request_is_scheduled(request)) {
-               RDEBUG3("Marked as resumable");
-       }
+       RDEBUG3("Marked as resumable");
 
-       fr_assert(request->backlog != NULL);
-
-       fr_heap_insert(request->backlog, request);
+       intp->funcs.mark_runnable(request, intp->uctx);
 }
 
-
 /** Get a talloc_ctx which is valid only for this frame
  *
  * @param[in] request          The current request.
@@ -1235,7 +1114,7 @@ TALLOC_CTX *unlang_interpret_frame_talloc_ctx(request_t *request)
         *      Ensure that the memory is always cleaned up when the
         *      request exits.  And make sure that this function is safe to call from anywhere.
         */
-       return (TALLOC_CTX *) request;
+       return (TALLOC_CTX *)request;
 }
 
 static xlat_arg_parser_t const unlang_interpret_xlat_args[] = {
@@ -1358,6 +1237,52 @@ finish:
        return XLAT_ACTION_DONE;
 }
 
+/** Initialize a unlang compiler / interpret.
+ *
+ * @param[in] ctx      to bind lifetime of the interpret to.
+ *                     Shouldn't be any free order issues here as
+ *                     the interpret itself has no state.
+ *                     But event loop should be stopped before
+ *                     freeing the interpret.
+ * @param[in] el       for any timer or I/O events.
+ * @param[in] funcs    Callbacks to used to communicate request
+ *                     state to our owner.
+ * @param[in] uctx     Data to pass to callbacks.
+ */
+unlang_interpret_t *unlang_interpret_init(TALLOC_CTX *ctx,
+                                         fr_event_list_t *el, unlang_request_func_t *funcs, void *uctx)
+{
+       unlang_interpret_t *intp;
+
+       MEM(intp = talloc(ctx, unlang_interpret_t));
+       *intp = (unlang_interpret_t){
+               .el = el,
+               .funcs = *funcs,
+               .uctx = uctx
+       };
+
+       return intp;
+}
+
+/** Set a specific interpreter for a request
+ *
+ */
+void unlang_interpret_set(request_t *request, unlang_interpret_t *intp)
+{
+       unlang_stack_t  *stack = request->stack;
+       stack->intp = intp;
+}
+
+/** Get the interpreter set for a request
+ *
+ */
+unlang_interpret_t *unlang_interpret_get(request_t *request)
+{
+       unlang_stack_t  *stack = request->stack;
+
+       return stack->intp;
+}
+
 void unlang_interpret_init_global(void)
 {
        xlat_t  *xlat;
index bfa29947f8c375a4f61a81c76cdd891bc74ea495..c27f90c1ec34e66843644f84836f8a60d7e2b032 100644 (file)
@@ -28,6 +28,7 @@
 extern "C" {
 #endif
 
+#include <freeradius-devel/server/request.h>
 #include <freeradius-devel/unlang/action.h>
 
 #define UNLANG_TOP_FRAME (true)
@@ -36,43 +37,107 @@ extern "C" {
 #define UNLANG_STACK_MAX (64)          //!< The maximum depth of the stack.
 #define UNLANG_FRAME_PRE_ALLOC (128)   //!< How much memory we pre-alloc for each frame.
 
-/** Return whether a request is currently scheduled
+/** Interpreter handle
  *
  */
-static inline bool unlang_request_is_scheduled(request_t const *request)
-{
-       return (request->runnable_id >= 0);
-}
+typedef struct unlang_interpret_s unlang_interpret_t;
+
+/** Signal the owner of the interpreter that this request should be initialised and executed
+ *
+ * This is called once per request, when it's about to start executing.
+ */
+typedef void (*unlang_request_init_t)(request_t *request, void *uctx);
+
+/** Signal the owner of the interpreter that this request completed processing
+ *
+ * This is called once per request, when the interpret is about to stop processing it.
+ */
+typedef void (*unlang_request_done_t)(request_t *request, rlm_rcode_t rcode, void *uctx);
+
+/** Signal the owner of the interpreter that a request has yielded
+ *
+ * This is called whenever a request has given control back to the interpeter.
+ */
+typedef void (*unlang_request_yield_t)(request_t *request, void *uctx);
+
+/** Signal the owner of the interpeter that a request is ready to be resumed
+ *
+ * This is called any time a yielded request has resumed.
+ */
+typedef void (*unlang_request_resume_t)(request_t *request, void *uctx);
+
+/** Signal the owner of the interpeter that a request is now runnable
+ *
+ * This is called any time a yielded request has been marked runnable.
+ */
+typedef void (*unlang_request_runnable_t)(request_t *request, void *uctx);
+
+/** Signal the owner of the interpeter that a request is now runnable
+ *
+ * This is called any time a yielded request has been marked runnable.
+ */
+typedef bool (*unlang_request_scheduled_t)(request_t const *request, void *uctx);
+
+/** External functions provided by the owner of the interpret
+ *
+ * These functions allow the event loop to signal the caller when a given
+ * request is ready to run for the first time, and when it should be resumed
+ * and passed back to #unlang_interpret to continue execution.
+ *
+ * This is the cleanest way to separate the interpret and the code that's
+ * managing requests.
+ *
+ * Test harnesses (for example) need to perform far less initialisation and
+ * request management than FeeRADIUS worker threads.
+ */
+typedef struct {
+       unlang_request_init_t           init_internal;  //!< Function called to initialise a request.
+       unlang_request_done_t           done_internal;  //!< Function called when an internal request completes.
+       unlang_request_done_t           done_external;  //!< Function called when an internal request completes.
+       unlang_request_yield_t          yield;          //!< Function called when a request yields.
+       unlang_request_resume_t         resume;         //!< Function called when a request is resumed.
+       unlang_request_runnable_t       mark_runnable;  //!< Function called when a request needs to be
+                                                       ///< added back to the runnable queue.
+       unlang_request_scheduled_t      scheduled;      //!< Function to check if a request is already
+                                                       ///< scheduled.
+} unlang_request_func_t;
+
+int                    unlang_interpret_push_section(request_t *request, CONF_SECTION *cs,
+                                                     rlm_rcode_t default_action, bool top_frame)
+                                                     CC_HINT(warn_unused_result);
+
+int                    unlang_interpret_push_instruction(request_t *request, void *instruction,
+                                                         rlm_rcode_t default_rcode, bool top_frame)
+                                                         CC_HINT(warn_unused_result);
+
+unlang_interpret_t     *unlang_interpret_init(TALLOC_CTX *ctx,
+                                              fr_event_list_t *el, unlang_request_func_t *func, void *uctx);
 
-int            unlang_interpret_push_section(request_t *request, CONF_SECTION *cs,
-                                             rlm_rcode_t default_action, bool top_frame)
-                                             CC_HINT(warn_unused_result);
+void                   unlang_interpret_set(request_t *request, unlang_interpret_t *intp);
 
-int            unlang_interpret_push_instruction(request_t *request, void *instruction,
-                                                 rlm_rcode_t default_rcode, bool top_frame)
-                                                 CC_HINT(warn_unused_result);
+unlang_interpret_t     *unlang_interpret_get(request_t *request);
 
-rlm_rcode_t    unlang_interpret(request_t *request);
+rlm_rcode_t            unlang_interpret(request_t *request) CC_HINT(hot);
 
-rlm_rcode_t    unlang_interpret_section(request_t *request, CONF_SECTION *cs, rlm_rcode_t default_action);
+rlm_rcode_t            unlang_interpret_synchronous(request_t *request);
 
-rlm_rcode_t    unlang_interpret_synchronous(request_t *request, CONF_SECTION *cs, rlm_rcode_t action, bool child_el);
+void                   *unlang_interpret_stack_alloc(TALLOC_CTX *ctx);
 
-void           *unlang_interpret_stack_alloc(TALLOC_CTX *ctx);
+bool                   unlang_request_is_scheduled(request_t const *request);
 
-void           unlang_interpret_mark_runnable(request_t *request);
+void                   unlang_interpret_mark_runnable(request_t *request);
 
-bool           unlang_interpret_is_resumable(request_t *request);
+bool                   unlang_interpret_is_resumable(request_t *request);
 
-void           unlang_interpret_signal(request_t *request, fr_state_signal_t action);
+void                   unlang_interpret_signal(request_t *request, fr_state_signal_t action);
 
-int            unlang_interpret_stack_depth(request_t *request);
+int                    unlang_interpret_stack_depth(request_t *request);
 
-rlm_rcode_t    unlang_interpret_stack_result(request_t *request);
+rlm_rcode_t            unlang_interpret_stack_result(request_t *request);
 
-TALLOC_CTX     *unlang_interpret_frame_talloc_ctx(request_t *request);
+TALLOC_CTX             *unlang_interpret_frame_talloc_ctx(request_t *request);
 
-void           unlang_interpret_init_global(void);
+void                   unlang_interpret_init_global(void);
 #ifdef __cplusplus
 }
 #endif
diff --git a/src/lib/unlang/interpret_priv.h b/src/lib/unlang/interpret_priv.h
new file mode 100644 (file)
index 0000000..0ba456b
--- /dev/null
@@ -0,0 +1,49 @@
+#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/interpret.h
+ * @brief Declarations for the unlang interpreter.
+ *
+ * @copyright 2019 The FreeRADIUS server project
+ */
+
+#include <freeradius-devel/util/event.h>
+#include <freeradius-devel/unlang/interpret.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+struct unlang_interpret_s {
+       fr_event_list_t         *el;
+       unlang_request_func_t   funcs;
+       void                    *uctx;
+};
+
+static inline void unlang_interpret_child_init(request_t *request)
+{
+       unlang_interpret_t *intp = unlang_interpret_get(request);
+
+       intp->funcs.init_internal(request, intp->uctx);
+}
+
+#ifdef __cplusplus
+}
+#endif
diff --git a/src/lib/unlang/interpret_synchronous.c b/src/lib/unlang/interpret_synchronous.c
new file mode 100644 (file)
index 0000000..f940522
--- /dev/null
@@ -0,0 +1,246 @@
+/*
+ *   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/interpret_synchronous.c
+ * @brief Synchronous interpreter
+ *
+ * @copyright 2021 The FreeRADIUS server project
+ */
+
+#include "interpret_priv.h"
+#include <freeradius-devel/server/module.h>
+
+typedef struct {
+       fr_heap_t               *runnable;
+       fr_event_list_t         *el;
+       int                     yielded;
+       unlang_interpret_t      *intp;
+} unlang_interpret_synchronous_t;
+
+
+/** Internal request (i.e. one generated by the interpreter) is now complete
+ *
+ */
+static void _request_internal_init(request_t *request, void *uctx)
+{
+       unlang_interpret_synchronous_t  *intps = uctx;
+
+       RDEBUG3("Initialising synchronous request");
+       unlang_interpret_set(request, intps->intp);
+       fr_heap_insert(intps->runnable, request);
+}
+
+/** Internal request (i.e. one generated by the interpreter) is now complete
+ *
+ */
+static void _request_internal_done(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
+{
+       RDEBUG3("Freeing synchronous request");
+       talloc_free(request);
+}
+
+/** External request is now complete
+ *
+ */
+static void _request_external_done(request_t *request, UNUSED rlm_rcode_t rcode, UNUSED void *uctx)
+{
+       /*
+        *      If we're running a real request, then the final
+        *      indentation MUST be zero.  Otherwise we skipped
+        *      something!
+        *
+        *      Also check that the request is NOT marked as
+        *      "yielded", but is in fact done.
+        *
+        *      @todo - check that the stack is at frame 0, otherwise
+        *      more things have gone wrong.
+        */
+       fr_assert_msg(request->parent || (request->log.unlang_indent == 0),
+                     "Request %s bad log indentation - expected 0 got %u", request->name, request->log.unlang_indent);
+       fr_assert_msg(!unlang_interpret_is_resumable(request),
+                     "Request %s is marked as yielded at end of processing", request->name);
+}
+
+/** Request is now runnable
+ *
+ */
+static void _request_runnable(request_t *request, void *uctx)
+{
+       unlang_interpret_synchronous_t  *intps = uctx;
+
+       fr_heap_insert(intps->runnable, request);
+}
+
+/** Interpreter yielded request
+ *
+ */
+static void _request_yield(request_t *request, UNUSED void *uctx)
+{
+       RDEBUG3("synchronous request yielded");
+}
+
+/** Interpreter is starting to work on request again
+ *
+ */
+static void _request_resume(request_t *request, UNUSED void *uctx)
+{
+       RDEBUG3("synchronous request resumed");
+}
+
+static bool _request_scheduled(request_t const *request, UNUSED void *uctx)
+{
+       return fr_heap_entry_inserted(request->runnable_id);
+}
+
+/** Allocate a new temporary interpreter
+ *
+ */
+static unlang_interpret_synchronous_t *unlang_interpret_alloc(TALLOC_CTX *ctx)
+{
+       unlang_interpret_synchronous_t *intps;
+
+       MEM(intps = talloc_zero(ctx, unlang_interpret_synchronous_t));
+       MEM(intps->runnable = fr_heap_talloc_alloc(intps, fr_pointer_cmp, request_t, runnable_id));
+       MEM(intps->el = fr_event_list_alloc(intps, NULL, NULL));
+       MEM(intps->intp = unlang_interpret_init(intps, intps->el,
+                                               &(unlang_request_func_t){
+                                                       .init_internal = _request_internal_init,
+                                                       .done_internal = _request_internal_done,
+                                                       .done_external = _request_external_done,
+                                                       .mark_runnable = _request_runnable,
+                                                       .yield = _request_yield,
+                                                       .resume = _request_resume,
+                                                       .scheduled = _request_scheduled
+                                               },
+                                               intps));
+
+       return intps;
+}
+
+/** Execute an unlang section synchronously
+ *
+ * Create a temporary event loop and swap it out for the one in the request.
+ * Execute unlang operations until we receive a non-yield return code then return.
+ *
+ * @note The use cases for this are very limited.  If you need to use it, chances
+ *     are what you're doing could be done better using one of the thread
+ *     event loops.
+ *
+ * @param[in] request  The current request.
+ * @return One of the RLM_MODULE_* macros.
+ */
+rlm_rcode_t unlang_interpret_synchronous(request_t *request)
+{
+       fr_event_list_t                 *old_el;
+       fr_heap_t                       *old_backlog;
+       unlang_interpret_t              *old_intp;
+       char const                      *caller;
+
+       unlang_interpret_synchronous_t  *intps;
+
+       rlm_rcode_t                     rcode;
+
+       request_t                       *sub_request = NULL;
+       bool                            dont_wait_for_event;
+       int                             iterations = 0;
+
+       old_el = request->el;
+       old_backlog = request->backlog;
+       old_intp = unlang_interpret_get(request);
+       caller = request->module;
+
+       intps = unlang_interpret_alloc(request);
+       request->backlog = intps->runnable;
+       request->el = intps->el;
+       unlang_interpret_set(request, intps->intp);
+
+       rcode = unlang_interpret(request);
+
+       while (!(dont_wait_for_event = (fr_heap_num_elements(intps->runnable) == 0)) ||
+              (intps->yielded > 0)) {
+               rlm_rcode_t     sub_rcode;
+               int             num_events;
+
+               DEBUG3("%u runnable, %u yielded", fr_heap_num_elements(intps->runnable), intps->yielded);
+               /*
+                *      Wait for a timer / IO event.  If there's a
+                *      failure, all kinds of bad things happen.  Oh
+                *      well.
+                */
+               DEBUG3("Gathering events - %s", dont_wait_for_event ? "Will not wait" : "will wait");
+               num_events = fr_event_corral(request->el, fr_time(), !dont_wait_for_event);
+               if (num_events < 0) break;
+
+               DEBUG3("%u event(s) pending%s",
+                      num_events == -1 ? 0 : num_events, num_events == -1 ? " - event loop exiting" : "");
+
+               /*
+                *      This function ends up pushing a
+                *      runnable request into the backlog, OR
+                *      setting new timers.
+                */
+               if (num_events > 0) {
+                       DEBUG4("Servicing event(s)");
+                       fr_event_service(request->el);
+               }
+
+               /*
+                *      If there are no runnable requests, then go
+                *      back to check the timers again.  Note that we
+                *      only wait if there are timer events left to
+                *      service.
+                *
+                *      If there WAS a timer event, but servicing that
+                *      timer event did not result in a runnable
+                *      request, THEN we're guaranteed that there is
+                *      still a timer event left.
+                */
+               sub_request = fr_heap_pop(intps->runnable);
+               if (!sub_request) continue;
+
+               if (unlang_interpret_is_resumable(sub_request)) intps->yielded--;
+
+               /*
+                *      Continue interpretation until there's nothing
+                *      in the backlog.  If this request YIELDs, then
+                *      do another loop around.
+                */
+               RDEBUG4(">>> interpreter (iteration %i)", ++iterations);
+               sub_rcode = unlang_interpret(sub_request);
+               RDEBUG4("<<< interpreter (iteration %i) - %s", iterations,
+                       fr_table_str_by_value(rcode_table, sub_rcode, "<INVALID>"));
+
+               if (unlang_interpret_is_resumable(sub_request)) intps->yielded++;
+
+               if (sub_request == request) {
+                       rcode = sub_rcode;
+               } else {
+                       RDEBUG4("Cleaning up subrequest");
+                       talloc_free(sub_request);
+               }
+       }
+
+       talloc_free(intps);
+       unlang_interpret_set(request, old_intp);
+       request->el = old_el;
+       request->backlog = old_backlog;
+       request->module = caller;
+
+       return rcode;
+}
index 4f710aed8141422dcf9778718a76b6eed4a8922e..e7ffcc598b7fe981d94b656264baf25ca214cff3 100644 (file)
@@ -67,7 +67,7 @@ unlang_action_t unlang_io_process_interpret(rlm_rcode_t *p_result, UNUSED module
  */
 request_t *unlang_io_subrequest_alloc(request_t *parent, fr_dict_t const *namespace, bool detachable)
 {
-       request_t                       *child;
+       request_t               *child;
 
        child = request_alloc(detachable ? NULL : parent,
                              (&(request_init_args_t){
@@ -78,9 +78,13 @@ request_t *unlang_io_subrequest_alloc(request_t *parent, fr_dict_t const *namesp
        if (!child) return NULL;
 
        /*
-        *      Push the child, and set it's top frame to be true.
+        *      Child gets its parent's interpreter
         */
+       ((unlang_stack_t *)child->stack)->intp = ((unlang_stack_t *)parent->stack)->intp;
 
+       /*
+        *      Push the child, and set it's top frame to be true.
+        */
        child->log.unlang_indent = parent->log.unlang_indent;
 
        /*
index 658ecfb7c83162e738c88dbffcf24758beb365ed..24666508ab54a22ee87f7c211ae88b1758227661 100644 (file)
@@ -25,6 +25,7 @@
 RCSID("$Id$")
 
 #include "function.h"
+#include "interpret_priv.h"
 #include "module_priv.h"
 #include "parallel_priv.h"
 #include "subrequest_priv.h"
@@ -393,7 +394,7 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
                        state->children[i].name = talloc_bstrdup(state, child->name);
                        state->children[i].request = child;
                        state->children[i].state = CHILD_RUNNABLE;
-                       fr_heap_insert(request->backlog, child);
+                       unlang_interpret_child_init(child);
                }
 
                /*
index f04f112920def4f1d64df0176d003c7a8e13b9da..e77fcb3089c8ab7187194f198bd2edc1862d21bc 100644 (file)
@@ -26,6 +26,7 @@ RCSID("$Id$")
 
 #include <freeradius-devel/server/state.h>
 #include "unlang_priv.h"
+#include "interpret_priv.h"
 #include "subrequest_priv.h"
 
 /** Parameters for initialising the subrequest
@@ -397,10 +398,7 @@ int unlang_detached_child_init(request_t *request)
         *      Mark the child as runnable.
         */
        fr_assert(request->parent == NULL);
-       if (fr_heap_insert(request->backlog, request) < 0) {
-               RPERROR("Failed inserting ourself into the backlog.");
-               return -1;
-       }
+       unlang_interpret_child_init(request);
 
        return 0;
 }
index 6c20356d8ab2a4187e9e7423c0c1d56aa8eaecaf..4a551a1ad5d898a778da6ef7583cdf4e45fef1cb 100644 (file)
@@ -250,6 +250,8 @@ struct unlang_stack_frame_s {
  *
  */
 typedef struct {
+       unlang_interpret_t      *intp;                          //!< Interpreter that the request is currently
+                                                               ///< associated with.
        int                     priority;                       //!< Current priority.
        rlm_rcode_t             result;                         //!< The current stack rcode.
        int                     depth;                          //!< Current depth we're executing at.
index 789e250d97de15aa21df7005fa0a937c4ba38abd..035642ccff7c9c15bd848fecd8a303ac953c06ba 100644 (file)
@@ -1369,7 +1369,11 @@ static int bfd_process(bfd_state_t *session, bfd_packet_t *bfd)
                request->module = NULL;
 
                DEBUG2("server %s {", cf_section_name2(request->server_cs));
-               unlang_interpret_section(request, session->unlang, RLM_MODULE_NOTFOUND);
+               if (unlang_interpret_push_section(request, session->unlang, RLM_MODULE_NOTFOUND, UNLANG_TOP_FRAME) < 0) {
+                       talloc_free(request);
+                       return 0;
+               }
+               unlang_interpret(request);
                DEBUG("}");
 
                /*
index ca7006ddabbc570a8b494136c9d788cd9980ce3f..9c0809301d747550641ab80cb53e9601117f2974 100644 (file)
@@ -835,7 +835,11 @@ static int proto_ldap_cookie_load(TALLOC_CTX *ctx, uint8_t **cookie, rad_listen_
 
        *cookie = NULL;
 
-       rcode = unlang_interpret_synchronous(request, unlang, RLM_MODULE_NOOP, true);
+       if (unlang_interpret_push_section(request, unlang, RLM_MODULE_NOOP, UNLANG_TOP_FRAME) < 0) {
+               ret = -1;
+               goto finish;
+       }
+       rcode = unlang_interpret_synchronous(request);
        switch (rcode) {
        case RLM_MODULE_OK:
        case RLM_MODULE_UPDATED: