From 3f5433979df2c65fb4a646a6bc6459798cb7a4e6 Mon Sep 17 00:00:00 2001 From: "Alan T. DeKok" Date: Fri, 27 Dec 2024 13:35:49 -0500 Subject: [PATCH] add unlang_xlat_yield_to_retry() which mirrors the functionality of unlang_module_yield_to_retry() --- src/lib/unlang/xlat.c | 147 ++++++++++++++++++++++++++++++++++++++++++ src/lib/unlang/xlat.h | 20 ++++++ 2 files changed, 167 insertions(+) diff --git a/src/lib/unlang/xlat.c b/src/lib/unlang/xlat.c index b85edbafb1d0..d03c559b1d0f 100644 --- a/src/lib/unlang/xlat.c +++ b/src/lib/unlang/xlat.c @@ -76,6 +76,18 @@ typedef struct { fr_event_timer_t const *ev; //!< Event in this worker's event heap. } unlang_xlat_event_t; +typedef struct { + request_t *request; + xlat_inst_t *inst; //!< xlat instance data. + xlat_thread_inst_t *thread; //!< Thread specific xlat instance. + + fr_unlang_xlat_retry_t retry_cb; //!< callback to run on timeout + void *rctx; //!< rctx data to pass to timeout callback + + fr_event_timer_t const *ev; //!< retry timer just for this xlat + fr_retry_t retry; //!< retry timers, etc. +} unlang_xlat_retry_t; + /** Frees an unlang event, removing it from the request's event loop * * @param[in] ev The event to free. @@ -579,6 +591,141 @@ xlat_action_t unlang_xlat_yield(request_t *request, return XLAT_ACTION_YIELD; } +/** Frees an unlang event, removing it from the request's event loop + * + * @param[in] ev The event to free. + * + * @return 0 + */ +static int _unlang_xlat_retry_free(unlang_xlat_retry_t *ev) +{ + if (ev->ev) (void) fr_event_timer_delete(&(ev->ev)); + + return 0; +} + +/** Call the callback registered for a timeout event + * + * @param[in] el the event timer was inserted into. + * @param[in] now The current time, as held by the event_list. + * @param[in] uctx unlang_module_event_t structure holding callbacks. + * + */ +static void unlang_xlat_event_retry_handler(UNUSED fr_event_list_t *el, fr_time_t now, void *uctx) +{ + unlang_xlat_retry_t *ev = talloc_get_type_abort(uctx, unlang_xlat_retry_t); + request_t *request = ev->request; + + switch (fr_retry_next(&ev->retry, now)) { + case FR_RETRY_CONTINUE: + /* + * Call the module retry handler, with the state of the retry. On MRD / MRC, the + * module is made runnable again, and the "resume" function is called. + */ + ev->retry_cb(XLAT_CTX(ev->inst->data, + ev->thread->data, + ev->thread->mctx, NULL, + UNCONST(void *, ev->rctx)), + ev->request, &ev->retry); + + /* + * Reset the timer. + */ + if (fr_event_timer_at(ev, unlang_interpret_event_list(request), &ev->ev, ev->retry.next, + unlang_xlat_event_retry_handler, request) < 0) { + RPEDEBUG("Failed inserting event"); + talloc_free(ev); + unlang_interpret_mark_runnable(request); + } + return; + + case FR_RETRY_MRD: + RDEBUG("Reached max_rtx_duration (%pVs > %pVs) - sending timeout", + fr_box_time_delta(fr_time_sub(now, ev->retry.start)), fr_box_time_delta(ev->retry.config->mrd)); + break; + + case FR_RETRY_MRC: + RDEBUG("Reached max_rtx_count %u- sending timeout", + ev->retry.config->mrc); + break; + } + + /* + * Run the retry handler on MRD / MRC, too. + */ + ev->retry_cb(XLAT_CTX(ev->inst->data, + ev->thread->data, + ev->thread->mctx, NULL, + UNCONST(void *, ev->rctx)), + ev->request, &ev->retry); + + /* + * On final timeout, always mark the request as runnable. + */ + talloc_free(ev); + unlang_interpret_mark_runnable(request); +} + + +/** Yield a request back to the interpreter, with retries + * + * This passes control of the request back to the unlang interpreter, setting + * callbacks to execute when the request is 'signalled' asynchronously, or when + * the retry timer hits. + * + * @note The module function which calls #unlang_module_yield_to_retry should return control + * of the C stack to the unlang interpreter immediately after calling #unlang_module_yield_to_retry. + * A common pattern is to use ``return unlang_module_yield_to_retry(...)``. + * + * @param[in] request The current request. + * @param[in] resume Called on unlang_interpret_mark_runnable(). + * @param[in] retry Called on when a retry timer hits + * @param[in] signal Called on unlang_action(). + * @param[in] sigmask Set of signals to block. + * @param[in] rctx to pass to the callbacks. + * @param[in] retry_cfg to set up the retries + * @return + * - XLAT_ACTION_YIELD on success + * - XLAT_ACTION_FAIL on failure + */ +xlat_action_t unlang_xlat_yield_to_retry(request_t *request, xlat_func_t resume, fr_unlang_xlat_retry_t retry, + xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx, + fr_retry_config_t const *retry_cfg) +{ + unlang_stack_t *stack = request->stack; + unlang_stack_frame_t *frame = &stack->frame[stack->depth]; + unlang_xlat_retry_t *ev; + unlang_frame_state_xlat_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_xlat_t); + + fr_assert(stack->depth > 0); + fr_assert(frame->instruction->type == UNLANG_TYPE_XLAT); + + if (!state->event_ctx) MEM(state->event_ctx = talloc_zero(state, bool)); + + ev = talloc_zero(state->event_ctx, unlang_xlat_retry_t); + if (unlikely(!ev)) return XLAT_ACTION_FAIL; + + ev->request = request; + fr_assert(state->exp->type == XLAT_FUNC); + ev->inst = state->exp->call.inst; + ev->thread = xlat_thread_instance_find(state->exp); + ev->retry_cb = retry; + ev->rctx = rctx; + + fr_retry_init(&ev->retry, fr_time(), retry_cfg); + + if (fr_event_timer_at(request, unlang_interpret_event_list(request), + &ev->ev, ev->retry.next, unlang_xlat_event_retry_handler, ev) < 0) { + RPEDEBUG("Failed inserting event"); + talloc_free(ev); + return XLAT_ACTION_FAIL; + } + + talloc_set_destructor(ev, _unlang_xlat_retry_free); + + return unlang_xlat_yield(request, resume, signal, sigmask, rctx); +} + /** Evaluate a "pure" (or not impure) xlat * * @param[in] ctx To allocate value boxes and values in. diff --git a/src/lib/unlang/xlat.h b/src/lib/unlang/xlat.h index 273818188c22..2af31000f4b8 100644 --- a/src/lib/unlang/xlat.h +++ b/src/lib/unlang/xlat.h @@ -29,6 +29,8 @@ RCSIDH(xlat_h, "$Id$") extern "C" { #endif +#include + /* * Forward declarations */ @@ -180,6 +182,20 @@ typedef struct { */ typedef void (*fr_unlang_xlat_timeout_t)(xlat_ctx_t const *xctx, request_t *request, fr_time_t fired); +/** A callback when the the timeout occurs + * + * Used when a xlat needs wait for an event. + * Typically the callback is set, and then the xlat returns unlang_xlat_yield(). + * + * @note The callback is automatically removed on unlang_interpret_mark_runnable(), i.e. if an event + * on a registered FD occurs before the timeout event fires. + * + * @param[in] xctx xlat calling ctx. Contains all instance data. + * @param[in] request the request. + * @param[in] retry retry status. "now" is in retry->updated + */ +typedef void (*fr_unlang_xlat_retry_t)(xlat_ctx_t const *xctx, request_t *request, fr_retry_t const *retry); + /** A callback when the FD is ready for reading * * Used when a xlat needs to read from an FD. Typically the callback is set, and then the @@ -501,6 +517,10 @@ xlat_action_t unlang_xlat_yield(request_t *request, xlat_func_t callback, xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx); +xlat_action_t unlang_xlat_yield_to_retry(request_t *request, xlat_func_t resume, fr_unlang_xlat_retry_t retry, + xlat_func_signal_t signal, fr_signal_t sigmask, void *rctx, + fr_retry_config_t const *retry_cfg); + /* * xlat_builtin.c */