]> git.ipfire.org Git - thirdparty/freeradius-server.git/commitdiff
Move to synchronous stack unwinding on cancellation
authorArran Cudbard-Bell <a.cudbardb@freeradius.org>
Tue, 29 Apr 2025 23:15:59 +0000 (19:15 -0400)
committerArran Cudbard-Bell <a.cudbardb@freeradius.org>
Wed, 30 Apr 2025 20:22:48 +0000 (16:22 -0400)
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.

22 files changed:
src/lib/unlang/action.h
src/lib/unlang/call.c
src/lib/unlang/caller.c
src/lib/unlang/catch.c
src/lib/unlang/compile.c
src/lib/unlang/condition.c
src/lib/unlang/foreach.c
src/lib/unlang/group.c
src/lib/unlang/interpret.c
src/lib/unlang/limit.c
src/lib/unlang/load_balance.c
src/lib/unlang/map.c
src/lib/unlang/module.c
src/lib/unlang/parallel.c
src/lib/unlang/return.c
src/lib/unlang/subrequest.c
src/lib/unlang/subrequest_child.c
src/lib/unlang/switch.c
src/lib/unlang/timeout.c
src/lib/unlang/transaction.c
src/lib/unlang/try.c
src/lib/unlang/unlang_priv.h

index 6bacd0ccd0a66e89592d46741b203e94ee99667f..6212cfd017ce7f2f20332e3a2f72ed55cbb6cbbc 100644 (file)
@@ -38,7 +38,6 @@ typedef enum {
        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;
index 549a66db2f71425a612a8fe75a7a39ec8562be45..2a3915e37e78b3c80d8a0d20b856a35e2c63867c 100644 (file)
@@ -252,7 +252,6 @@ void unlang_call_init(void)
                           &(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
                           });
 }
index ab049242057eb4228a1c81b0298b28baf22b3b4b..a8fda52ea758eb7b2fe79ec9d6b548755ce4ef26 100644 (file)
@@ -58,6 +58,6 @@ void unlang_caller_init(void)
                           &(unlang_op_t){
                                .name = "caller",
                                .interpret = unlang_caller,
-                               .debug_braces = true
+                               .flag = UNLANG_OP_FLAG_DEBUG_BRACES
                           });
 }
index 48ead6359730c8f42ba8c9c0fe0a0148dbd66e51..78dd113ea10f6855613cac040bb3a32b56890d18 100644 (file)
@@ -102,6 +102,6 @@ void unlang_catch_init(void)
                           &(unlang_op_t){
                                .name = "catch",
                                .interpret = unlang_catch,
-                               .debug_braces = true
+                               .flag = UNLANG_OP_FLAG_DEBUG_BRACES
                           });
 }
index d135984998415a954c49504a0852185aeaee2642..f31010c4df2b6fded706c1fc2a0b5e0165dc0400 100644 (file)
@@ -1545,6 +1545,7 @@ static unlang_t *compile_edit_pair(unlang_t *parent, unlang_compile_t *unlang_ct
 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.
  *
@@ -1558,7 +1559,7 @@ static int compile_variable(unlang_t *parent, unlang_compile_t *unlang_ctx, CONF
        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.
@@ -1949,7 +1950,7 @@ static bool compile_action_subsection(unlang_t *c, CONF_SECTION *cs, CONF_SECTIO
 
        /*
         *      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"
@@ -5318,7 +5319,7 @@ static void unlang_perf_dump(fr_log_t *log, unlang_t const *instruction, int dep
                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);
@@ -5337,7 +5338,7 @@ static void unlang_perf_dump(fr_log_t *log, unlang_t const *instruction, int dep
                }
        }
 
-       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);
                }
index 26325f366177232b53a36fecfc6db5fc4d6a8f93..f711a30c21684f110a157a5d5a79261617a903b5 100644 (file)
@@ -106,7 +106,7 @@ void unlang_condition_init(void)
                           &(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",
                           });
@@ -114,15 +114,15 @@ void unlang_condition_init(void)
        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",
                           });
index b3d4b597b881b57743a99767b0afff977aeb29f5..2f1e360a444f518773138e0b99a66a5e2ec0ad34 100644 (file)
@@ -295,8 +295,6 @@ static unlang_action_t unlang_foreach_attr_next(rlm_rcode_t *p_result, request_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);
 
@@ -478,11 +476,6 @@ static unlang_action_t unlang_foreach(rlm_rcode_t *p_result, request_t *request,
        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);
 
@@ -533,6 +526,10 @@ static unlang_action_t unlang_foreach(rlm_rcode_t *p_result, request_t *request,
 
 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;
@@ -541,7 +538,9 @@ static unlang_action_t unlang_break(rlm_rcode_t *p_result, request_t *request, u
         *      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)
@@ -550,7 +549,7 @@ 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,
index d94120dd86f80d930be81037487de3c84eae018e..2ce631989a6429eb3c2136c65d546ba3f95296be 100644 (file)
@@ -34,11 +34,6 @@ unlang_action_t unlang_group(rlm_rcode_t *p_result, request_t *request, unlang_s
 
 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);
 }
 
@@ -49,20 +44,20 @@ void unlang_group_init(void)
                           &(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
                           });
 }
index ec70a8e9f493b0986df5fed7f49da9904d82d1e1..2c5bba05110d72a46df51b311ae8748707aaba3f 100644 (file)
@@ -38,7 +38,6 @@ RCSID("$Id$")
 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 },
@@ -89,10 +88,13 @@ static void frame_dump(request_t *request, unlang_stack_frame_t *frame)
        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
         */
@@ -101,42 +103,19 @@ static void frame_dump(request_t *request, unlang_stack_frame_t *frame)
        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
@@ -260,7 +239,7 @@ unlang_action_t unlang_interpret_push_children(rlm_rcode_t *p_result, request_t
        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);
 
@@ -330,11 +309,19 @@ unlang_frame_action_t result_calculate(request_t *request, unlang_stack_frame_t
                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;
@@ -484,46 +471,15 @@ finalize:
                        *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.
@@ -545,7 +501,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *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;
@@ -598,7 +554,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                        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();
                }
@@ -622,25 +578,15 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                 */
                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);
@@ -648,6 +594,17 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                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
@@ -670,18 +627,6 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                        *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.
@@ -713,7 +658,8 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                 *      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();
 
                                /*
@@ -741,7 +687,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                                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");
                                }
@@ -756,7 +702,7 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
                 *      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("}");
                        }
@@ -775,6 +721,9 @@ unlang_frame_action_t frame_eval(request_t *request, unlang_stack_frame_t *frame
 }
 
 /** 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.
@@ -873,7 +822,7 @@ CC_HINT(hot) rlm_rcode_t unlang_interpret(request_t *request, bool running)
                        /*
                         *      Close out the section we entered earlier
                         */
-                       if (unlang_ops[frame->instruction->type].debug_braces) {
+                       if (has_debug_braces(frame)) {
                                REXDENT();
 
                                /*
@@ -894,6 +843,15 @@ CC_HINT(hot) rlm_rcode_t unlang_interpret(request_t *request, bool running)
                         */
                        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);
 
                        /*
@@ -1140,17 +1098,24 @@ void unlang_interpret_request_detach(request_t *request)
        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.
@@ -1166,21 +1131,12 @@ void unlang_stack_signal(request_t *request, fr_signal_t action, int limit)
        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;
        }
 
@@ -1204,7 +1160,10 @@ void unlang_stack_signal(request_t *request, fr_signal_t action, int limit)
  * 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.
@@ -1500,15 +1459,7 @@ static void unlang_cancel_event(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now
         */
        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
@@ -1529,6 +1480,19 @@ static xlat_action_t unlang_cancel_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
 
        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.
@@ -1558,18 +1522,6 @@ static xlat_action_t unlang_cancel_xlat(TALLOC_CTX *ctx, fr_dcursor_t *out,
                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));
 
index 670851dd2fd7433033b9f341c8ab759f6772e746..877c9c8dc5b54ca5e13887b2427cb6cc55886b94 100644 (file)
@@ -128,7 +128,7 @@ void unlang_limit_init(void)
                                .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",
 
index 0e907dba4fd54aaf6a5879aa6d1789073f032732..52305ede8aa52588c9f14e33de203583baaa839d 100644 (file)
@@ -253,8 +253,7 @@ void unlang_load_balance_init(void)
                           &(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",
                           });
@@ -263,8 +262,7 @@ void unlang_load_balance_init(void)
                           &(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",
                           });
index cdd9fe9420a50c7da9f48333824f45605ca66168..c2e7a9c603bcc7b1dfc0bae1f4ad3aa61dd23663 100644 (file)
@@ -383,13 +383,13 @@ void unlang_map_init(void)
                           &(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",
index 135a9a866178f15ac9b076100a7552d6a89f0f21..1013f1e0eae88d91d67c801835d8064aa571b24f 100644 (file)
@@ -661,10 +661,6 @@ static unlang_action_t unlang_module_resume(rlm_rcode_t *p_result, request_t *re
                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;
@@ -826,11 +822,6 @@ static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request,
        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.
         */
@@ -934,9 +925,6 @@ static unlang_action_t unlang_module(rlm_rcode_t *p_result, request_t *request,
                }
                break;
 
-       case UNLANG_ACTION_UNWIND:
-               break;
-
        case UNLANG_ACTION_FAIL:
        fail:
                *p_result = RLM_MODULE_FAIL;
@@ -960,7 +948,7 @@ void unlang_module_init(void)
                           &(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",
index 7b6a6cfacb1dfdf17f3dff45ff4a776e0319d259..7c5c891109d2251330139fdf781f85063bd6e581 100644 (file)
@@ -360,8 +360,6 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
                 *      done.
                 */
                } else {
-                       unlang_stack_frame_t *child_frame;
-
                        if (unlang_function_push(child,
                                                 NULL,
                                                 unlang_parallel_child_done,
@@ -369,8 +367,6 @@ static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t
                                                 ~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);
@@ -497,7 +493,6 @@ void unlang_parallel_init(void)
                                .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
                           });
 }
index 355147a2ddfbe4f42b8922a104c0106acd186f3c..fdebfaf422376a09d780d04e1e3661e32a86681a 100644 (file)
@@ -37,7 +37,7 @@ unlang_action_t unlang_return(rlm_rcode_t *p_result, request_t *request, unlang_
         *      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)
index 35c8e18c71d17cfa0644fa5f01de83c1c76ed96a..d537340ff82b055f47dc29aa1869c03b3eb100c3 100644 (file)
@@ -299,8 +299,7 @@ int unlang_subrequest_op_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",
                        });
index 9c5c00cc856ada1445a1715bf515aaf91bcc5458..8efb21cb496b85aa1dece3c00628ac101e322f75 100644 (file)
@@ -233,8 +233,6 @@ int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_sub
                                 UNLANG_TOP_FRAME,
                                 state) < 0) return -1;
 
-       return_point_set(frame_current(child)); /* Stop return going through the resumption frame */
-
        return 0;
 }
 
index f1056e18a03ef8b2d89267446b5a6b54607589f2..bfe95db560acf5944acc0673cb08ec2d834f7471 100644 (file)
@@ -27,7 +27,7 @@ RCSID("$Id$")
 #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;
 
@@ -141,13 +141,13 @@ void unlang_switch_init(void)
                           &(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
                           });
 }
index a8da67fa09fbd3d574262aac098aacd43153aa6a..cca9e1e7ad3fa9facc2cd2bd1f30898a0b046a05 100644 (file)
@@ -261,7 +261,7 @@ void unlang_timeout_init(void)
                           &(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",
                           });
index a30536fe660f3b5663cbcbcb80b6ce7a8046e3e3..6b2fe61966bc3ad6a30e3c76af8c0dba81c89b05 100644 (file)
@@ -129,6 +129,6 @@ void unlang_transaction_init(void)
                                .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
                           });
 }
index 434fc14ffc6fc50064d63b9394de81246444547f..69d2ee5f85a5c97e8f256e6f675d40d8de628d23 100644 (file)
@@ -45,6 +45,6 @@ void unlang_try_init(void)
                           &(unlang_op_t){
                                .name = "try",
                                .interpret = unlang_try,
-                               .debug_braces = true
+                               .flag = UNLANG_OP_FLAG_DEBUG_BRACES
                           });
 }
index 1e8f345d27a7525ba042a3bf345fbb6be73da566..adf72fa8416b9338255beac8fe56834da28940f6 100644 (file)
@@ -209,6 +209,14 @@ typedef void (*unlang_dump_t)(request_t *request, unlang_stack_frame_t *frame);
 
 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
@@ -227,11 +235,7 @@ typedef struct {
        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
 
@@ -348,47 +352,148 @@ extern unlang_op_t unlang_ops[];
 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)
 {
@@ -518,9 +623,7 @@ static inline void frame_pop(request_t *request, unlang_stack_t *stack)
         *      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);
        }