Skip to content

Commit

Permalink
add unlang_xlat_yield_to_retry()
Browse files Browse the repository at this point in the history
which mirrors the functionality of unlang_module_yield_to_retry()
  • Loading branch information
alandekok committed Dec 27, 2024
1 parent d283e98 commit 3f54339
Show file tree
Hide file tree
Showing 2 changed files with 167 additions and 0 deletions.
147 changes: 147 additions & 0 deletions src/lib/unlang/xlat.c
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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.
Expand Down
20 changes: 20 additions & 0 deletions src/lib/unlang/xlat.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@ RCSIDH(xlat_h, "$Id$")
extern "C" {
#endif

#include <freeradius-devel/util/retry.h>

/*
* Forward declarations
*/
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
*/
Expand Down

0 comments on commit 3f54339

Please sign in to comment.