Fix signalling in parallel. Both timeouts triggered on the parent side, and timeouts triggered on the child side now work.
Use relative timers for the retry keyword so synthetic time works correctly, same with the synchronous interpreter.
`max_request_time = 30`, so child requests will be cancelled 30
seconds after the parent request was received.
-When it is necessary to have the child request execute for longer, the
-`Request-Lifetime` attribute can be set. This attribute sets the
-lifetime of the child request after it has been detached. This
-setting allows child requests to last longer than `max_request_time`
-seconds from when the parent request was received.
-
-The `Request-Lifetime` attribute sets the lifetime in seconds. The
-minimum lifetime is five (5) seconds. The maximum lifetime is 3600
-seconds. Only integer seconds can be set.
-
.Example
[source,unlang]
----
NAS-Port := parent.request.NAS-Port
Acct-Session-Id := parent.request.Acct-Session-Id
- control.Request-Lifetime := 60
-
detach
radius
}
ATTRIBUTE Outer-Realm-Name 1251 string
ATTRIBUTE Inner-Realm-Name 1252 string
-ATTRIBUTE Request-Lifetime 1253 integer
ATTRIBUTE Proto 1254 tlv
#
{
fr_worker_t *worker = talloc_get_type_abort(uctx, fr_worker_t);
+ RDEBUG4("%s - Request detaching", __FUNCTION__);
+
if (request_is_detachable(request)) {
/*
* End the time tracking... We don't track detached requests,
{
fr_worker_t *worker = uctx;
- RDEBUG3("Request marked as runnable");
+ RDEBUG4("%s - Request marked as runnable", __FUNCTION__);
fr_heap_insert(&worker->runnable, request);
}
*/
static void _worker_request_yield(request_t *request, UNUSED void *uctx)
{
- RDEBUG3("Request yielded");
+ RDEBUG4("%s - Request yielded", __FUNCTION__);
if (likely(!request_is_detached(request))) fr_time_tracking_yield(&request->async->tracking, fr_time());
}
*/
static void _worker_request_resume(request_t *request, UNUSED void *uctx)
{
- RDEBUG3("Request resuming");
+ RDEBUG4("%s - Request resuming", __FUNCTION__);
if (likely(!request_is_detached(request))) fr_time_tracking_resume(&request->async->tracking, fr_time());
}
{
fr_worker_t *worker = talloc_get_type_abort(uctx, fr_worker_t);
- RDEBUG3("Request priority changed");
+ RDEBUG4("%s - Request priority changed", __FUNCTION__);
/* Extract the request from the runnable queue _if_ it's in the runnable queue */
if (fr_heap_extract(&worker->runnable, request) < 0) return;
static int _request_local_free(request_t *request)
{
+ RDEBUG4("Local request freed (%p)", request);
+
/*
* Ensure anything that might reference the request is
* freed before it is.
* @copyright 2019 The FreeRADIUS server project
*/
+#include <freeradius-devel/server/virtual_servers.h>
#ifdef __cplusplus
extern "C" {
#endif
typedef struct virtual_server_s virtual_server_t;
+typedef struct virtual_server_compile_s virtual_server_compile_t;
#include <freeradius-devel/io/schedule.h>
#include <freeradius-devel/server/cf_parse.h>
/** Processing sections which are allowed in this virtual server.
*
*/
-typedef struct {
+struct virtual_server_compile_s {
section_name_t const *section; //!< Identifier for the section.
size_t offset; //!< where the CONF_SECTION pointer is written
bool dont_cache; //!< If true, the CONF_SECTION pointer won't be written
unlang_mod_actions_t const *actions; //!< Default actions for this section.
section_name_t const **methods; //!< list of auxilliary module methods which are allowed in
///< if the main name doesn't match.
-} virtual_server_compile_t;
+};
#define COMPILE_TERMINATOR { .section = NULL }
MEM(pair_prepend_request(&vp, attr_tls_packet_type) >= 0);
vp->vp_uint32 = enum_tls_packet_type_new_session->vb_uint32;
- if (unlang_subrequest_child_push(NULL, child,
- &(unlang_subrequest_session_t){
- .enable = true,
- .unique_ptr = child->parent
- },
- true, UNLANG_SUB_FRAME) < 0) {
+ if (unlang_subrequest_child_push(child, NULL, child->parent, true, UNLANG_SUB_FRAME) < 0) {
return UNLANG_ACTION_FAIL;
}
if (unlang_function_push(child, NULL, tls_new_session_result, NULL, 0, UNLANG_SUB_FRAME, NULL) < 0) return UNLANG_ACTION_FAIL;
* Sets up a dispatch frame in the parent
* and a result processing frame in the child.
*/
- if (unlang_subrequest_child_push(NULL, child,
- &(unlang_subrequest_session_t){
- .enable = true,
- .unique_ptr = tls_session
- },
+ if (unlang_subrequest_child_push(child, NULL,
+ tls_session,
true, UNLANG_SUB_FRAME) < 0) {
return UNLANG_ACTION_FAIL;
}
call_env.c \
caller.c \
catch.c \
+ child_request.c \
compile.c \
condition.c \
detach.c \
parallel.c \
return.c \
subrequest.c \
- subrequest_child.c \
switch.c \
timeout.c \
tmpl.c \
static int _unlang_global_free(UNUSED void *uctx)
{
- unlang_subrequest_op_free();
TALLOC_FREE(unlang_ctx);
return 0;
--- /dev/null
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software
+ * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/child_request.c
+ * @brief Common child request management code.
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/state.h>
+#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/server/signal.h>
+
+#include "lib/server/rcode.h"
+#include "lib/util/talloc.h"
+#include "unlang_priv.h"
+#include "child_request_priv.h"
+
+typedef struct {
+ unlang_child_request_t *cr; //!< A pointer to memory in the parent's frame state
+ ///< allocated for this child to write results to.
+} unlang_frame_state_child_request_t;
+
+fr_table_num_ordered_t const unlang_child_states_table[] = {
+ { L("CANCELLED"), CHILD_CANCELLED },
+ { L("DETACH"), CHILD_DETACHED },
+ { L("DONE"), CHILD_DONE },
+ { L("EXITED"), CHILD_EXITED },
+ { L("INIT"), CHILD_INIT },
+ { L("RUNNABLE"), CHILD_RUNNABLE }
+};
+size_t unlang_child_states_table_len = NUM_ELEMENTS(unlang_child_states_table);
+
+/** Process a detach signal in the child
+ *
+ * This processes any detach signals the child receives
+ * The child doesn't actually do the detaching
+ */
+static void unlang_child_request_signal(request_t *request, UNUSED unlang_stack_frame_t *frame, fr_signal_t action)
+{
+ unlang_frame_state_child_request_t *state;
+ unlang_child_request_t *cr;
+
+ /*
+ * We're already detached so we don't
+ * need to notify the parent we're
+ * waking up, and we don't need to detach
+ * again...
+ */
+ if (request_is_detached(request)) return;
+
+ state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t);
+ cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */
+
+ /*
+ * Ignore signals which aren't detach, and ar
+ * and ignore the signal if we have no parent.
+ */
+ switch (action) {
+ case FR_SIGNAL_DETACH:
+ /*
+ * Place child's state back inside the parent
+ */
+ if (cr->config.session_unique_ptr) fr_state_store_in_parent(request,
+ cr->config.session_unique_ptr,
+ cr->num);
+
+ RDEBUG3("Detached - Removing subrequest from parent, and marking parent as runnable");
+
+ /*
+ * Indicate to the parent there's no longer a child
+ */
+ cr->state = CHILD_DETACHED;
+
+ /*
+ * Don't run the request-done frame, the request will have been
+ * detached by whatever signalled us, so we can't inform the parent
+ * when we exit.
+ */
+ repeatable_clear(frame);
+
+ /*
+ * Tell the parent to resume if all the request's siblings are done
+ */
+ if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) unlang_interpret_mark_runnable(request->parent);
+ break;
+
+ /*
+ * This frame is not cancellable, so FR_SIGNAL_CANCEL
+ * does nothing. If the child is cancelled in its
+ * entirety, then its stack will unwind up to this point
+ * and unlang_subrequest_child_done will mark the
+ * parent as runnable. We don't need to do anything here.
+ *
+ * We set the state for debugging purposes, and to reduce
+ * the amount of work we do in unlang_subrequest_child_done.
+ */
+ case FR_SIGNAL_CANCEL:
+ cr->state = CHILD_CANCELLED;
+ break;
+
+ default:
+ return;
+ }
+}
+
+/** When the child is done, tell the parent that we've exited.
+ *
+ * This is pushed as a frame at the top of the child's stack, so when
+ * the child is done executing, it runs this to inform the parent
+ * that its done.
+ */
+static unlang_action_t unlang_child_request_done(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+{
+ unlang_frame_state_child_request_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_child_request_t);
+ unlang_child_request_t *cr = state->cr; /* Can't use talloc_get_type_abort, may be an array element */
+
+ /*
+ * The repeat function for this frame should've
+ * been cleared, so this function should not run
+ * for detached requests.
+ */
+ fr_assert(!request_is_detached(request));
+
+ switch (cr->state) {
+ case CHILD_RUNNABLE:
+ /*
+ * Place child state back inside the parent
+ */
+ if (cr->config.session_unique_ptr) {
+ fr_state_store_in_parent(request,
+ cr->config.session_unique_ptr,
+ cr->num);
+ }
+
+ /*
+ * Record the child's result and the last
+ * priority. For parallel, this lets one
+ * child be used to control the rcode of
+ * the parallel keyword.
+ */
+ cr->result.rcode = *p_result;
+ cr->result.priority = frame->priority;
+ if (cr->result.p_result) *(cr->result.p_result) = cr->result.rcode;
+ break;
+
+ case CHILD_CANCELLED:
+ /*
+ * Child session state is no longer consistent
+ * after cancellation, so discard it.
+ */
+ if (cr->config.session_unique_ptr) {
+ fr_state_discard_child(request->parent, cr->config.session_unique_ptr, cr->num);
+ }
+ break;
+
+ default:
+ fr_assert_msg(0, "child %s resumed top frame with invalid state %s",
+ request->name,
+ fr_table_str_by_value(unlang_child_states_table, cr->state, "<INVALID>"));
+ }
+
+ cr->state = CHILD_EXITED;
+
+ /*
+ * Tell the parent to resume if all the request's siblings are done
+ */
+ if (!cr->sibling_count || (--(*cr->sibling_count) == 0)) {
+ RDEBUG3("All children have exited, marking parent %s as runnable", request->parent->name);
+ unlang_interpret_mark_runnable(request->parent);
+ } else {
+ RDEBUG3("Child %s exited, %u sibling(s) remaining",
+ request->name,
+ *cr->sibling_count);
+ }
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Push a resumption frame onto a child's stack
+ *
+ * Push a frame onto the stack of the child to inform the parent when it's complete.
+ * An additional frame is pushed onto the child's stack by the 'run' function which
+ * executes in the context of the parent.
+ *
+ * @param[in] cr state for this child request. This is a pointer
+ * to a structure in the parent's frame state.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+static int unlang_child_request_stack_init(unlang_child_request_t *cr)
+{
+ request_t *child = cr->request;
+ unlang_frame_state_child_request_t *state;
+ unlang_stack_frame_t *frame;
+
+ static unlang_t inform_parent = {
+ .type = UNLANG_TYPE_CHILD_REQUEST,
+ .name = "child-request",
+ .debug_name = "child-request-resume",
+ .actions = {
+ .actions = {
+ [RLM_MODULE_REJECT] = 0,
+ [RLM_MODULE_FAIL] = 0,
+ [RLM_MODULE_OK] = 0,
+ [RLM_MODULE_HANDLED] = 0,
+ [RLM_MODULE_INVALID] = 0,
+ [RLM_MODULE_DISALLOW] = 0,
+ [RLM_MODULE_NOTFOUND] = 0,
+ [RLM_MODULE_NOOP] = 0,
+ [RLM_MODULE_UPDATED] = 0
+ },
+ .retry = RETRY_INIT,
+ }
+ };
+
+ /* Sets up the frame for us to use immediately */
+ if (unlikely(unlang_interpret_push_instruction(child, &inform_parent, RLM_MODULE_NOOP, true) < 0)) {
+ return -1;
+ }
+
+ frame = frame_current(child);
+ state = frame->state;
+ state->cr = cr;
+ repeatable_set(frame); /* Run this on the way back up */
+
+ return 0;
+}
+
+/** Initialize a child request
+ *
+ * This initializes the child request result and configuration structure,
+ * and pushes a resumption frame onto the child's stack.
+ *
+ * @param[in] ctx Memory to use for any additional memory allocated
+ * to the unlang_child_request_t.
+ * @param[out] out Child request to initialize.
+ * @param[in] child The child request to initialize.
+ * @param[in] p_result Where to write out the rcode from the child.
+ * @param[in,out] sibling_count If non-null the bumber of siblings. This is incremented
+ * for each child created.
+ * @param[in] unique_session_ptr Unique session pointer for this child.
+ * If NULL session data won't be stored/restored for the child.
+ * @param[in] free_child Free the child when done?
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+int unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child,
+ rlm_rcode_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child)
+{
+ *out = (unlang_child_request_t){
+ .name = talloc_bstrdup(ctx, child->name),
+ .num = sibling_count ? (*sibling_count)++ : 0,
+ .request = child,
+ .state = CHILD_INIT,
+ .sibling_count = sibling_count,
+ .config = {
+ .session_unique_ptr = unique_session_ptr,
+ .free_child = free_child
+ },
+ .result = {
+ .p_result = p_result,
+ .rcode = RLM_MODULE_NOT_SET
+ }
+ };
+
+ return unlang_child_request_stack_init(out);
+}
+
+int unlang_child_request_op_init(void)
+{
+ unlang_register(UNLANG_TYPE_CHILD_REQUEST,
+ &(unlang_op_t){
+ .name = "child-request",
+ .interpret = unlang_child_request_done,
+ .signal = unlang_child_request_signal,
+ /*
+ * Frame can't be cancelled, because children need to
+ * write out status to the parent. If we don't do this,
+ * then all children must be detachable and must detach
+ * so they don't try and write out status to a "done"
+ * parent.
+ *
+ * It's easier to allow the child/parent relationship
+ * to end normally so that non-detachable requests are
+ * guaranteed the parent still exists.
+ */
+ .flag = UNLANG_OP_FLAG_NO_CANCEL,
+ .frame_state_size = sizeof(unlang_frame_state_child_request_t),
+ .frame_state_type = "unlang_frame_state_child_request_t"
+ });
+
+ return 0;
+}
--- /dev/null
+#pragma once
+/*
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2, or (at your option)
+ * any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License
+ * along with this program; if not, write to the Free Software Foundation,
+ * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ */
+
+/**
+ * $Id$
+ *
+ * @file unlang/subrequest_child_priv.h
+ *
+ * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
+ */
+#include <freeradius-devel/server/request.h>
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+
+/** Parallel child states
+ *
+ */
+typedef enum {
+ CHILD_INIT = 0, //!< Initial state, has no request allocated.
+
+ CHILD_RUNNABLE, //!< Running/runnable. The request is currently
+ ///< running, or is yielded but runnable.
+ CHILD_EXITED, //!< Child has run to completion, and is waiting
+ ///< to be reaped.
+ CHILD_DETACHED, //!< Child has detached, we can't signal it or
+ ///< communicate with it anymore. It will be freed
+ ///< by the interpreter when it completes.
+ CHILD_CANCELLED, //!< Child was cancelled. The request is still
+ ///< available, but the result from it should not
+ ///< be used and it should be free by the parent.
+ CHILD_DONE, //!< The child has been processed by the parent
+ ///< the request should still exist, and should be freed.
+
+ CHILD_FREED //!< The child has been freed. The request is no
+ ///< longer available, and should not be used.
+ ///< this is mostly for debugging purposes.
+} unlang_child_request_state_t;
+
+extern fr_table_num_ordered_t const unlang_child_states_table[];
+extern size_t unlang_child_states_table_len;
+
+/** Each child has a state, a number, a request, and a count of their siblings
+ */
+typedef struct {
+ char const *name; //!< Cache the request name.
+ int num; //!< The child number.
+ request_t *request; //!< Child request. The actual request the child will run.
+
+ unlang_child_request_state_t state; //!< State of the child.
+
+ unsigned int *sibling_count; //!< Number of siblings.
+ ///< as a child completes, it decrements this number.
+ ///< once it reaches zero, the parent is signalled
+ ///< to resume.
+
+ struct {
+ void const *session_unique_ptr; //!< Session unique ptr identifier. If not NULL, the child's
+ ///< session data will be stored in the parent, and be restored
+ ///< during a later request.
+ bool free_child;
+ } config;
+
+ struct {
+ rlm_rcode_t rcode; //!< Where to store the result of the child.
+ rlm_rcode_t *p_result; //!< If not NULL, write the rcode here too.
+ int priority; //!< Priority of the highest frame on the stack of the child.
+ } result;
+} unlang_child_request_t;
+
+int unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child,
+ rlm_rcode_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child);
+
+int unlang_child_request_op_init(void);
+
+#ifdef __cplusplus
+}
+#endif
for (c = instruction; c != NULL; c = c->next) {
switch (c->type) {
case UNLANG_TYPE_NULL:
- case UNLANG_TYPE_SUBREQUEST_CHILD:
+ case UNLANG_TYPE_CHILD_REQUEST:
case UNLANG_TYPE_MAX:
fr_assert(0);
break;
*
* @copyright 2006-2016 The FreeRADIUS server project
*/
+
RCSID("$Id$")
+#include <freeradius-devel/unlang/interpret.h>
+#include <freeradius-devel/util/timer.h>
#include <freeradius-devel/server/base.h>
#include <freeradius-devel/server/modpriv.h>
#include <freeradius-devel/unlang/xlat_func.h>
#include "interpret_priv.h"
#include "module_priv.h"
-#include "parallel_priv.h"
/** The default interpreter instance for this thread
* frame is cleaned up.
*/
if (fr_time_delta_ispos(instruction->actions.retry.mrd)) {
- retry->timeout = fr_time_add(fr_time(), instruction->actions.retry.mrd);
-
- if (fr_timer_at(retry, unlang_interpret_event_list(request)->tl, &retry->ev, retry->timeout,
+ if (fr_timer_in(retry, unlang_interpret_event_list(request)->tl, &retry->ev, instruction->actions.retry.mrd,
false, instruction_retry_handler, request) < 0) {
RPEDEBUG("Failed inserting retry event");
*result = RLM_MODULE_FAIL;
}
}
+/** Tell the interpreter to detach the request
+ *
+ * This function should not be called directly use unlang_interpret_signal(request, FR_SIGNAL_DETACH) instead.
+ * This will ensure all frames on the request's stack receive the detach signal.
+ */
static inline CC_HINT(always_inline)
void unlang_interpret_request_detach(request_t *request)
{
retry->state = FR_RETRY_CONTINUE;
retry->count = 1;
- retry->timeout = fr_time_add(fr_time(), timeout);
-
- return fr_timer_at(retry, unlang_interpret_event_list(request)->tl, &retry->ev, retry->timeout,
+ return fr_timer_in(retry, unlang_interpret_event_list(request)->tl, &retry->ev, timeout,
false, instruction_timeout_handler, request);
}
* This can be useful for doing stacked
* cancellations in policy.
*/
- vb->vb_time_delta = fr_time_sub(when, fr_time());
+ vb->vb_time_delta = fr_time_sub(when, unlang_interpret_event_list(request)->tl->time());
fr_dcursor_insert(out, vb);
}
"Request %s is marked as yielded at end of processing", request->name);
}
- RDEBUG3("Synchronous done external request");
+ RDEBUG3("Done synchronous external request");
}
/** Internal request (i.e. one generated by the interpreter) is now complete
* well.
*/
DEBUG4("Gathering events - %s", dont_wait_for_event ? "Will not wait" : "will wait");
- num_events = fr_event_corral(el, fr_time(), !dont_wait_for_event);
+ num_events = fr_event_corral(el, el->tl->time(), !dont_wait_for_event);
if (num_events < 0) {
RPERROR("fr_event_corral");
break;
*
* @copyright 2006-2019 The FreeRADIUS server project
*/
+
+
RCSID("$Id$")
-#include "function.h"
+#include <freeradius-devel/server/rcode.h>
+#include <freeradius-devel/server/signal.h>
+#include <freeradius-devel/server/state.h>
+#include <freeradius-devel/server/request.h>
+#include <freeradius-devel/util/table.h>
+
+#include "action.h"
+#include "interpret.h"
+#include "subrequest.h"
#include "interpret_priv.h"
-#include "module_priv.h"
+#include "unlang_priv.h"
#include "parallel_priv.h"
-#include "subrequest_priv.h"
-
+#include "child_request_priv.h"
/** Cancel a specific child
*
+ * For most states we just change the current state to CANCELLED. For the RUNNABLE state
+ * we need to signal the child to cancel itself.
+ *
+ * We don't free any requests here, we just mark them up so their rcodes are ignored when
+ * the parent is resumed, the parent then frees the child, once we're sure its done being
+ * run through the intepreter.
*/
-static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, int i)
+static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, unlang_child_request_t *cr)
{
- request_t *child = state->children[i].request;
+ request_t *child = cr->request;
request_t *request = child->parent; /* For debug messages */
+ unlang_child_request_state_t child_state = cr->state;
- switch (state->children[i].state) {
+ switch (cr->state) {
case CHILD_INIT:
- state->children[i].state = CHILD_CANCELLED;
- fr_assert(!state->children[i].request);
+ cr->state = CHILD_CANCELLED;
+ fr_assert(!cr->request);
break;
case CHILD_EXITED:
- state->children[i].state = CHILD_CANCELLED;
- TALLOC_FREE(state->children[i].request);
+ cr->state = CHILD_CANCELLED; /* Don't process its return code */
break;
case CHILD_RUNNABLE: /* Don't check runnable_id, may be yielded */
unlang_interpret_signal(child, FR_SIGNAL_CANCEL);
/*
- * Free it.
+ * We don't free the request here, we wait
+ * until it signals us that it's done.
*/
- TALLOC_FREE(state->children[i].request);
break;
case CHILD_DONE:
- fr_assert(!fr_heap_entry_inserted(child->runnable));
-
- /*
- * Completed children just get freed
- */
- TALLOC_FREE(state->children[i].request);
+ cr->state = CHILD_CANCELLED;
break;
- case CHILD_DETACHED:
+ case CHILD_DETACHED: /* Can't signal detached requests*/
+ fr_assert(!cr->request);
+ return;
+
case CHILD_CANCELLED:
+ break;
+
+ case CHILD_FREED:
return;
}
- RDEBUG3("parallel - child %s (%d/%d) CANCELLED",
- state->children[i].name,
- i + 1, state->num_children);
- state->children[i].state = CHILD_CANCELLED;
+ RDEBUG3("parallel - child %s (%d/%d) CANCELLED, previously %s",
+ cr->name, cr->num, state->num_children,
+ fr_table_str_by_value(unlang_child_states_table, child_state, "<INVALID>"));
}
-#if 0
-/** Cancel all the child's siblings
+/** Send a signal from parent request to all of it's children
*
- * Siblings will be excluded from final result calculation for the parallel section.
*/
-static void unlang_parallel_cancel_siblings(request_t *request)
+static void unlang_parallel_signal(UNUSED request_t *request,
+ unlang_stack_frame_t *frame, fr_signal_t action)
{
- unlang_stack_t *stack = request->parent->stack;
- unlang_stack_frame_t *frame = &stack->frame[stack->depth];
unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
- int i;
-
- for (i = 0; i < state->num_children; i++) {
- if (state->children[i].request == request) continue; /* Don't cancel this one */
-
- unlang_parallel_cancel_child(state, i);
- }
-}
-#endif
-
-/** Signal handler to deal with UNLANG_ACTION_DETACH
- *
- * When a request detaches we need
- */
-static void unlang_parallel_child_signal(request_t *request, UNUSED fr_signal_t action, void *uctx)
-{
- unlang_parallel_child_t *child = uctx;
- unlang_stack_frame_t *frame;
- unlang_parallel_state_t *state;
-
- frame = frame_current(request->parent);
- state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
- RDEBUG3("parallel - child %s (%d/%d) DETACHED",
- request->name,
- child->num + 1, state->num_children);
-
- child->state = CHILD_DETACHED;
- child->request = NULL;
- state->num_complete++;
+ unsigned int i;
/*
- * All children exited, resume the parent
+ * Signal any runnable children to get them to exit
*/
- if (state->num_complete == state->num_children) {
- RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
- unlang_interpret_mark_runnable(request->parent);
- }
-
- return;
-}
+ if (action == FR_SIGNAL_CANCEL) {
+ for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, &state->children[i]);
-/** When the chld is done, tell the parent that we've exited.
- *
- */
-static unlang_action_t unlang_parallel_child_done(UNUSED rlm_rcode_t *p_result, UNUSED int *p_priority, request_t *request, void *uctx)
-{
- unlang_parallel_child_t *child = uctx;
+ return;
+ }
/*
- * If we have a parent, then we're running synchronously
- * with it. Tell the parent that we've exited, and it
- * can continue.
- *
- * Otherwise we're a detached child, and we don't tell
- * the parent anything. Because we have that kind of
- * relationship.
+ * Signal all of the runnable/running children.
*/
- if (child->state == CHILD_RUNNABLE) {
- /*
- * Reach into the parent to get the unlang_parallel_state_t
- * for the whole parallel block.
- */
- unlang_stack_frame_t *frame = frame_current(request->parent);
- unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
- RDEBUG3("parallel - child %s (%d/%d) EXITED",
- request->name,
- child->num + 1, state->num_children);
-
- child->state = CHILD_EXITED;
- state->num_complete++;
+ for (i = 0; i < state->num_children; i++) {
+ if (state->children[i].state != CHILD_RUNNABLE) continue;
- /*
- * All children exited, resume the parent
- */
- if (state->num_complete == state->num_children) {
- RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
- unlang_interpret_mark_runnable(request->parent);
- }
+ unlang_interpret_signal(state->children[i].request, action);
}
-
- /*
- * Don't change frame->result, it's the result of the child.
- */
- return UNLANG_ACTION_CALCULATE_RESULT;
}
+
static unlang_action_t unlang_parallel_resume(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
-
- int i, priority;
+ unsigned int i;
+ int priority;
rlm_rcode_t result;
+ fr_assert(state->num_runnable == 0);
+
for (i = 0; i < state->num_children; i++) {
- if (!state->children[i].request) continue;
+ if (state->children[i].state != CHILD_EXITED) continue;
- fr_assert(state->children[i].state == CHILD_EXITED);
REQUEST_VERIFY(state->children[i].request);
RDEBUG3("parallel - child %s (%d/%d) DONE",
for (i = 0; i < state->num_children; i++) {
if (!state->children[i].request) continue;
- fr_assert(!fr_heap_entry_inserted(state->children[i].request->runnable));
- TALLOC_FREE(state->children[i].request);
+ fr_assert(!unlang_request_is_scheduled(state->children[i].request));
+
+ unlang_subrequest_detach_and_free(&state->children[i].request);
+
+ state->children[i].state = CHILD_FREED;
}
*p_result = state->result;
return UNLANG_ACTION_CALCULATE_RESULT;
}
-/** Run one or more sub-sections from the parallel section.
- *
- */
-static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
+static unlang_action_t unlang_parallel(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
- unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
- request_t *child;
- int i;
+ unlang_t const *instruction;
+ unlang_group_t *g;
+ unlang_parallel_t *gext;
+ unlang_parallel_state_t *state;
+
+ int i;
+
+ g = unlang_generic_to_group(frame->instruction);
+ if (!g->num_children) {
+ *p_result = RLM_MODULE_NOOP;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+
+ gext = unlang_group_to_parallel(g);
/*
- * If the children should be created detached, we return
- * "noop". This function then creates the children,
- * detaches them, and returns.
+ * Allocate an array for the children.
*/
- if (state->detach) {
- state->priority = 0;
- state->result = RLM_MODULE_NOOP;
+ MEM(frame->state = state = _talloc_zero_pooled_object(request,
+ sizeof(unlang_parallel_state_t) +
+ (sizeof(state->children[0]) * g->num_children),
+ "unlang_parallel_state_t",
+ g->num_children,
+ (talloc_array_length(request->name) * 2)));
+ if (!state) {
+ *p_result = RLM_MODULE_FAIL;
+ return UNLANG_ACTION_CALCULATE_RESULT;
}
+ (void) talloc_set_type(state, unlang_parallel_state_t);
+ state->result = RLM_MODULE_FAIL;
+ state->priority = -1; /* as-yet unset */
+ state->detach = gext->detach;
+ state->clone = gext->clone;
+ state->num_children = g->num_children;
+
/*
- * Loop over all the children.
- *
- * We always service the parallel section from top to
- * bottom, and we always service all of it.
+ * Initialize all of the children.
*/
- for (i = 0; i < state->num_children; i++) {
- fr_assert(state->children[i].instruction != NULL);
+ for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
+ request_t *child;
+
child = unlang_io_subrequest_alloc(request,
request->proto_dict, state->detach);
child->packet->code = request->packet->code;
if (state->clone) {
/*
- * Note that we do NOT copy the
- * Session-State list! That
- * contains state information for
- * the parent.
- */
+ * Note that we do NOT copy the
+ * Session-State list! That
+ * contains state information for
+ * the parent.
+ */
if ((fr_pair_list_copy(child->request_ctx,
&child->request_pairs,
&request->request_pairs) < 0) ||
(fr_pair_list_copy(child->control_ctx,
&child->control_pairs,
&request->control_pairs) < 0)) {
- REDEBUG("failed copying lists to clone");
+ REDEBUG("failed copying lists to child");
error:
+
/*
- * Detached children which have
- * already been created are
- * allowed to continue.
+ * Remove all previously
+ * spawned children.
*/
- if (!state->detach) {
- /*
- * Remove the current child
- *
- * Should also free detached children
- */
- unlang_interpret_request_done(child);
-
- /*
- * Remove all previously
- * spawned children.
- */
- for (--i; i >= 0; i--) unlang_interpret_request_done(child);
+ for (--i; i >= 0; i--) {
+ unlang_subrequest_detach_and_free(&state->children[i].request);
+ state->children[i].state = CHILD_FREED;
}
RETURN_MODULE_FAIL;
}
/*
- * Child starts detached, the parent knows
- * and can exit immediately once all
- * the children are initialised.
+ * Initialise our frame state, and push the first
+ * instruction onto the child's stack.
+ *
+ * This instruction will mark the parent as runnable
+ * when it is executed.
+ *
+ * We only do this if the requests aren't detached.
+ * If they are detached, this repeat function would
+ * be immediately disabled, so no point...
+ */
+ if (!state->detach) {
+ if (unlang_child_request_init(state, &state->children[i], child, NULL, &state->num_runnable,
+ frame_current(request)->instruction, false) < 0) goto error;
+ fr_assert(state->children[i].state == CHILD_INIT);
+ } else {
+ state->children[i].num = i;
+ state->children[i].request = child;
+ }
+
+ /*
+ * Push the first instruction for the child to run,
+ * which in case of parallel, is the child's
+ * subsection within the parallel block.
*/
- if (state->detach) {
+ if (unlang_interpret_push(child,
+ instruction, RLM_MODULE_FAIL,
+ UNLANG_NEXT_STOP,
+ state->detach ? UNLANG_TOP_FRAME : UNLANG_SUB_FRAME) < 0) goto error;
+ }
+
+ /*
+ * Now we're sure all the children are initialised
+ * start them running.
+ */
+ if (state->detach) {
+ for (i = 0; i < (int)state->num_children; i++) {
if (RDEBUG_ENABLED3) {
request_t *parent = request;
- request = child;
+ request = state->children[i].request;
RDEBUG3("parallel - child %s (%d/%d) DETACHED",
request->name,
i + 1, state->num_children);
request = parent;
}
- state->children[i].state = CHILD_DETACHED;
+ /*
+ * Adds to the runnable queue
+ */
+ interpret_child_init(state->children[i].request);
/*
- * Detach the child, and insert
- * it into the backlog.
+ * Converts to a detached request
*/
- if ((unlang_subrequest_lifetime_set(child) < 0) || (request_detach(child) < 0)) {
- request = child;
- RPEDEBUG("Failed detaching request");
- talloc_free(child);
+ unlang_interpret_signal(state->children[i].request, FR_SIGNAL_DETACH);
+ }
- RETURN_MODULE_FAIL;
- }
/*
- * If the children don't start detached
- * push a function onto the stack to
- * notify the parent when the child is
- * done.
+ * We are now done, all the children are detached
+ * so we don't need to wait around for them to complete.
*/
- } else {
- if (unlang_function_push(child,
- NULL,
- unlang_parallel_child_done,
- unlang_parallel_child_signal,
- ~FR_SIGNAL_DETACH,
- UNLANG_TOP_FRAME,
- &state->children[i]) < 0) goto error;
-
- state->children[i].num = i;
- state->children[i].name = talloc_bstrdup(state, child->name);
- state->children[i].request = child;
- state->children[i].state = CHILD_RUNNABLE;
+ return UNLANG_ACTION_CALCULATE_RESULT;
+ }
+ for (i = 0; i < (int)state->num_children; i++) {
+ /*
+ * Ensure we restore the session state information
+ * into the child.
+ */
+ if (state->children[i].config.session_unique_ptr) {
+ fr_state_restore_to_child(state->children[i].request,
+ state->children[i].config.session_unique_ptr,
+ state->children[i].num);
}
- interpret_child_init(child);
/*
- * Push the first instruction for
- * the child to run.
+ * Ensures the child is setup correctly and adds
+ * it into the runnable queue of whatever owns
+ * the interpreter.
*/
- if (unlang_interpret_push(child,
- state->children[i].instruction, RLM_MODULE_FAIL,
- UNLANG_NEXT_STOP,
- state->detach ? UNLANG_TOP_FRAME : UNLANG_SUB_FRAME) < 0) goto error;
+ interpret_child_init(state->children[i].request);
+ state->children[i].state = CHILD_RUNNABLE;
}
/*
- * If all children start detached,
- * then we're done.
- */
- if (state->detach) return UNLANG_ACTION_CALCULATE_RESULT;
-
- /*
- * Don't call this function again when
- * the parent resumes, instead call
- * a function to process the results
+ * Don't call this function again when the parent resumes,
+ * instead call a function to process the results
* of the children.
*/
frame_repeat(frame, unlang_parallel_resume);
/*
* Yield to the children
*
- * They scamper off to play on their
- * own when they're all done, the last
- * one tells the parent, so it can resume,
- * and gather up all the results.
+ * They scamper off to play on their own when they're all done,
+ * the last one tells the parent, so it can resume,
+ * and gather up the results, and mercilessly reap the children.
*/
return UNLANG_ACTION_YIELD;
}
-/** Send a signal from parent request to all of it's children
- *
- */
-static void unlang_parallel_signal(UNUSED request_t *request,
- unlang_stack_frame_t *frame, fr_signal_t action)
-{
- unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
- int i;
-
- if (action == FR_SIGNAL_CANCEL) {
- for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, i);
-
- return;
- }
-
- /*
- * Signal all of the runnable/running children.
- */
- for (i = 0; i < state->num_children; i++) {
- if (state->children[i].state != CHILD_RUNNABLE) continue;
-
- unlang_interpret_signal(state->children[i].request, action);
- }
-}
-
-static unlang_action_t unlang_parallel(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_t const *instruction;
- unlang_group_t *g;
- unlang_parallel_t *gext;
- unlang_parallel_state_t *state;
-
- int i;
-
- g = unlang_generic_to_group(frame->instruction);
- if (!g->num_children) {
- *p_result = RLM_MODULE_NOOP;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- gext = unlang_group_to_parallel(g);
-
- /*
- * Allocate an array for the children.
- */
- MEM(frame->state = state = _talloc_zero_pooled_object(request,
- sizeof(unlang_parallel_state_t) +
- (sizeof(state->children[0]) * g->num_children),
- "unlang_parallel_state_t",
- g->num_children,
- (talloc_array_length(request->name) * 2)));
- if (!state) {
- *p_result = RLM_MODULE_FAIL;
- return UNLANG_ACTION_CALCULATE_RESULT;
- }
-
- (void) talloc_set_type(state, unlang_parallel_state_t);
- state->result = RLM_MODULE_FAIL;
- state->priority = -1; /* as-yet unset */
- state->detach = gext->detach;
- state->clone = gext->clone;
- state->num_children = g->num_children;
-
- /*
- * Initialize all of the children.
- */
- for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
- state->children[i].state = CHILD_INIT;
- state->children[i].instruction = instruction;
- }
-
- frame->process = unlang_parallel_process;
- return unlang_parallel_process(p_result, request, frame);
-}
-
void unlang_parallel_init(void)
{
unlang_register(UNLANG_TYPE_PARALLEL,
.name = "parallel",
.interpret = unlang_parallel,
.signal = unlang_parallel_signal,
- .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET
+ .flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_NO_CANCEL
});
}
*
* @copyright 2006-2019 The FreeRADIUS server project
*/
+#include "child_request_priv.h"
#include "unlang_priv.h"
#ifdef __cplusplus
extern "C" {
#endif
-/** Parallel child states
- *
- */
-typedef enum {
- CHILD_INIT = 0, //!< Initial state.
- CHILD_RUNNABLE, //!< Running/runnable.
- CHILD_EXITED, //!< Child has exited
- CHILD_DETACHED, //!< Child has detached.
- CHILD_CANCELLED, //!< Child was cancelled.
- CHILD_DONE //!< The child has completed.
-} unlang_parallel_child_state_t;
-
-/** Each parallel child has a state, and an associated request
- *
- */
-typedef struct {
- int num; //!< The child number.
-
- unlang_parallel_child_state_t state; //!< State of the child.
- request_t *request; //!< Child request.
- char *name; //!< Cache the request name.
- unlang_t const *instruction; //!< broken out of g->children
-} unlang_parallel_child_t;
-
typedef struct {
rlm_rcode_t result;
int priority;
- int num_children; //!< How many children are executing.
- int num_complete; //!< How many children are complete.
+ unsigned int num_children; //!< How many children are executing.
+ unsigned int num_runnable; //!< How many children are complete.
bool detach; //!< are we creating the child detached
bool clone; //!< are the children cloned
- unlang_parallel_child_t children[]; //!< Array of children.
+ unlang_t *instruction; //!< The instruction the children should
+ ///< start executing.
+
+ unlang_child_request_t children[]; //!< Array of children.
} unlang_parallel_state_t;
typedef struct {
*
* @copyright 2006-2019 The FreeRADIUS server project
*/
+
RCSID("$Id$")
#include <freeradius-devel/server/state.h>
#include <freeradius-devel/server/tmpl_dcursor.h>
+#include <freeradius-devel/server/request.h>
#include <freeradius-devel/unlang/action.h>
#include "unlang_priv.h"
#include "interpret_priv.h"
#include "subrequest_priv.h"
-#include "subrequest_child_priv.h"
+#include "child_request_priv.h"
/** Send a signal from parent request to subrequest
*
*/
-static void unlang_subrequest_signal_child(UNUSED request_t *request, unlang_stack_frame_t *frame,
- fr_signal_t action)
+static void unlang_subrequest_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- request_t *child = talloc_get_type_abort(state->child, request_t);
+ unlang_child_request_t *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+ request_t *child = talloc_get_type_abort(cr->request, request_t);
+
+ switch (cr->state) {
+ case CHILD_DETACHED:
+ RDEBUG3("subrequest detached during its execution - Not sending signal to child");
+ return;
+
+ case CHILD_CANCELLED:
+ RDEBUG3("subrequest is cancelled - Not sending signal to child");
+ return;
+
+ default:
+ break;
+ }
/*
* Parent should never receive a detach
unlang_stack_frame_t *frame)
{
unlang_group_t *g = unlang_generic_to_group(frame->instruction);
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state,
- unlang_frame_state_subrequest_t);
- request_t *child = state->child;
+ unlang_child_request_t *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+ request_t *child = cr->request;
unlang_subrequest_t *gext;
/*
* Child detached
*/
- if (!state->child) {
+ if (cr->state == CHILD_DETACHED) {
RDEBUG3("subrequest detached during its execution - Not updating rcode or reply attributes");
/*
return UNLANG_ACTION_EXECUTE_NEXT;
}
- RDEBUG3("subrequest complete");
+ RDEBUG3("subrequest completeed with rcode %s",
+ fr_table_str_by_value(mod_rcode_table, cr->result.rcode, "<invalid>"));
+
+ *p_result = cr->result.rcode;
+ frame->priority = cr->result.priority;
/*
* If there's a no destination tmpl, we're done.
return UNLANG_ACTION_CALCULATE_RESULT;
}
-static unlang_action_t unlang_subrequest_parent_init(rlm_rcode_t *p_result, request_t *request,
- unlang_stack_frame_t *frame)
+/** Allocates a new subrequest and initialises it
+ *
+ */
+static unlang_action_t unlang_subrequest_init(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- request_t *child;
- rlm_rcode_t rcode;
- fr_pair_t *vp;
+ unlang_child_request_t *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+ request_t *child;
+ fr_pair_t *vp;
+
+ unlang_group_t *g;
+ unlang_subrequest_t *gext;
- unlang_group_t *g;
- unlang_subrequest_t *gext;
+ /*
+ * This should only be set for manually pushed subrequests
+ */
+ fr_assert(!cr->config.free_child);
/*
* Initialize the state
}
gext = unlang_group_to_subrequest(g);
- child = state->child = unlang_io_subrequest_alloc(request, gext->dict, UNLANG_DETACHABLE);
+ child = unlang_io_subrequest_alloc(request, gext->dict, UNLANG_DETACHABLE);
if (!child) {
fail:
- rcode = RLM_MODULE_FAIL;
-
- /*
- * Pass the result back to the module
- * that created the subrequest, or
- * use it to modify the current section
- * rcode.
- */
- if (state->p_result) *state->p_result = rcode;
-
- *p_result = rcode;
+ *p_result = cr->result.rcode = RLM_MODULE_FAIL;
+ if (cr->result.p_result) *cr->result.p_result = *p_result;
return UNLANG_ACTION_CALCULATE_RESULT;
}
/*
/*
* Setup the child so it'll inform us when
* it resumes, or if it detaches.
+ *
+ * frame->instruction should be consistent
+ * as it's allocated by the unlang compiler.
*/
- if (unlang_subrequest_child_push_resume(child, state) < 0) goto fail;
+ if (unlang_child_request_init(cr, cr, child, NULL, NULL, frame->instruction, false) < 0) goto fail;
/*
* Push the first instruction the child's
if (unlang_interpret_push(child, g->children, frame->result,
UNLANG_NEXT_SIBLING, UNLANG_SUB_FRAME) < 0) goto fail;
- state->p_result = p_result;
- state->detachable = true;
-
/*
- * Store/restore session information in the subrequest
- * keyed off the exact subrequest keyword.
+ * Finally, setup the function that will be
+ * called when the child indicates the
+ * parent should be resumed.
*/
- state->session.enable = true;
- state->session.unique_ptr = frame->instruction;
- state->session.unique_int = 0;
-
frame_repeat(frame, unlang_subrequest_parent_resume);
+ /*
+ * This is a common function, either pushed
+ * onto the parent's stack, or called directly
+ * from the subrequest instruction..
+ */
return unlang_subrequest_child_run(p_result, request, frame); /* returns UNLANG_ACTION_YIELD */
}
return unlang_io_subrequest_alloc(parent, namespace, UNLANG_NORMAL_CHILD);
}
+
+/** Function to run in the context of the parent on resumption
+ *
+ * @note Only executes if unlang_subrequest_child_push was called, not with the normal subrequest keyword.
+ */
+static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, UNUSED request_t *request,
+ unlang_stack_frame_t *frame)
+{
+ unlang_child_request_t *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+
+ if (cr->result.rcode == RLM_MODULE_NOT_SET) {
+ *p_result = cr->result.rcode = RLM_MODULE_NOOP;
+ }
+
+ if (cr->result.p_result) *cr->result.p_result = cr->result.rcode;
+ cr->result.priority = frame->priority;
+
+ /*
+ * We can free the child here as we're its parent
+ */
+ if (cr->config.free_child) {
+ if (request_is_detachable(cr->request)) {
+ unlang_subrequest_detach_and_free(&cr->request);
+ } else {
+ TALLOC_FREE(cr->request);
+ }
+ }
+
+ return UNLANG_ACTION_CALCULATE_RESULT;
+}
+
+/** Function called by the unlang interpreter, or manually to start the child running
+ *
+ * The reason why we do this on the unlang stack is so that _this_ frame
+ * is marked as resumable in the parent, not whatever frame was previously
+ * being processed by the interpreter when the parent was called.
+ *
+ * i.e. after calling unlang_subrequest_child_push, the code in the parent
+ * can call UNLANG_ACTION_PUSHED_CHILD, which will result in _this_ frame
+ * being executed, and _this_ frame can yield.
+ *
+ * @note Called from the parent to start a child running.
+ */
+unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
+ unlang_stack_frame_t *frame)
+{
+ unlang_child_request_t *cr = talloc_get_type_abort(frame->state, unlang_child_request_t);
+ request_t *child = cr->request;
+
+ /*
+ * No parent means this is a pre-detached child
+ * so the parent should continue executing.
+ */
+ if (!child || !child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
+
+
+ /*
+ * Ensure we restore the session state information
+ * into the child.
+ */
+ if (cr->config.session_unique_ptr) fr_state_restore_to_child(child,
+ cr->config.session_unique_ptr,
+ cr->num);
+ /*
+ * Ensures the child is setup correctly and adds
+ * it into the runnable queue of whatever owns
+ * the interpreter.
+ */
+ interpret_child_init(child);
+
+ /*
+ * This function is being called by something
+ * other than the subrequest keyword.
+ *
+ * Set a different resumption function that
+ * just writes the final rcode out.
+ */
+ if (frame->process == unlang_subrequest_child_run) {
+ frame_repeat(frame, unlang_subrequest_child_done);
+ }
+
+ cr->state = CHILD_RUNNABLE;
+
+ return UNLANG_ACTION_YIELD;
+}
+
+/** Push a pre-existing child back onto the stack as a subrequest
+ *
+ * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
+ * that calls it.
+ *
+ * After the child is no longer required it *MUST* be freed with #unlang_subrequest_detach_and_free.
+ * It's not enough to free it with talloc_free.
+ *
+ * This function should be called _before_ pushing any additional frames onto the child's
+ * stack for it to execute.
+ *
+ * The parent should return UNLANG_ACTION_PUSHED_CHILD, when it's done setting up the
+ * child request. It should NOT return UNLANG_ACTION_YIELD.
+ *
+ * @param[in] p_result Where to write the result of the subrequest.
+ * @param[in] child to push.
+ * @param[in] unique_session_ptr Unique identifier for child's session data.
+ * @param[in] free_child automatically free the child when it's finished executing.
+ * This is useful if extracting the result from the child is
+ * done using the child's stack, and so the parent never needs
+ * to access it.
+ * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return.
+ * Set to UNLANG_SUB_FRAME if the interprer should continue.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+
+int unlang_subrequest_child_push(request_t *child,
+ rlm_rcode_t *p_result, void const *unique_session_ptr, bool free_child, bool top_frame)
+{
+ unlang_child_request_t *cr;
+ unlang_stack_frame_t *frame;
+
+ static unlang_t subrequest_instruction = {
+ .type = UNLANG_TYPE_SUBREQUEST,
+ .name = "subrequest",
+ .debug_name = "subrequest",
+ .actions = {
+ .actions = {
+ [RLM_MODULE_REJECT] = 0,
+ [RLM_MODULE_FAIL] = 0,
+ [RLM_MODULE_OK] = 0,
+ [RLM_MODULE_HANDLED] = 0,
+ [RLM_MODULE_INVALID] = 0,
+ [RLM_MODULE_DISALLOW] = 0,
+ [RLM_MODULE_NOTFOUND] = 0,
+ [RLM_MODULE_NOOP] = 0,
+ [RLM_MODULE_UPDATED] = 0
+ },
+ .retry = RETRY_INIT,
+ }
+ };
+
+ fr_assert_msg(free_child || child->parent, "Child's request pointer must not be NULL when calling subrequest_child_push");
+
+ if (!fr_cond_assert_msg(stack_depth_current(child) == 0,
+ "Child stack depth must be 0 (not %d), when calling subrequest_child_push",
+ stack_depth_current(child))) return -1;
+
+ /*
+ * Push a new subrequest frame onto the stack
+ * of the parent.
+ *
+ * This allocates memory for the frame state
+ * which we fill in below.
+ *
+ * This frame executes once the subrequest has
+ * completed.
+ */
+ if (unlang_interpret_push(child->parent, &subrequest_instruction,
+ RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, top_frame) < 0) {
+ return -1;
+ }
+
+ frame = frame_current(child->parent);
+ frame->process = unlang_subrequest_child_run;
+
+ /*
+ * Setup the state for the subrequest
+ */
+ cr = talloc_get_type_abort(frame_current(child->parent)->state, unlang_child_request_t);
+
+ /*
+ * Initialise our frame state, and push the first
+ * instruction onto the child's stack.
+ *
+ * This instruction will mark the parent as runnable
+ * when it executed.
+ */
+ if (unlang_child_request_init(cr, cr, child, p_result, NULL, unique_session_ptr, free_child) < 0) return -1;
+
+ return 0;
+}
+
+/** Add a child request to the runnable queue
+ *
+ * @param[in] request to add to the runnable queue.
+ * @return
+ * - 0 on success.
+ * - -1 on failure.
+ */
+int unlang_subrequest_child_push_and_detach(request_t *request)
+{
+ /*
+ * Ensures the child is setup correctly and adds
+ * it into the runnable queue of whatever owns
+ * the interpreter.
+ */
+ interpret_child_init(request);
+
+ if (request_detach(request) < 0) {
+ RPEDEBUG("Failed detaching request");
+ return -1;
+ }
+
+ return 0;
+}
+
/** Initialise subrequest ops
*
*/
unlang_register(UNLANG_TYPE_SUBREQUEST,
&(unlang_op_t){
.name = "subrequest",
- .interpret = unlang_subrequest_parent_init,
- .signal = unlang_subrequest_signal_child,
+ .interpret = unlang_subrequest_init,
+ .signal = unlang_subrequest_signal,
/*
* Frame can't be cancelled, because children need to
* write out status to the parent. If we don't do this,
* guaranteed the parent still exists.
*/
.flag = UNLANG_OP_FLAG_DEBUG_BRACES | UNLANG_OP_FLAG_RCODE_SET | UNLANG_OP_FLAG_NO_CANCEL,
- .frame_state_size = sizeof(unlang_frame_state_subrequest_t),
- .frame_state_type = "unlang_frame_state_subrequest_t",
+ .frame_state_size = sizeof(unlang_child_request_t),
+ .frame_state_type = "unlang_child_request_t",
});
- if (unlang_subrequest_child_op_init() < 0) return -1;
+ if (unlang_child_request_op_init() < 0) return -1;
return 0;
}
-
-void unlang_subrequest_op_free(void)
-{
- unlang_subrequest_child_op_free();
-}
* @copyright 2019 The FreeRADIUS server project
* @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
*/
+
#ifdef __cplusplus
extern "C" {
#endif
void unlang_subrequest_detach_and_free(request_t **child);
-int unlang_subrequest_lifetime_set(request_t *request);
-
-int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
- unlang_subrequest_session_t const *session,
- bool free_child, bool top_frame);
+int unlang_subrequest_child_push(request_t *child,
+ rlm_rcode_t *p_result, void const *unique_session_ptr, bool free_child, bool top_frame);
int unlang_subrequest_child_push_and_detach(request_t *child);
+++ /dev/null
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2 of the License, or
- * (at your option) any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software
- * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
- */
-
-/**
- * $Id$
- *
- * @file unlang/subrequest.c
- * @brief Unlang "subrequest" and "detach" keyword evaluation.
- *
- * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- */
-#include <freeradius-devel/server/state.h>
-#include "interpret_priv.h"
-#include "subrequest_priv.h"
-#include "subrequest_child_priv.h"
-
-typedef struct {
- unlang_frame_state_subrequest_t *parent_state;
-} unlang_frame_state_subrequest_child_t;
-
-/** Holds a synthesised instruction that we insert into the parent request
- *
- */
-static unlang_subrequest_t *subrequest_instruction;
-
-static fr_dict_t const *dict_freeradius;
-
-extern fr_dict_autoload_t subrequest_dict[];
-fr_dict_autoload_t subrequest_dict[] = {
- { .out = &dict_freeradius, .proto = "freeradius" },
- { NULL }
-};
-
-static fr_dict_attr_t const *request_attr_request_lifetime;
-
-extern fr_dict_attr_autoload_t subrequest_dict_attr[];
-fr_dict_attr_autoload_t subrequest_dict_attr[] = {
- { .out = &request_attr_request_lifetime, .name = "Request-Lifetime", .type = FR_TYPE_UINT32, .dict = &dict_freeradius },
- { NULL }
-};
-
-
-static unlang_subrequest_t subrequest_child_instruction = {
- .group = {
- .self = {
- .type = UNLANG_TYPE_SUBREQUEST_CHILD,
- .name = "subrequest-child",
- .debug_name = "subrequest-child",
- .actions = {
- .actions = {
- [RLM_MODULE_REJECT] = 0,
- [RLM_MODULE_FAIL] = 0,
- [RLM_MODULE_OK] = 0,
- [RLM_MODULE_HANDLED] = 0,
- [RLM_MODULE_INVALID] = 0,
- [RLM_MODULE_DISALLOW] = 0,
- [RLM_MODULE_NOTFOUND] = 0,
- [RLM_MODULE_NOOP] = 0,
- [RLM_MODULE_UPDATED] = 0
- },
- .retry = RETRY_INIT,
- },
- }
- }
-};
-
-/** Event handler to free a detached child
- *
- */
-static void unlang_detached_max_request_time(UNUSED fr_timer_list_t *tl, UNUSED fr_time_t now, void *uctx)
-{
- request_t *request = talloc_get_type_abort(uctx, request_t);
-
- RDEBUG("Reached Request-Lifetime. Forcibly stopping request");
-
- unlang_interpret_signal(request, FR_SIGNAL_CANCEL); /* Request should now be freed */
-}
-
-/** Initialize a detached child
- *
- * Detach it from the parent, set up it's lifetime, and mark it as
- * runnable.
- */
-int unlang_subrequest_lifetime_set(request_t *request)
-{
- fr_pair_t *vp;
-
- /*
- * Set Request Lifetime
- */
- vp = fr_pair_find_by_da(&request->control_pairs, NULL, request_attr_request_lifetime);
- if (!vp || (vp->vp_uint32 > 0)) {
- fr_time_delta_t when = fr_time_delta_wrap(0);
- fr_timer_t **ev_p;
-
- if (!vp) {
- when = fr_time_delta_add(when, fr_time_delta_from_sec(30)); /* default to 30s if not set */
-
- } else if (vp->vp_uint32 > 3600) {
- RWDEBUG("Request-Timeout can be no more than 3600 seconds");
- when = fr_time_delta_add(when, fr_time_delta_from_sec(3600));
-
- } else if (vp->vp_uint32 < 5) {
- RWDEBUG("Request-Timeout can be no less than 5 seconds");
- when = fr_time_delta_add(when ,fr_time_delta_from_sec(5));
-
- } else {
- when = fr_time_delta_from_sec(vp->vp_uint32);
- }
-
- ev_p = talloc_size(request, sizeof(*ev_p));
- memset(ev_p, 0, sizeof(*ev_p));
-
- if (fr_timer_in(request, unlang_interpret_event_list(request)->tl, ev_p, when,
- false, unlang_detached_max_request_time, request) < 0) {
- talloc_free(ev_p);
- return -1;
- }
- }
-
- return 0;
-}
-
-/** Process a detach signal in the child
- *
- * This processes any detach signals the child receives
- * The child doesn't actually do the detaching
- */
-static void unlang_subrequest_child_signal(request_t *request, UNUSED unlang_stack_frame_t *frame, fr_signal_t action)
-{
- unlang_frame_state_subrequest_child_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_child_t);
- unlang_frame_state_subrequest_t *parent_state;
-
- /*
- * We're already detached so we don't
- * need to notify the parent we're
- * waking up, and we don't need to detach
- * again...
- */
- if (!request->parent) return;
-
- parent_state = talloc_get_type_abort(state->parent_state, unlang_frame_state_subrequest_t);
-
- /*
- * Ignore signals which aren't detach, and ar
- * and ignore the signal if we have no parent.
- */
- switch (action) {
- case FR_SIGNAL_DETACH:
- /*
- * Place child's state back inside the parent
- */
- if (parent_state->session.enable) fr_state_store_in_parent(request,
- parent_state->session.unique_ptr,
- parent_state->session.unique_int);
-
- if (!fr_cond_assert(unlang_subrequest_lifetime_set(request) == 0)) {
- REDEBUG("Child could not be detached");
- return;
- }
-
- RDEBUG3("Removing subrequest from parent, and marking parent as runnable");
-
- /*
- * Indicate to the parent there's no longer a child
- */
- parent_state->child = NULL;
-
- /*
- * Tell the parent to resume
- */
- unlang_interpret_mark_runnable(request->parent);
- break;
-
- /*
- * This frame is not cancellable, so FR_SIGNAL_CANCEL
- * does nothing. If the child is cancelled in its
- * entirety, then its stack will unwind up to this point
- * and unlang_subrequest_child_done will mark the
- * parent as runnable. We don't need to do anything here.
- */
- default:
- return;
- }
-}
-
-/** When the child is done, tell the parent that we've exited.
- *
- * This is pushed as a frame at the top of the child's stack, so when
- * the child is done executing, it runs this to inform the parent
- * that its done.
- */
-static unlang_action_t unlang_subrequest_child_done(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
-{
- unlang_frame_state_subrequest_child_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_child_t);
- unlang_frame_state_subrequest_t *parent_state;
-
- /*
- * Child was detached, nothing more to do.
- *
- * This frame was left on the stack when the child
- * detached. It's normally meant to trigger a
- * resume in the parent, but as there is no parent
- * it just becomes a noop and gets popped.
- *
- * This is cheaper/less complex then rooting it
- * out at detach time and unsetting the repeat
- * function.
- */
- if (!request->parent) return UNLANG_ACTION_CALCULATE_RESULT;
-
- /*
- * 'state' in this context, is the frame state
- * in the parent request, so we cannot examine it
- * until AFTER we've determined there is still
- * a parent, else the memory could've been freed.
- *
- * i.e. don't move the get_type_abort call onto
- * the same line as the state variable declaration.
- */
- parent_state = talloc_get_type_abort(state->parent_state, unlang_frame_state_subrequest_t);
-
- /*
- * Place child state back inside the parent
- */
- if (parent_state->session.enable) fr_state_store_in_parent(request,
- parent_state->session.unique_ptr,
- parent_state->session.unique_int);
- /*
- * Record the child's result
- */
- if (parent_state->p_result) *parent_state->p_result = *p_result;
-
- /*
- * Resume the parent
- */
- unlang_interpret_mark_runnable(request->parent);
-
- return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Push a resumption frame onto a child's stack
- *
- * This is necessary so that the child informs its parent when it's done/detached
- * and so that the child responds to detach signals.
- */
-int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *parent_state)
-{
- int ret;
- unlang_frame_state_subrequest_child_t *state;
- unlang_stack_frame_t *frame;
-
- /* Sets up the frame for us to use immediately */
- ret = unlang_interpret_push_instruction(child, &subrequest_child_instruction, RLM_MODULE_NOOP, true);
- if (ret < 0) return -1;
-
- frame = frame_current(child);
- state = frame->state;
- state->parent_state = parent_state;
- repeatable_set(frame); /* Run this on the way back up */
-
- return 0;
-}
-
-/** Function to run in the context of the parent on resumption
- *
- */
-static unlang_action_t unlang_subrequest_calculate_result(rlm_rcode_t *p_result, UNUSED request_t *request,
- unlang_stack_frame_t *frame)
-{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
-
- if (*p_result == RLM_MODULE_NOT_SET) *p_result = RLM_MODULE_NOOP;
-
- if (state->free_child) unlang_subrequest_detach_and_free(&state->child);
-
- return UNLANG_ACTION_CALCULATE_RESULT;
-}
-
-/** Function called by the unlang interpreter to start the child running
- *
- * The reason why we do this on the unlang stack is so that _this_ frame
- * is marked as resumable in the parent, not whatever frame was previously
- * being processed by the interpreter when the parent was called.
- *
- * i.e. after calling unlang_subrequest_child_push, the code in the parent
- * can call UNLANG_ACTION_PUSHED_CHILD, which will result in _this_ frame
- * being executed, and _this_ frame can yield.
- *
- * @note Called from the parent to start a child running.
- */
-unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
- unlang_stack_frame_t *frame)
-{
- unlang_frame_state_subrequest_t *state = talloc_get_type_abort(frame->state, unlang_frame_state_subrequest_t);
- request_t *child = state->child;
-
- /*
- * No parent means this is a pre-detached child
- * so the parent should continue executing.
- */
- if (!child || !child->parent) return UNLANG_ACTION_CALCULATE_RESULT;
-
-
- /*
- * Ensure we restore the session state information
- * into the child.
- */
- if (state->session.enable) fr_state_restore_to_child(child,
- state->session.unique_ptr,
- state->session.unique_int);
- /*
- * Ensures the child is setup correctly and adds
- * it into the runnable queue of whatever owns
- * the interpreter.
- */
- interpret_child_init(child);
-
- /*
- * Don't run this function again on resumption
- */
- if (frame->process == unlang_subrequest_child_run) frame->process = unlang_subrequest_calculate_result;
- repeatable_set(frame);
-
- return UNLANG_ACTION_YIELD;
-}
-
-/** Push a pre-existing child back onto the stack as a subrequest
- *
- * The child *MUST* have been allocated with unlang_io_subrequest_alloc, or something
- * that calls it.
- *
- * After the child is no longer required it *MUST* be freed with #unlang_subrequest_detach_and_free.
- * It's not enough to free it with talloc_free.
- *
- * This function should be called _before_ pushing any additional frames onto the child's
- * stack for it to execute.
- *
- * The parent should return UNLANG_ACTION_PUSHED_CHILD, when it's done setting up the
- * child request. It should NOT return UNLANG_ACTION_YIELD.
- *
- * @param[in] out Where to write the result of the subrequest.
- * @param[in] child to push.
- * @param[in] session control values. Whether we restore/store session info.
- * @param[in] free_child automatically free the child when it's finished executing.
- * This is useful if extracting the result from the child is
- * done using the child's stack, and so the parent never needs
- * to access it.
- * @param[in] top_frame Set to UNLANG_TOP_FRAME if the interpreter should return.
- * Set to UNLANG_SUB_FRAME if the interprer should continue.
- * @return
- * - 0 on success.
- * - -1 on failure.
- */
-int unlang_subrequest_child_push(rlm_rcode_t *out, request_t *child,
- unlang_subrequest_session_t const *session,
- bool free_child, bool top_frame)
-{
- unlang_frame_state_subrequest_t *state;
- unlang_stack_frame_t *frame;
-
- fr_assert_msg(child->parent, "Child's request pointer must not be NULL when calling subrequest_child_push");
-
- /*
- * Push a new subrequest frame onto the stack
- *
- * This allocates memory for the frame state
- * which we fill in below.
- */
- if (unlang_interpret_push(child->parent, &subrequest_instruction->group.self,
- RLM_MODULE_NOT_SET, UNLANG_NEXT_STOP, top_frame) < 0) {
- return -1;
- }
-
- frame = frame_current(child->parent);
- frame->process = unlang_subrequest_child_run;
-
- /*
- * Setup the state for the subrequest
- */
- state = talloc_get_type_abort(frame_current(child->parent)->state, unlang_frame_state_subrequest_t);
- state->p_result = out;
- state->child = child;
- state->session = *session;
- state->free_child = free_child;
-
- if (!fr_cond_assert_msg(stack_depth_current(child) == 0,
- "Child stack depth must be 0 (not %d), when calling subrequest_child_push",
- stack_depth_current(child))) return -1;
-
- /*
- * Push a resumption frame onto the stack
- * so the child calls its parent when it's
- * complete.
- */
- if (unlang_subrequest_child_push_resume(child, state) < 0) return -1;
-
- return 0;
-}
-
-int unlang_subrequest_child_push_and_detach(request_t *request)
-{
- /*
- * Ensures the child is setup correctly and adds
- * it into the runnable queue of whatever owns
- * the interpreter.
- */
- interpret_child_init(request);
-
- if ((unlang_subrequest_lifetime_set(request) < 0) || (request_detach(request) < 0)) {
- RPEDEBUG("Failed detaching request");
- return -1;
- }
-
- return 0;
-}
-
-int unlang_subrequest_child_op_init(void)
-{
- if (fr_dict_autoload(subrequest_dict) < 0) {
- PERROR("%s", __FUNCTION__);
- return -1;
- }
- if (fr_dict_attr_autoload(subrequest_dict_attr) < 0) {
- PERROR("%s", __FUNCTION__);
- return -1;
- }
-
- /*
- * Needs to be dynamically allocated
- * so that talloc_get_type works
- * correctly.
- */
- MEM(subrequest_instruction = talloc(NULL, unlang_subrequest_t));
- *subrequest_instruction = (unlang_subrequest_t){
- .group = {
- .self = {
- .type = UNLANG_TYPE_SUBREQUEST,
- .name = "subrequest",
- .debug_name = "subrequest",
- .actions = {
- .actions = {
- [RLM_MODULE_REJECT] = 0,
- [RLM_MODULE_FAIL] = 0,
- [RLM_MODULE_OK] = 0,
- [RLM_MODULE_HANDLED] = 0,
- [RLM_MODULE_INVALID] = 0,
- [RLM_MODULE_DISALLOW] = 0,
- [RLM_MODULE_NOTFOUND] = 0,
- [RLM_MODULE_NOOP] = 0,
- [RLM_MODULE_UPDATED] = 0
- },
- .retry = RETRY_INIT,
- },
- }
- }
- };
-
- unlang_register(UNLANG_TYPE_SUBREQUEST_CHILD,
- &(unlang_op_t){
- .name = "subrequest-child",
- .interpret = unlang_subrequest_child_done,
- .signal = unlang_subrequest_child_signal,
- /*
- * Frame can't be cancelled, because children need to
- * write out status to the parent. If we don't do this,
- * then all children must be detachable and must detach
- * so they don't try and write out status to a "done"
- * parent.
- *
- * It's easier to allow the child/parent relationship
- * to end normally so that non-detachable requests are
- * guaranteed the parent still exists.
- */
- .flag = UNLANG_OP_FLAG_NO_CANCEL,
- .frame_state_size = sizeof(unlang_frame_state_subrequest_child_t),
- .frame_state_type = "unlang_frame_state_subrequest_child_t"
- });
-
- return 0;
-}
-
-void unlang_subrequest_child_op_free(void)
-{
- fr_dict_autofree(subrequest_dict);
- talloc_free(subrequest_instruction);
-}
+++ /dev/null
-#pragma once
-/*
- * This program is free software; you can redistribute it and/or modify
- * it under the terms of the GNU General Public License as published by
- * the Free Software Foundation; either version 2, or (at your option)
- * any later version.
- *
- * This program is distributed in the hope that it will be useful,
- * but WITHOUT ANY WARRANTY; without even the implied warranty of
- * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- * GNU General Public License for more details.
- *
- * You should have received a copy of the GNU General Public License
- * along with this program; if not, write to the Free Software Foundation,
- * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
- */
-
-/**
- * $Id$
- *
- * @file unlang/subrequest_child_priv.h
- *
- * @copyright 2021 Arran Cudbard-Bell (a.cudbardb@freeradius.org)
- */
-#include <freeradius-devel/server/request.h>
-#include "subrequest_priv.h"
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-unlang_action_t unlang_subrequest_child_run(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame);
-
-int unlang_subrequest_child_push_resume(request_t *child, unlang_frame_state_subrequest_t *state);
-
-int unlang_subrequest_child_op_init(void);
-
-void unlang_subrequest_child_op_free(void);
-
-#ifdef __cplusplus
-}
-#endif
///< if the packet-type is static.
} unlang_subrequest_t;
-/** Parameters for initialising the subrequest (parent's frame state)
- *
- */
-typedef struct {
- rlm_rcode_t *p_result; //!< Where to store the result.
- request_t *child; //!< Pre-allocated child request.
- bool free_child; //!< Whether we should free the child after
- ///< it completes.
- bool detachable; //!< Whether the request can be detached.
- unlang_subrequest_session_t session; //!< Session configuration.
-} unlang_frame_state_subrequest_t;
-
/** Cast a group structure to the subrequest keyword extension
*
*/
return (unlang_group_t *)subrequest;
}
+unlang_action_t unlang_subrequest_child_run(UNUSED rlm_rcode_t *p_result, UNUSED request_t *request,
+ unlang_stack_frame_t *frame);
+
int unlang_subrequest_detach_child(request_t *request);
#ifdef __cplusplus
#include <freeradius-devel/server/cf_util.h> /* Need CONF_* definitions */
#include <freeradius-devel/server/map_proc.h>
#include <freeradius-devel/server/modpriv.h>
+#include <freeradius-devel/server/time_tracking.h>
#include <freeradius-devel/util/debug.h>
#include <freeradius-devel/unlang/base.h>
#include <freeradius-devel/io/listen.h>
UNLANG_TYPE_MAP, //!< Mapping section (like #UNLANG_TYPE_UPDATE, but uses
//!< values from a #map_proc_t call).
UNLANG_TYPE_SUBREQUEST, //!< create a child subrequest
- UNLANG_TYPE_SUBREQUEST_CHILD, //!< a frame at the top of a child's request stack used to signal the
+ UNLANG_TYPE_CHILD_REQUEST, //!< a frame at the top of a child's request stack used to signal the
///< parent when the child is complete.
UNLANG_TYPE_DETACH, //!< detach a child
UNLANG_TYPE_CALL, //!< call another virtual server
request_t *request;
int depth; //!< of this retry structure
fr_retry_state_t state;
- fr_time_t timeout;
uint32_t count;
fr_timer_t *ev;
} unlang_retry_t;
int unlang_subrequest_op_init(void);
-void unlang_subrequest_op_free(void);
-
void unlang_detach_init(void);
void unlang_switch_init(void);
* This must be done before pushing frames onto
* the child's stack.
*/
- if (unlang_subrequest_child_push(&eap_session->submodule_rcode, eap_session->subrequest,
- &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = eap_session },
+ if (unlang_subrequest_child_push(eap_session->subrequest, &eap_session->submodule_rcode,
+ eap_session,
false, UNLANG_SUB_FRAME) < 0) {
child_fail:
unlang_interpet_frame_discard(request); /* Ensure the yield frame doesn't stick around */
RDEBUG2("No tunnel username (SSL resumption?)");
}
- if (unlang_subrequest_child_push(&eap_session->submodule_rcode, child,
- &(unlang_subrequest_session_t){ .enable = true, .unique_ptr = child },
+ if (unlang_subrequest_child_push(child, &eap_session->submodule_rcode,
+ child,
false, UNLANG_SUB_FRAME) < 0) goto finish;
if (unlang_function_push(child, NULL, process_reply, NULL, 0,
UNLANG_SUB_FRAME, eap_session) != UNLANG_ACTION_PUSHED_CHILD) goto finish;
+#
+# PRE: subrequest
+#
parallel {
group {
parent.control += {
--- /dev/null
+#
+# PRE: parallel
+#
+try {
+ parallel {
+ timeout 1s {
+ %time.advance(2s)
+ parent.control.NAS-Port += 1
+ }
+ # Ensure a timeout in one child does not affect the other
+ group {
+ parent.control.NAS-Port += 1
+ ok
+ }
+ }
+}
+catch timeout {
+ if (!(%{control.NAS-Port[#]} == 1)) {
+ test_fail
+ }
+
+ success
+}
--- /dev/null
+#
+# PRE: parallel
+#
+try {
+ timeout 1s {
+ parallel {
+ # This child should get cancelled by the parent
+ group {
+ delay_10s
+ parent.control.NAS-Port += 1
+ ok
+ }
+ # This child should complete
+ group {
+ parent.control.NAS-Port += 1
+ # Trigger the parent's timeout section so we don't have to wait
+ %time.advance(2s)
+ ok
+ }
+ }
+ }
+}
+catch timeout {
+ if (!(%{control.NAS-Port[#]} == 1)) {
+ test_fail
+ }
+
+ success
+}
ok {
ok = 10
}
- noop {
+ updated {
fail = 1
}
}
if (ok) {
success
-} elsif (noop) {
+} else {
test_fail
}
--- /dev/null
+#
+# PRE: subrequest try timeout
+#
+try {
+ timeout 100ms {
+ subrequest ::Access-Request {
+ %time.advance(500ms) # Smoke test, see if things explode
+ parent.control.User-Name := 'bob'
+ success
+ }
+ }
+}
+catch timeout {
+ if (control.User-Name == 'bob') {
+ test_fail
+ } else {
+ success
+ }
+}