Don't set break and return points in stack frames, represent them as intrinsic properties of operations.
Add "op" flags for brackets, set rcode, return point and break point.
UNLANG_ACTION_EXECUTE_NEXT, //!< Execute the next #unlang_t.
UNLANG_ACTION_PUSHED_CHILD, //!< #unlang_t pushed a new child onto the stack,
//!< execute it instead of continuing.
- UNLANG_ACTION_UNWIND, //!< Break out of the current group.
UNLANG_ACTION_YIELD, //!< Temporarily pause execution until an event occurs.
UNLANG_ACTION_STOP_PROCESSING //!< Break out of processing the current request (unwind).
} unlang_action_t;
&(unlang_op_t){
.name = "call",
.interpret = unlang_call_frame_init,
- .rcode_set = true,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_DEBUG_BRACES
});
}
&(unlang_op_t){
.name = "caller",
.interpret = unlang_caller,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
}
&(unlang_op_t){
.name = "catch",
.interpret = unlang_catch,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
}
static int define_local_variable(CONF_ITEM *ci, unlang_variable_t *var, tmpl_rules_t *t_rules, fr_type_t type, char const *name,
fr_dict_attr_t const *ref);
+#define debug_braces(_type) (unlang_ops[_type].flag & UNLANG_OP_FLAG_DEBUG_BRACES)
/** Compile a variable definition.
*
char const *attr, *value;
unlang_group_t *group;
- fr_assert(unlang_ops[parent->type].debug_braces);
+ fr_assert(debug_braces(parent->type));
/*
* Enforce locations for local variables.
/*
* Over-riding the actions can be done in certain limited
- * situations. In other situations (e.g. "redundant",
+ * situations. In other situations (e.g. "redundant",
* "load-balance"), it doesn't make sense.
*
* Note that this limitation also applies to "retry"
fr_log(log, L_DBG, file, line, "%.*s", depth, unlang_spaces);
}
- if (unlang_ops[instruction->type].debug_braces) {
+ if (debug_braces(instruction->type)) {
fr_log(log, L_DBG, file, line, "%s { #", instruction->debug_name);
} else {
fr_log(log, L_DBG, file, line, "%s #", instruction->debug_name);
}
}
- if (unlang_ops[instruction->type].debug_braces) {
+ if (debug_braces(instruction->type)) {
if (depth) {
fr_log(log, L_DBG, file, line, "%.*s", depth, unlang_spaces);
}
&(unlang_op_t){
.name = "if",
.interpret = unlang_if,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
.frame_state_size = sizeof(unlang_frame_state_cond_t),
.frame_state_type = "unlang_frame_state_cond_t",
});
unlang_register(UNLANG_TYPE_ELSE,
&(unlang_op_t){
.name = "else",
- .interpret = unlang_group,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
+ .interpret = unlang_group
});
unlang_register(UNLANG_TYPE_ELSIF,
&(unlang_op_t){
.name = "elseif",
.interpret = unlang_if,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
.frame_state_size = sizeof(unlang_frame_state_cond_t),
.frame_state_type = "unlang_frame_state_cond_t",
});
unlang_frame_state_foreach_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_foreach_t);
fr_pair_t *vp;
- if (is_stack_unwinding_to_break(request->stack)) return UNLANG_ACTION_CALCULATE_RESULT;
-
vp = fr_dcursor_current(&state->cursor);
fr_assert(vp != NULL);
unlang_foreach_t *gext = unlang_group_to_foreach(g);
unlang_frame_state_foreach_t *state;
- /*
- * Ensure any breaks terminate here...
- */
- break_point_set(frame);
-
MEM(frame->state = state = talloc_zero(request->stack, unlang_frame_state_foreach_t));
talloc_set_destructor(state, _free_unlang_frame_state_foreach);
static unlang_action_t unlang_break(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
+ unlang_action_t ua;
+ unlang_stack_t *stack = request->stack;
+ unsigned int break_depth;
+
RDEBUG2("%s", unlang_ops[frame->instruction->type].name);
*p_result = frame->result;
* Stop at the next break point, or if we hit
* the a top frame.
*/
- return unwind_to_break(request->stack);
+ ua = unwind_to_break(&break_depth, request->stack);
+ repeatable_clear(&stack->frame[break_depth]);
+ return ua;
}
void unlang_foreach_init(void)
&(unlang_op_t){
.name = "foreach",
.interpret = unlang_foreach,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_BREAK_POINT
});
unlang_register(UNLANG_TYPE_BREAK,
static unlang_action_t unlang_policy(rlm_rcode_t *result, request_t *request, unlang_stack_frame_t *frame)
{
- /*
- * Ensure returns stop at the enclosing policy
- */
- return_point_set(frame);
-
return unlang_group(result, request, frame);
}
&(unlang_op_t){
.name = "group",
.interpret = unlang_group,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
unlang_register(UNLANG_TYPE_REDUNDANT,
&(unlang_op_t){
.name = "redundant",
.interpret = unlang_group,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
unlang_register(UNLANG_TYPE_POLICY,
&(unlang_op_t){
.name = "policy",
.interpret = unlang_policy,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RETURN_POINT
});
}
static _Thread_local unlang_interpret_t *intp_thread_default;
static fr_table_num_ordered_t const unlang_action_table[] = {
- { L("unwind"), UNLANG_ACTION_UNWIND },
{ L("calculate-result"), UNLANG_ACTION_CALCULATE_RESULT },
{ L("next"), UNLANG_ACTION_EXECUTE_NEXT },
{ L("pushed-child"), UNLANG_ACTION_PUSHED_CHILD },
RDEBUG2("priority %d", frame->priority);
RDEBUG2("top_frame %s", is_top_frame(frame) ? "yes" : "no");
RDEBUG2("repeat %s", is_repeatable(frame) ? "yes" : "no");
- RDEBUG2("break_point %s", is_break_point(frame) ? "yes" : "no");
- RDEBUG2("return_point %s", is_return_point(frame) ? "yes" : "no");
RDEBUG2("resumable %s", is_yielded(frame) ? "yes" : "no");
+ RDEBUG2("cancelled %s", is_cancelled(frame) ? "yes" : "no");
+ if (frame->instruction) {
+ RDEBUG2("break_point %s", is_break_point(frame) ? "yes" : "no");
+ RDEBUG2("return_point %s", is_return_point(frame) ? "yes" : "no");
+ }
/*
* Call the custom frame dump function
*/
REXDENT();
}
-static char *stack_unwind_flag_dump(uint8_t unwind)
-{
- static __thread char buf[256];
- size_t len;
-
-#define UNWIND_FRAME_FLAG_DUMP(attrib) \
- if (unwind & attrib) strcat(buf, #attrib" ")
-
- snprintf(buf, sizeof(buf), "unwind=0x%x (", unwind);
-
- UNWIND_FRAME_FLAG_DUMP(UNWIND_FRAME_FLAG_TOP_FRAME);
- UNWIND_FRAME_FLAG_DUMP(UNWIND_FRAME_FLAG_BREAK_POINT);
- UNWIND_FRAME_FLAG_DUMP(UNWIND_FRAME_FLAG_RETURN_POINT);
-
- len = strlen(buf);
- if (buf[len - 1] == ' ') buf[len - 1] = '\0'; /* Trim trailing space */
- strcat(buf, ")");
-
-#undef UNWIND_FRAME_FLAG_DUMP
-
- return buf;
-}
-
static void stack_dump(request_t *request)
{
int i;
unlang_stack_t *stack = request->stack;
- RDEBUG2("----- Begin stack debug [depth %i, %s] -----", stack->depth, stack_unwind_flag_dump(stack->unwind));
+ RDEBUG2("----- Begin stack debug [depth %i] -----", stack->depth);
for (i = stack->depth; i >= 0; i--) {
unlang_stack_frame_t *frame = &stack->frame[i];
RDEBUG2("[%d] Frame contents", i);
frame_dump(request, frame);
}
- RDEBUG2("----- End stack debug [depth %i, %s] -------", stack->depth, stack_unwind_flag_dump(stack->unwind));
+ RDEBUG2("----- End stack debug [depth %i] -------", stack->depth);
}
#define DUMP_STACK if (DEBUG_ENABLED5) stack_dump(request)
#else
unlang_group_t *g;
unlang_variable_ref_t *ref;
- fr_assert(unlang_ops[frame->instruction->type].debug_braces);
+ fr_assert(has_debug_braces(frame));
g = unlang_generic_to_group(frame->instruction);
fr_table_str_by_value(mod_rcode_table, *result, "<invalid>"),
*priority);
+ if (is_cancelled(frame)) {
+ RDEBUG4("** [%i] %s - frame is cancelled",
+ stack->depth, __FUNCTION__);
+ frame->result = *result;
+ frame->priority = *priority;
+ return UNLANG_FRAME_ACTION_POP;
+ }
+
/*
* Update request->rcode if the instruction says we should
* We don't care about priorities for this.
*/
- if (unlang_ops[instruction->type].rcode_set) {
+ if (is_rcode_set(frame)) {
RDEBUG3("Setting rcode to '%s'",
fr_table_str_by_value(rcode_table, *result, "<INVALID>"));
request->rcode = *result;
*priority);
}
- /*
- * Not allowed in frame uflags...
- */
- fr_assert(!(frame->uflags & UNWIND_FRAME_FLAG_NO_CLEAR));
-
- /*
- * If we are unwinding the stack due to a break / return,
- * then handle it now.
- */
- if (stack->unwind) {
- /*
- * Continue unwinding...
- */
- if (!(stack->unwind & frame->uflags) || (stack->unwind & UNWIND_FRAME_FLAG_NO_CLEAR)) {
- RDEBUG4("** [%i] %s - unwinding current frame with (%s %d) - flags - stack (%i), frame (%i)",
- stack->depth, __FUNCTION__,
- fr_table_str_by_value(mod_rcode_table, frame->result, "<invalid>"),
- frame->priority, stack->unwind, frame->uflags);
-
- return UNLANG_FRAME_ACTION_POP;
- }
-
- /*
- * If we've been told to unwind, and we've hit
- * the frame we should be unwinding to,
- * and the "NO_CLEAR" flag hasn't been set, then
- * clear the unwind field so we stop unwinding.
- */
- stack->unwind = UNWIND_FRAME_FLAG_NONE;
-
- RDEBUG4("** [%i] %s - unwind stop (%s %d) - flags - stack unwind (%i), frame uflags (%i)",
- stack->depth, __FUNCTION__,
- fr_table_str_by_value(mod_rcode_table, frame->result, "<invalid>"),
- frame->priority, stack->unwind, frame->uflags);
- }
-
return frame->next ? UNLANG_FRAME_ACTION_NEXT : UNLANG_FRAME_ACTION_POP;
}
/** Evaluates all the unlang nodes in a section
+ *
+ * This function interprets a list of unlang instructions at a given level using the same
+ * stack frame, and pushes additional frames onto the stack as needed.
+ *
+ * This function can be seen as moving horizontally.
*
* @param[in] request The current request.
* @param[in] frame The current stack frame.
*/
while (frame->instruction) {
unlang_t const *instruction = frame->instruction;
- unlang_action_t ua = UNLANG_ACTION_UNWIND;
+ unlang_action_t ua;
unlang_frame_action_t fa;
DUMP_STACK;
return UNLANG_FRAME_ACTION_POP;
}
- if (!is_repeatable(frame) && (unlang_ops[instruction->type].debug_braces)) {
+ if (!is_repeatable(frame) && has_debug_braces(frame)) {
RDEBUG2("%s {", instruction->debug_name);
RINDENT();
}
*/
repeatable_clear(frame);
unlang_frame_perf_resume(frame);
- ua = frame->process(result, request, frame);
/*
- * If this frame is breaking or returning
- * frame then clear that unwind flag,
- * it's been consumed by this call.
- *
- * We leave the unwind flags for the eval
- * call so that the process function knows
- * that the stack is being unwound.
+ * catch plays games with the frame so we skip
+ * to the next catch section at a given depth,
+ * it's not safe to access frame->instruction
+ * after this point, and the cached instruction
+ * should be used instead.
*/
- if (is_break_point(frame)) {
- stack_unwind_break_clear(stack);
- stack_unwind_top_frame_clear(stack);
- }
- if (is_return_point(frame)) {
- stack_unwind_return_clear(stack);
- stack_unwind_top_frame_clear(stack);
- }
+ ua = frame->process(result, request, frame);
RDEBUG4("** [%i] %s << %s (%d)", stack->depth, __FUNCTION__,
fr_table_str_by_value(unlang_action_table, ua, "<INVALID>"), *priority);
fr_assert(*priority >= -1);
fr_assert(*priority <= MOD_PRIORITY_MAX);
+ /*
+ * If the frame is cancelled we ignore the
+ * return code of the process function and
+ * jump to calculate result (which tells us
+ * to start popping frames). This is because
+ * the cancellation can be signalled
+ * asynchronously, and the process function
+ * may not be aware that it's happened.
+ */
+ if (is_cancelled(frame)) goto calculate_result;
+
switch (ua) {
/*
* The request is now defunct, and we should not
*result = frame->result;
return UNLANG_FRAME_ACTION_NEXT;
- /*
- * We're in a looping construct and need to stop
- * execution of the current section.
- */
- case UNLANG_ACTION_UNWIND:
- if (*priority < 0) *priority = 0;
- frame->result = *result;
- frame->priority = *priority;
- frame->next = NULL;
- fr_assert(stack->unwind != UNWIND_FRAME_FLAG_NONE);
- return UNLANG_FRAME_ACTION_POP;
-
/*
* Yield control back to the scheduler, or whatever
* called the interpreter.
* the section rcode and priority.
*/
case UNLANG_ACTION_CALCULATE_RESULT:
- if (unlang_ops[instruction->type].debug_braces) {
+ calculate_result:
+ if (has_debug_braces(instruction)) {
REXDENT();
/*
return UNLANG_FRAME_ACTION_POP;
case UNLANG_FRAME_ACTION_RETRY:
- if (unlang_ops[instruction->type].debug_braces) {
+ if (has_debug_braces(instruction)) {
REXDENT();
RDEBUG2("} # retrying the same section");
}
* Execute the next instruction in this frame
*/
case UNLANG_ACTION_EXECUTE_NEXT:
- if (unlang_ops[instruction->type].debug_braces) {
+ if (has_debug_braces(instruction)) {
REXDENT();
RDEBUG2("}");
}
}
/** Run the interpreter for a current request
+ *
+ * This function runs the interpreter for a request. It deals with popping
+ * stack frames, and calaculating the final result for the frame.
*
* @param[in] request to run. If this is an internal request
* the request may be freed by the interpreter.
/*
* Close out the section we entered earlier
*/
- if (unlang_ops[frame->instruction->type].debug_braces) {
+ if (has_debug_braces(frame)) {
REXDENT();
/*
*/
if (is_top_frame(frame)) break; /* stop */
+ /*
+ * Carry on popping until we find something that shouldn't
+ * be cancelled.
+ */
+ if (is_cancelled(frame)) {
+ fa = UNLANG_FRAME_ACTION_POP;
+ continue;
+ }
+
fa = result_calculate(request, frame, &stack->result, &stack->priority);
/*
intp->funcs.detach(request, intp->uctx);
}
-/** Send a signal (usually stop) to a request
+/** Delivers a frame to one or more frames in the stack
*
- * This is typically called via an "async" action, i.e. an action
- * outside of the normal processing of the request.
+ * This is typically called via an "async" action, i.e. an action outside
+ * of the normal processing of the request.
+ *
+ * For FR_SIGNAL_CANCEL all frames are marked up for cancellation, but the
+ * cancellation is handled by the interpret.
*
- * If there is no #unlang_module_signal_t callback defined, the action is ignored.
+ * Other signal types are delivered immediately, inrrespecitve of whether
+ * the request is currently being processed or not.
*
- * The signaling stops at the "limit" frame. This is so that keywords
+ * Signaling stops at the "limit" frame. This is so that keywords
* such as "timeout" and "limit" can signal frames *lower* than theirs
* to stop, but then continue with their own work.
*
+ * @note It's better (clearer) to use one of the unwind_* functions
+ * unless the entire request is being cancelled.
+ *
* @param[in] request The current request.
* @param[in] action to signal.
* @param[in] limit the frame at which to stop signaling.
fr_assert(stack->depth > 0);
/*
- * Destructive signal where we clean each of the
- * stack frames up in turn.
- *
- * We do this to avoid possible free ordering
- * issues where memory allocated by modules higher
- * in the stack is used by modules lower in the
- * stack.
+ * Does not complete the unwinding here, just marks
+ * up the frames for unwinding. The request must
+ * be marked as runnable to complete the cancellation.
*/
if (action == FR_SIGNAL_CANCEL) {
- for (i = depth; i > limit; i--) {
- frame = &stack->frame[i];
- if (frame->signal) frame->signal(request, frame, action);
- frame_cleanup(frame);
- }
- stack->depth = i;
+ unwind_to_depth(stack, limit);
return;
}
* This is typically called via an "async" action, i.e. an action
* outside of the normal processing of the request.
*
- * If there is no #unlang_module_signal_t callback defined, the action is ignored.
+ * @note This does NOT immediately stop the request, it just deliveres
+ * signals, and in the case of a cancel, marks up frames for unwinding.
+ * The request must be marked as runnable, and executed by the
+ * interpreter to complete the cancellation.
*
* @param[in] request The current request.
* @param[in] action to signal.
*/
talloc_free(request_data_get(request, (void *)unlang_cancel_xlat, 0));
- unlang_interpret_signal(request, FR_SIGNAL_CANCEL);
-}
-
-static xlat_action_t unlang_cancel_never_run(UNUSED TALLOC_CTX *ctx, UNUSED fr_dcursor_t *out,
- UNUSED xlat_ctx_t const *xctx,
- UNUSED request_t *request, UNUSED fr_value_box_list_t *in)
-{
- fr_assert_msg(0, "Should never be run");
- return XLAT_ACTION_FAIL;
+ unwind_all(request->stack);
}
/** Allows a request to dynamically alter its own lifetime
XLAT_ARGS(args, &timeout);
+ /*
+ * No timeout means cancel immediately, so yield allowing
+ * the interpreter to run the event we added to cancel
+ * the request.
+ *
+ * We call unlang_xlat_yield to keep the interpreter happy
+ * as it expects to see a resume function set.
+ */
+ if (!timeout || fr_time_delta_eq(timeout->vb_time_delta, fr_time_delta_from_sec(0))) {
+ unwind_all(request->stack);
+ return XLAT_ACTION_DONE;
+ }
+
/*
* First see if we already have a timeout event
* that was previously added by this xlat.
return XLAT_ACTION_FAIL;
}
- /*
- * No timeout means cancel immediately, so yield allowing
- * the interpreter to run the event we added to cancel
- * the request.
- *
- * We call unlang_xlat_yield to keep the interpreter happy
- * as it expects to see a resume function set.
- */
- if (!timeout || fr_time_delta_eq(timeout->vb_time_delta, fr_time_delta_from_sec(0))) {
- return unlang_xlat_yield(request, unlang_cancel_never_run, NULL, 0, NULL);
- }
-
if (ev_p_og) {
MEM(vb = fr_value_box_alloc(ctx, FR_TYPE_TIME_DELTA, NULL));
.name = "limit",
.interpret = unlang_limit,
.signal = unlang_limit_signal,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
.frame_state_size = sizeof(unlang_frame_state_limit_t),
.frame_state_type = "unlang_frame_state_limit_t",
&(unlang_op_t){
.name = "load-balance group",
.interpret = unlang_load_balance,
- .rcode_set = true,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
.frame_state_size = sizeof(unlang_frame_state_redundant_t),
.frame_state_type = "unlang_frame_state_redundant_t",
});
&(unlang_op_t){
.name = "redundant-load-balance group",
.interpret = unlang_redundant_load_balance,
- .rcode_set = true,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
.frame_state_size = sizeof(unlang_frame_state_redundant_t),
.frame_state_type = "unlang_frame_state_redundant_t",
});
&(unlang_op_t){
.name = "update",
.interpret = unlang_update_state_init,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
unlang_register(UNLANG_TYPE_MAP,
&(unlang_op_t){
.name = "map",
- .rcode_set = true,
+ .flag = UNLANG_OP_FLAG_RCODE_SET,
.interpret = unlang_map_state_init,
.frame_state_size = sizeof(unlang_frame_state_map_proc_t),
.frame_state_type = "unlang_frame_state_map_proc_t",
request->module = state->previous_module;
break;
- case UNLANG_ACTION_UNWIND:
- request->module = state->previous_module;
- break;
-
case UNLANG_ACTION_FAIL:
*p_result = RLM_MODULE_FAIL;
request->module = state->previous_module;
state->thread = module_thread(m->mmc.mi);
fr_assert(state->thread != NULL);
- /*
- * Don't allow returning _through_ modules
- */
- return_point_set(frame_current(request));
-
/*
* For logging unresponsive children.
*/
}
break;
- case UNLANG_ACTION_UNWIND:
- break;
-
case UNLANG_ACTION_FAIL:
fail:
*p_result = RLM_MODULE_FAIL;
&(unlang_op_t){
.name = "module",
.interpret = unlang_module,
- .rcode_set = true,
+ .flag = UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_RETURN_POINT,
.signal = unlang_module_signal,
.frame_state_size = sizeof(unlang_frame_state_module_t),
.frame_state_type = "unlang_frame_state_module_t",
* done.
*/
} else {
- unlang_stack_frame_t *child_frame;
-
if (unlang_function_push(child,
NULL,
unlang_parallel_child_done,
~FR_SIGNAL_DETACH,
UNLANG_TOP_FRAME,
&state->children[i]) < 0) goto error;
- child_frame = frame_current(child);
- return_point_set(child_frame); /* Don't unwind this frame */
state->children[i].num = i;
state->children[i].name = talloc_bstrdup(state, child->name);
.name = "parallel",
.interpret = unlang_parallel,
.signal = unlang_parallel_signal,
- .rcode_set = true,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET
});
}
* Stop at the next return point, or if we hit
* the a top frame.
*/
- return unwind_to_return(request->stack);
+ return unwind_to_return(NULL, request->stack);
}
void unlang_return_init(void)
.name = "subrequest",
.interpret = unlang_subrequest_parent_init,
.signal = unlang_subrequest_signal_child,
- .rcode_set = true,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET,
.frame_state_size = sizeof(unlang_frame_state_subrequest_t),
.frame_state_type = "unlang_frame_state_subrequest_t",
});
UNLANG_TOP_FRAME,
state) < 0) return -1;
- return_point_set(frame_current(child)); /* Stop return going through the resumption frame */
-
return 0;
}
#include "group_priv.h"
#include "switch_priv.h"
-static unlang_action_t unlang_switch(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+static unlang_action_t unlang_switch(UNUSED rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
unlang_t *found;
&(unlang_op_t){
.name = "switch",
.interpret = unlang_switch,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
unlang_register(UNLANG_TYPE_CASE,
&(unlang_op_t){
.name = "case",
.interpret = unlang_case,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
}
&(unlang_op_t){
.name = "timeout",
.interpret = unlang_timeout,
- .debug_braces = true,
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES,
.frame_state_size = sizeof(unlang_frame_state_timeout_t),
.frame_state_type = "unlang_frame_state_timeout_t",
});
.signal = unlang_transaction_signal,
.frame_state_size = sizeof(unlang_frame_state_transaction_t),
.frame_state_type = "unlang_frame_state_transaction_t",
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
}
&(unlang_op_t){
.name = "try",
.interpret = unlang_try,
- .debug_braces = true
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES
});
}
typedef int (*unlang_thread_instantiate_t)(unlang_t const *instruction, void *thread_inst);
+typedef enum CC_HINT(flag_enum) {
+ UNLANG_OP_FLAG_NONE = 0x00, //!< No flags.
+ UNLANG_OP_FLAG_DEBUG_BRACES = 0x01, //!< Print debug braces.
+ UNLANG_OP_FLAG_RCODE_SET = 0x02, //!< Set request->rcode to the result of this operation.
+ UNLANG_OP_FLAG_BREAK_POINT = 0x04, //!< Break point.
+ UNLANG_OP_FLAG_RETURN_POINT = 0x08, //!< Return point.
+} unlang_op_flag_t;
+
/** An unlang operation
*
* These are like the opcodes in other interpreters. Each operation, when executed
size_t thread_inst_size;
char const *thread_inst_type;
-
- bool debug_braces; //!< Whether the operation needs to print braces
- ///< in debug mode.
-
- bool rcode_set; //!< Set request->rcode to the result of this operation.
+ unlang_op_flag_t flag; //!< Flags for this operation.
size_t frame_state_size; //!< size of instance data in the stack frame
extern fr_table_num_sorted_t const mod_rcode_table[];
extern size_t mod_rcode_table_len;
+static inline void repeatable_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_REPEAT; }
+static inline void top_frame_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_TOP_FRAME; }
+static inline void yielded_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_YIELDED; }
+static inline void cancel_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_CANCEL; }
+
+static inline void repeatable_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_REPEAT; }
+static inline void top_frame_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_TOP_FRAME; }
+static inline void yielded_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_YIELDED; }
+static inline void cancel_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_CANCEL; }
+
+static inline bool is_repeatable(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_REPEAT; }
+static inline bool is_top_frame(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_TOP_FRAME; }
+static inline bool is_yielded(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_YIELDED; }
+static inline bool is_cancelled(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_CANCEL; }
+
+static inline bool _instruction_has_debug_braces(unlang_t const *instruction) { return unlang_ops[instruction->type].flag & UNLANG_OP_FLAG_DEBUG_BRACES; }
+static inline bool _frame_has_debug_braces(unlang_stack_frame_t const *frame) { return unlang_ops[frame->instruction->type].flag & UNLANG_OP_FLAG_DEBUG_BRACES; }
+#define has_debug_braces(_thing) \
+ _Generic((_thing), \
+ unlang_t *: _instruction_has_debug_braces((unlang_t const *)(_thing)), \
+ unlang_t const *: _instruction_has_debug_braces((unlang_t const *)(_thing)), \
+ unlang_stack_frame_t *: _frame_has_debug_braces((unlang_stack_frame_t const *)(_thing)), \
+ unlang_stack_frame_t const *: _frame_has_debug_braces((unlang_stack_frame_t const *)(_thing)) \
+ )
+static inline bool is_rcode_set(unlang_stack_frame_t const *frame) { return unlang_ops[frame->instruction->type].flag & UNLANG_OP_FLAG_RCODE_SET; }
+static inline bool is_break_point(unlang_stack_frame_t const *frame) { return unlang_ops[frame->instruction->type].flag & UNLANG_OP_FLAG_BREAK_POINT; }
+static inline bool is_return_point(unlang_stack_frame_t const *frame) { return unlang_ops[frame->instruction->type].flag & UNLANG_OP_FLAG_RETURN_POINT; }
+
+/** Find the first frame with a given flag
+ *
+ * @return
+ * - 0 if no frame has the flag.
+ * - The index of the first frame with the flag.
+ */
+static inline unsigned int unlang_frame_by_flag(unlang_stack_t *stack, unlang_frame_flag_t flag)
+{
+ unsigned int i;
-static inline void repeatable_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_REPEAT; }
-static inline void top_frame_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_TOP_FRAME; }
-static inline void break_point_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_BREAK_POINT; }
-static inline void return_point_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_RETURN_POINT; }
-static inline void yielded_set(unlang_stack_frame_t *frame) { frame->uflags |= UNWIND_FRAME_FLAG_YIELDED; }
+ for (i = stack->depth; i > 0; i--) {
+ unlang_stack_frame_t *frame = &stack->frame[i];
+
+ if (frame->uflags & flag) return i;
+ }
+ return 0;
+}
+
+/** Find the first frame with a given flag
+ *
+ * @return
+ * - 0 if no frame has the flag.
+ * - The index of the first frame with the flag.
+ */
+static inline unsigned int unlang_frame_by_op_flag(unlang_stack_t *stack, unlang_op_flag_t flag)
+{
+ unsigned int i;
-static inline void repeatable_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_REPEAT; }
-static inline void top_frame_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_TOP_FRAME; }
-static inline void break_point_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_BREAK_POINT; }
-static inline void return_point_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_RETURN_POINT; }
-static inline void yielded_clear(unlang_stack_frame_t *frame) { frame->uflags &= ~UNWIND_FRAME_FLAG_YIELDED; }
+ for (i = stack->depth; i > 0; i--) {
+ unlang_stack_frame_t *frame = &stack->frame[i];
-static inline bool is_repeatable(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_REPEAT; }
-static inline bool is_top_frame(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_TOP_FRAME; }
-static inline bool is_break_point(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_BREAK_POINT; }
-static inline bool is_return_point(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_RETURN_POINT; }
-static inline bool is_yielded(unlang_stack_frame_t const *frame) { return frame->uflags & UNWIND_FRAME_FLAG_YIELDED; }
+ if (unlang_ops[frame->instruction->type].flag & flag) return i;
+ }
+ return 0;
+}
-static inline unlang_action_t unwind_to_break(unlang_stack_t *stack)
+/** Mark up frames as cancelled so they're immediately popped by the interpreter
+ *
+ * @note We used to do this asynchronously, but now we may need to execute timeout sections
+ * which means it's not enough to pop and cleanup the stack, we need continue executing
+ * the request.
+ *
+ * @param[in] stack The current stack.
+ * @param[in] to_depth mark all frames below this depth as cancelled.
+ */
+static inline unlang_action_t unwind_to_depth(unlang_stack_t *stack, unsigned int to_depth)
{
- stack->unwind = UNWIND_FRAME_FLAG_BREAK_POINT | UNWIND_FRAME_FLAG_TOP_FRAME;
- return UNLANG_ACTION_UNWIND;
+ unlang_stack_frame_t *frame;
+ unsigned int i, depth = stack->depth; /* must be signed to avoid underflow */
+
+ if (!fr_cond_assert(to_depth >= 1)) return UNLANG_ACTION_FAIL;
+
+ for (i = depth; i >= to_depth; i--) {
+ frame = &stack->frame[i];
+ frame->uflags |= UNWIND_FRAME_FLAG_CANCEL;
+ }
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
-static inline unlang_action_t unwind_to_return(unlang_stack_t *stack)
+
+/** Mark the entire stack as cancelled
+ *
+ * This cancels all frames up to the next "break" frame.
+ *
+ * @param[out] break_depth Depth of the break point.
+ * @param[in] stack The current stack.
+ * @return UNLANG_ACTION_CALCULATE_RESULT
+ */
+static inline unlang_action_t unwind_to_break(unsigned int *break_depth, unlang_stack_t *stack)
{
- stack->unwind = UNWIND_FRAME_FLAG_RETURN_POINT | UNWIND_FRAME_FLAG_TOP_FRAME;
- return UNLANG_ACTION_UNWIND;
+ unsigned int depth;
+
+ depth = unlang_frame_by_op_flag(stack, UNLANG_OP_FLAG_BREAK_POINT);
+ if (depth == 0) return UNLANG_ACTION_CALCULATE_RESULT;
+
+ unwind_to_depth(stack, depth + 1); /* cancel UP TO the break point */
+
+ if (break_depth) *break_depth = depth;
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
-static inline unlang_action_t unwind_all(unlang_stack_t *stack)
+
+/** Mark the entire stack as cancelled
+ *
+ * This cancels all frames up to the next "return" frame.
+ *
+ * @param[out] return_depth Depth of the break point.
+ * @param[in] stack The current stack.
+ * @return UNLANG_ACTION_CALCULATE_RESULT
+ */
+static inline unlang_action_t unwind_to_return(unsigned int *return_depth, unlang_stack_t *stack)
{
- stack->unwind = UNWIND_FRAME_FLAG_TOP_FRAME | UNWIND_FRAME_FLAG_NO_CLEAR;
- return UNLANG_ACTION_UNWIND;
+ unsigned int depth;
+
+ depth = unlang_frame_by_op_flag(stack, UNLANG_OP_FLAG_RETURN_POINT);
+ if (depth == 0) return UNLANG_ACTION_CALCULATE_RESULT;
+
+ unwind_to_depth(stack, depth + 1); /* cancel UP TO the break point */
+
+ if (return_depth) *return_depth = depth;
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
-static inline bool is_stack_unwinding_to_top_frame(unlang_stack_t *stack) { return stack->unwind & UNWIND_FRAME_FLAG_TOP_FRAME; }
-static inline bool is_stack_unwinding_to_break(unlang_stack_t *stack) { return stack->unwind & UNWIND_FRAME_FLAG_BREAK_POINT; }
-static inline bool is_stack_unwinding_to_return(unlang_stack_t *stack) { return stack->unwind & UNWIND_FRAME_FLAG_RETURN_POINT; }
-static inline void stack_unwind_top_frame_clear(unlang_stack_t *stack) { stack->unwind &= ~UNWIND_FRAME_FLAG_TOP_FRAME; }
-static inline void stack_unwind_break_clear(unlang_stack_t *stack) { stack->unwind &= ~UNWIND_FRAME_FLAG_BREAK_POINT; }
-static inline void stack_unwind_return_clear(unlang_stack_t *stack) { stack->unwind &= ~UNWIND_FRAME_FLAG_RETURN_POINT; }
+/** Mark the entire stack as cancelled
+ *
+ * This cancels all frames in the stack ignoring the top frames.
+ *
+ * @param[in] stack The current stack.
+ */
+static inline void unwind_all(unlang_stack_t *stack)
+{
+ unwind_to_depth(stack, 1);
+}
static inline unlang_stack_frame_t *frame_current(request_t *request)
{
* Signal the frame to get it back into a consistent state
* as we won't be calling the resume function.
*/
- if (stack->unwind && is_repeatable(frame) &&
- ((is_stack_unwinding_to_break(stack) && !is_break_point(frame)) ||
- (is_stack_unwinding_to_return(stack) && !is_return_point(frame)))) {
+ if (is_cancelled(frame)) {
if (frame->signal) frame->signal(request, frame, FR_SIGNAL_CANCEL);
repeatable_clear(frame);
}