#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>
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)
}
if (count == 1) {
- request_run(el, request);
+ request_run(request);
} else {
int i;
request_t *old = request_clone(request);
for (i = 0; i < count; i++) {
request = request_clone(old);
- request_run(el, request);
+ request_run(request);
talloc_free(request);
}
}
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
request->async->packet_ctx = NULL;
request->async->listen = NULL;
#endif
-
- DEBUG3("Freeing request %s", request->name);
- talloc_free(request);
}
* unique.
*/
worker_send_reply(worker, request, 1, now);
+
+ DEBUG3("Freeing request %s", request->name);
+ talloc_free(request);
}
/*
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 */
/*
request->async->recv_time = cd->request.recv_time;
-
request->async->listen = cd->listen;
request->async->packet_ctx = cd->packet_ctx;
listen = request->async->listen;
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.
*/
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
*
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;
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);
}
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)) {
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;
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;
* 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.
*/
function.c \
group.c \
interpret.c \
+ interpret_synchronous.c \
io.c \
load_balance.c \
map.c \
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;
.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;
}
* $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
*/
#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 },
#define DUMP_STACK
#endif
-/** Different operations the interpreter can execute
+/** Different operations the interpret can execute
*/
unlang_op_t unlang_ops[UNLANG_TYPE_MAX];
* @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.
/*
* 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");
* - 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;
/** 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...
/** 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;
*
* @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;
/*
* Yield control back to the scheduler, or whatever
- * called the interpreter.
+ * called the interpret.
*/
case UNLANG_ACTION_YIELD:
*result = RLM_MODULE_YIELD; /* Fixup rcode */
RDEBUG2("}");
}
break;
- } /* switch over return code from the interpreter function */
+ } /* switch over return code from the interpret function */
frame_next(stack, 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 */
#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__,
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;
* 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;
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 = {
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.
* @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;
}
/*
- * 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.
*/
*/
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.
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.
* 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.
* 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[] = {
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;
extern "C" {
#endif
+#include <freeradius-devel/server/request.h>
#include <freeradius-devel/unlang/action.h>
#define UNLANG_TOP_FRAME (true)
#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
--- /dev/null
+#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
--- /dev/null
+/*
+ * 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;
+}
*/
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){
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;
/*
RCSID("$Id$")
#include "function.h"
+#include "interpret_priv.h"
#include "module_priv.h"
#include "parallel_priv.h"
#include "subrequest_priv.h"
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);
}
/*
#include <freeradius-devel/server/state.h>
#include "unlang_priv.h"
+#include "interpret_priv.h"
#include "subrequest_priv.h"
/** Parameters for initialising the subrequest
* 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;
}
*
*/
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.
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("}");
/*
*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: