#endif
UNLANG_TYPE_POLICY, //!< Policy section.
UNLANG_TYPE_XLAT_INLINE, //!< xlat statement, inline in "unlang"
- UNLANG_TYPE_RESUME, //!< where to resume something.
+ UNLANG_TYPE_MODULE_RESUME, //!< where to resume processing within a module.
UNLANG_TYPE_MAX
} unlang_type_t;
/** A node in a graph of #unlang_op_t (s) that we execute
*
- * The interpreter acts like a turing machine, with the nodes forming the tape and the
- * #unlang_action_t the instructions.
+ * The interpreter acts like a turing machine, with #unlang_t nodes forming the tape
+ * and the #unlang_action_t the instructions.
*
- * This is the parent 'class' for multiple unlang node specialisations.
+ * This is the parent 'class' for multiple #unlang_t node specialisations.
* The #unlang_t struct is listed first in the specialisation so that we can cast between
* parent/child classes without knowledge of the layout of the structures.
*
* required for resumption is satisfied, it also specifies the ctx for that function,
* which represents the internal state of the module at the time of yielding.
*
- * If you want normal coroutine behaviour... ctx is arbitrary, and could include a state enum,
+ * If you want normal coroutine behaviour... ctx is arbitrary and could include a state enum,
* in which case the function pointer could be the same as the function that yielded, and something
* like Duff's device could be used to jump back to the yield point.
*
* without being straightjacketed.
*/
typedef struct {
- unlang_module_call_t module; //!< Module call that returned #RLM_MODULE_YIELD.
- module_thread_instance_t *thread; //!< thread-local data for this module
- fr_unlang_resume_t callback; //!< Function the yielding module indicated should
- //!< be called when the request could be resumed.
- fr_unlang_action_t action_callback; //!< Function the yielding module indicated should
- //!< be called when the request is poked via an action
- void const *ctx; //!< Context data for the callback. Usually represents
- //!< the module's internal state at the time of yielding.
-} unlang_resumption_t;
+ unlang_module_call_t module; //!< Module call that returned #RLM_MODULE_YIELD.
+ //!< This field must be first, as it includes an
+ //!< #unlang_t field which must be at the start
+ //!< of every unlang_* structure.
+
+ module_thread_instance_t *thread; //!< thread-local data for this module.
+ fr_unlang_module_resume_t callback; //!< Function the yielding module indicated should
+ //!< be called when the request could be resumed.
+
+ fr_unlang_action_t action_callback; //!< Function the yielding module indicated should
+ //!< be called when the request is poked via an action
+ //!< may be removed in future.
+
+
+ void const *ctx; //!< Context data for the callback. Usually represents
+ //!< the module's internal state at the time of yielding.
+} unlang_module_resumption_t;
/** A naked xlat
*
char *xlat_name;
} unlang_xlat_inline_t;
+/** A module stack entry
+ *
+ * Represents a single module call.
+ */
typedef struct {
module_thread_instance_t *thread; //!< thread-local data for this module
} unlang_stack_entry_modcall_t;
/** Our interpreter stack, as distinct from the C stack
*
- * We don't call the modules recursively. Instead we iterate over a list of unlang_t and
+ * We don't call the modules recursively. Instead we iterate over a list of #unlang_t and
* and manage the call stack ourselves.
*
* After looking at various green thread implementations, it was decided that using the existing
typedef struct {
rlm_rcode_t result;
int priority;
- unlang_type_t unwind; //!< Unwind to this one if it exists.
+ unlang_type_t unwind; //!< Unwind to this one if it exists.
bool do_next_sibling;
bool was_if;
bool if_taken;
bool resume;
bool top_frame;
- unlang_t *instruction;
-
+ unlang_t *instruction; //!< The unlang node we're evaluating.
+
+ /** Stack frame specialisations
+ *
+ * These store extra (mutable) state data, for the immutable (#unlang_t)
+ * instruction. Instructions can't be used to store data because they
+ * might be shared between multiple threads.
+ *
+ * Which stack_entry specialisation to use is determined by the
+ * instruction->type.
+ */
union {
- unlang_stack_entry_modcall_t modcall;
- unlang_stack_entry_foreach_t foreach;
- unlang_stack_entry_redundant_t redundant;
+ unlang_stack_entry_modcall_t modcall; //!< State for a modcall.
+ unlang_stack_entry_foreach_t foreach; //!< Foreach iterator state.
+ unlang_stack_entry_redundant_t redundant; //!< Redundant section state.
};
} unlang_stack_frame_t;
extern char const *const comp2str[];
-/* Simple conversions: unlang_module_call_t and unlang_group_t are subclasses of unlang_t,
- * so we often want to go back and forth between them. */
+/** @name Conversion functions for converting #unlang_t to its specialisations
+ *
+ * Simple conversions: #unlang_module_call_t and #unlang_group_t are subclasses of #unlang_t,
+ * so we often want to go back and forth between them.
+ *
+ * @{
+ */
static inline unlang_module_call_t *unlang_generic_to_module_call(unlang_t *p)
{
rad_assert(p->type == UNLANG_TYPE_MODULE_CALL);
return (unlang_t *)p;
}
-static inline unlang_resumption_t *unlang_generic_to_resumption(unlang_t *p)
+static inline unlang_module_resumption_t *unlang_generic_to_module_resumption(unlang_t *p)
{
- rad_assert(p->type == UNLANG_TYPE_RESUME);
- return talloc_get_type_abort(p, unlang_resumption_t);
+ rad_assert(p->type == UNLANG_TYPE_MODULE_RESUME);
+ return talloc_get_type_abort(p, unlang_module_resumption_t);
}
-static inline unlang_t *unlang_resumption_to_generic(unlang_resumption_t *p)
+static inline unlang_t *unlang_module_resumption_to_generic(unlang_module_resumption_t *p)
{
return (unlang_t *)p;
}
+/* @} **/
#ifdef __cplusplus
}
*/
static void safe_lock(module_instance_t *instance)
{
- if (instance->mutex)
- pthread_mutex_lock(instance->mutex);
+ if (instance->mutex) pthread_mutex_lock(instance->mutex);
}
/*
/*
* Recursively collect active callers. Slow, but correct.
*/
-static uint64_t collect_active_callers(unlang_t *instruction)
+static uint64_t unlang_active_callers(unlang_t *instruction)
{
uint64_t active_callers;
unlang_t *child;
for (child = g->children;
child != NULL;
child = child->next) {
- active_callers += collect_active_callers(child);
+ active_callers += unlang_active_callers(child);
}
break;
}
unlang_t *child = frame->redundant.child;
if (child->type != UNLANG_TYPE_MODULE_CALL) {
- active_callers = collect_active_callers(child);
+ active_callers = unlang_active_callers(child);
RDEBUG3("load-balance child %d sub-section has %" PRIu64 " active", num, active_callers);
} else {
*/
unlang_push(stack, frame->redundant.child, frame->result, false);
frame->resume = true;
+
return UNLANG_ACTION_PUSHED_CHILD;
}
-
static unlang_action_t unlang_group(REQUEST *request, unlang_stack_t *stack,
UNUSED rlm_rcode_t *result, UNUSED int *priority)
{
}
unlang_push(stack, g->children, frame->result, true);
+
return UNLANG_ACTION_PUSHED_CHILD;
}
unlang_stack_frame_t *frame = &stack->frame[stack->depth];
unlang_t *instruction = frame->instruction;
unlang_t *this, *found, *null_case;
- unlang_group_t *g, *h;
+ unlang_group_t *g, *h;
fr_cond_t cond;
fr_value_box_t data;
vp_map_t map;
request->module = sp->module_instance->name;
frame->modcall.thread->total_calls++;
+ /*
+ * Lock is noop unless instance->mutex is set.
+ */
safe_lock(sp->module_instance);
request->rcode = sp->method(sp->module_instance->data, frame->modcall.thread->data, request);
safe_unlock(sp->module_instance);
* Is now marked as "stop" when it wasn't before, we must have been blocked.
*/
if (request->master_state == REQUEST_STOP_PROCESSING) {
- RWARN("Module %s became unblocked for request %" PRIu64 "", sp->module_instance->module->name, request->number);
+ RWARN("Module %s became unblocked for request %" PRIu64 "",
+ sp->module_instance->module->name, request->number);
return UNLANG_ACTION_STOP_PROCESSING;
}
*presult = request->rcode;
RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
fr_int2str(mod_rcode_table, *presult, "<invalid>"));
+
return UNLANG_ACTION_CALCULATE_RESULT;
}
return unlang_group(request, stack, presult, priority);
}
-static unlang_action_t unlang_resumption(REQUEST *request, unlang_stack_t *stack,
- rlm_rcode_t *presult, int *priority)
+static unlang_action_t unlang_module_resumption(REQUEST *request, unlang_stack_t *stack,
+ rlm_rcode_t *presult, int *priority)
{
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
- unlang_t *instruction = frame->instruction;
- unlang_resumption_t *mr = unlang_generic_to_resumption(instruction);
- unlang_module_call_t *sp;
- void *mutable;
+ unlang_stack_frame_t *frame = &stack->frame[stack->depth];
+ unlang_t *instruction = frame->instruction;
+ unlang_module_resumption_t *mr = unlang_generic_to_module_resumption(instruction);
+ unlang_module_call_t *sp;
+ void *mutable;
sp = &mr->module;
memcpy(&mutable, &mr->ctx, sizeof(mutable));
request->module = sp->module_instance->name;
+ /*
+ * Lock is noop unless instance->mutex is set.
+ */
safe_lock(sp->module_instance);
*presult = mr->callback(request, mr->module.module_instance->data, mr->thread->data, mutable);
safe_unlock(sp->module_instance);
* Is now marked as "stop" when it wasn't before, we must have been blocked.
*/
if (request->master_state == REQUEST_STOP_PROCESSING) {
- RWARN("Module %s became unblocked for request %" PRIu64 "", sp->module_instance->module->name, request->number);
+ RWARN("Module %s became unblocked for request %" PRIu64 "",
+ sp->module_instance->module->name, request->number);
return UNLANG_ACTION_STOP_PROCESSING;
}
*presult = request->rcode;
RDEBUG2("%s (%s)", instruction->name ? instruction->name : "",
fr_int2str(mod_rcode_table, *presult, "<invalid>"));
+
return UNLANG_ACTION_CALCULATE_RESULT;
}
.func = unlang_xlat_inline,
.debug_braces = false
},
- [UNLANG_TYPE_RESUME] = {
- .name = "resume",
- .func = unlang_resumption,
+ [UNLANG_TYPE_MODULE_RESUME] = {
+ .name = "module-call-resume",
+ .func = unlang_module_resumption,
.debug_braces = false
},
[UNLANG_TYPE_MAX] = { NULL, NULL, false }
case UNLANG_ACTION_CALCULATE_RESULT:
if (result == RLM_MODULE_YIELD) {
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+ rad_assert(frame->instruction->type == UNLANG_TYPE_MODULE_RESUME);
frame->resume = true;
RDEBUG4("** [%i] %s - exited (yield)", stack->depth, __FUNCTION__);
return RLM_MODULE_YIELD;
fr_event_timer_t *ev; //!< Event in this worker's event heap.
} unlang_event_t;
+/** Frees an unlang event, removing it from the request's event loop
+ *
+ * @param[in] ev The event to free.
+ *
+ * @return 0
+ */
static int _unlang_event_free(unlang_event_t *ev)
{
if (ev->ev) {
/** Set a timeout for the request.
*
* Used when a module needs wait for an event. Typically the callback is set, and then the
- * module returns unlang_yield().
+ * module returns unlang_module_yield().
*
* @note The callback is automatically removed on unlang_resumable().
*
/** Set a callback for the request.
*
* Used when a module needs to read from an FD. Typically the callback is set, and then the
- * module returns unlang_yield().
+ * module returns unlang_module_yield().
*
* @note The callback is automatically removed on unlang_resumable().
*
return 0;
}
-/** Delete a previously set timeout callback.
+/** Delete a previously set timeout callback
*
* param[in] request the request
* param[in] ctx a local context for the callback
return 0;
}
-/** Delete a previously set file descriptor callback.
+/** Delete a previously set file descriptor callback
*
* param[in] request the request
* param[in] fd the file descriptor
return 0;
}
+static void _unlang_timer_hook(UNUSED fr_event_list_t *el, UNUSED struct timeval *now, void *ctx)
+{
+ REQUEST *request = talloc_get_type_abort(ctx, REQUEST);
+#ifdef DEBUG_STATE_MACHINE
+ fr_state_action_t action = FR_ACTION_TIMER;
+#endif
+
+ TRACE_STATE_MACHINE;
+
+ request->process(request, FR_ACTION_TIMER);
+}
+
+/** Delay processing of a request for a period
+ *
+ * Adds a timer event to resume processing the module after a specified period has elapsed.
+ *
+ * @param[in] request The current request.
+ * @param[in] delay processing by.
+ * @param[in] process The function to call when the delay expires.
+ * @return
+ * - 0 on success.
+ * - <0 on error.
+ */
+int unlang_delay(REQUEST *request, struct timeval *delay, fr_request_process_t process)
+{
+ struct timeval when;
+
+ fr_timeval_add(&when, &request->reply->timestamp, delay);
+
+ RDEBUG2("Waiting for %d.%06d seconds",
+ (int) delay->tv_sec, (int) delay->tv_usec);
+
+ if (fr_event_timer_insert(request->el, _unlang_timer_hook, request, &when, &request->ev) < 0) {
+ RDEBUG("Failed inserting delay event: %s", fr_strerror());
+ return -1;
+ }
+
+ request->process = process;
+
+ return 0;
+}
+
/** Mark a request as resumable.
*
* It's not called "unlang_resume", because it doesn't actually
* resume the request, it just schedules it for resumption.
*
- * @note that this schedules the request for resumption. It does not
- * immediately start running the request.
+ * @note that this schedules the request for resumption. It does not immediately
+ * start running the request.
*
* @param[in] request The current request.
*/
fr_heap_insert(request->backlog, request);
}
-/** Signal a request which an action.
+/** Send a signal (usually stop) to a request
*
* This is typically called via an "async" action, i.e. an action
* outside of the normal processing of the request.
*/
void unlang_action(REQUEST *request, fr_state_action_t action)
{
- unlang_stack_frame_t *frame;
- unlang_stack_t *stack = request->stack;
- unlang_resumption_t *mr;
- void *mutable;
+ unlang_stack_frame_t *frame;
+ unlang_stack_t *stack = request->stack;
+ unlang_module_resumption_t *mr;
+ void *mutable;
rad_assert(stack->depth > 0);
frame = &stack->frame[stack->depth];
- rad_assert(frame->instruction->type == UNLANG_TYPE_RESUME);
+ rad_assert(frame->instruction->type == UNLANG_TYPE_MODULE_RESUME);
- mr = unlang_generic_to_resumption(frame->instruction);
+ mr = unlang_generic_to_module_resumption(frame->instruction);
if (!mr->action_callback) return;
memcpy(&mutable, &mr->ctx, sizeof(mutable));
mr->action_callback(request, mr->module.module_instance->data, mr->thread, mutable, action);
}
-/** Yield a request
+/** Yield a request back to the interpreter from within a module
+ *
+ * This passes control of the request back to the unlang interpreter, setting
+ * callbacks to execute when the request is 'signalled' asynchronously, or whatever
+ * timer or I/O event the module was waiting for occurs.
+ *
+ * @note The module function which calls #unlang_module_yield should return control
+ * of the C stack to the unlang interpreter immediately after calling #unlang_module_yield.
+ * A common pattern is to use ``return unlang_module_yield(...)``.
*
* @param[in] request The current request.
* @param[in] callback to call on unlang_resumable().
* @param[in] ctx to pass to the callbacks.
* @return always returns RLM_MODULE_YIELD.
*/
-rlm_rcode_t unlang_yield(REQUEST *request, fr_unlang_resume_t callback,
- fr_unlang_action_t action_callback, void const *ctx)
+rlm_rcode_t unlang_module_yield(REQUEST *request, fr_unlang_module_resume_t callback,
+ fr_unlang_action_t action_callback, void const *ctx)
{
- unlang_stack_frame_t *frame;
- unlang_stack_t *stack = request->stack;
- unlang_resumption_t *mr;
- unlang_module_call_t *sp;
+ unlang_stack_frame_t *frame;
+ unlang_stack_t *stack = request->stack;
+ unlang_module_resumption_t *mr;
+ unlang_module_call_t *sp;
rad_assert(stack->depth > 0);
frame = &stack->frame[stack->depth];
rad_assert((frame->instruction->type == UNLANG_TYPE_MODULE_CALL) ||
- (frame->instruction->type == UNLANG_TYPE_RESUME));
+ (frame->instruction->type == UNLANG_TYPE_MODULE_RESUME));
sp = unlang_generic_to_module_call(frame->instruction);
- mr = talloc(request, unlang_resumption_t);
+ mr = talloc(request, unlang_module_resumption_t);
rad_assert(mr != NULL);
memcpy(&mr->module, frame->instruction, sizeof(mr->module));
mr->thread = frame->modcall.thread;
- mr->module.self.type = UNLANG_TYPE_RESUME;
+ mr->module.self.type = UNLANG_TYPE_MODULE_RESUME;
mr->callback = callback;
mr->action_callback = action_callback;
mr->thread = module_thread_instance_find(sp->module_instance);
mr->ctx = ctx;
- frame->instruction = unlang_resumption_to_generic(mr);
+ /*
+ * Replaces the current MODULE_CALL stack frame with a
+ * MODULE_RESUME frame.
+ */
+ frame->instruction = unlang_module_resumption_to_generic(mr);
return RLM_MODULE_YIELD;
}
-
-static void unlang_timer_hook(UNUSED fr_event_list_t *el, UNUSED struct timeval *now, void *ctx)
-{
- REQUEST *request = talloc_get_type_abort(ctx, REQUEST);
-#ifdef DEBUG_STATE_MACHINE
- fr_state_action_t action = FR_ACTION_TIMER;
-#endif
-
- TRACE_STATE_MACHINE;
-
- request->process(request, FR_ACTION_TIMER);
-}
-
-/** Delay processing of a request for a time
- *
- * @param[in] request The current request.
- * @param[in] delay processing by.
- * @param[in] process The function to call when the delay expires.
- * @return
- * - 0 on success.
- * - <0 on error.
- */
-int unlang_delay(REQUEST *request, struct timeval *delay, fr_request_process_t process)
-{
- struct timeval when;
-
- fr_timeval_add(&when, &request->reply->timestamp, delay);
-
- RDEBUG2("Waiting for %d.%06d seconds",
- (int) delay->tv_sec, (int) delay->tv_usec);
-
- if (fr_event_timer_insert(request->el, unlang_timer_hook, request, &when, &request->ev) < 0) {
- RDEBUG("Failed inserting delay event: %s", fr_strerror());
- return -1;
- }
-
- request->process = process;
- return 0;
-}