The FreeRADIUS server $Id: 15bac2a4c627c01d1aa2047687b3418955ac7f00 $
Loading...
Searching...
No Matches
parallel.c
Go to the documentation of this file.
1/*
2 * This program is free software; you can redistribute it and/or modify
3 * it under the terms of the GNU General Public License as published by
4 * the Free Software Foundation; either version 2 of the License, or
5 * (at your option) any later version.
6 *
7 * This program is distributed in the hope that it will be useful,
8 * but WITHOUT ANY WARRANTY; without even the implied warranty of
9 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10 * GNU General Public License for more details.
11 *
12 * You should have received a copy of the GNU General Public License
13 * along with this program; if not, write to the Free Software
14 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301, USA
15 */
16
17/**
18 * $Id: 807f66b0fcb33a0a33e3bb2a7b6ec51ebf39c98e $
19 *
20 * @file unlang/parallel.c
21 * @brief Implementation of the unlang "parallel" keyword.
22 *
23 * @copyright 2006-2019 The FreeRADIUS server project
24 */
25
26
27RCSID("$Id: 807f66b0fcb33a0a33e3bb2a7b6ec51ebf39c98e $")
28
29#include <freeradius-devel/server/rcode.h>
30#include <freeradius-devel/server/signal.h>
31#include <freeradius-devel/server/state.h>
32
33#include "action.h"
34#include "interpret.h"
35#include "mod_action.h"
36#include "subrequest.h"
37#include "interpret_priv.h"
38#include "unlang_priv.h"
39#include "parallel_priv.h"
40#include "child_request_priv.h"
41
42/** Cancel a specific child
43 *
44 * For most states we just change the current state to CANCELLED. For the RUNNABLE state
45 * we need to signal the child to cancel itself.
46 *
47 * We don't free any requests here, we just mark them up so their rcodes are ignored when
48 * the parent is resumed, the parent then frees the child, once we're sure its done being
49 * run through the interpreter.
50 */
51static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, unlang_child_request_t *cr)
52{
53 request_t *child;
54 request_t *request;
55 unlang_child_request_state_t child_state = cr->state;
56
57 switch (cr->state) {
58 case CHILD_INIT:
60 fr_assert(!cr->request);
61 return;
62
63 case CHILD_EXITED:
64 cr->state = CHILD_CANCELLED; /* Don't process its return code */
65 break;
66
67 case CHILD_RUNNABLE: /* Don't check runnable_id, may be yielded */
68 fr_assert(cr->request);
69
70 /*
71 * Signal the child to stop
72 *
73 * The signal function cleans up the request
74 * and signals anything that was tracking it
75 * that it's now complete.
76 */
77
78 child = cr->request;
79
81
82 /*
83 * We don't free the request here, we wait
84 * until it signals us that it's done.
85 */
86 break;
87
88 case CHILD_DONE:
90 break;
91
92 case CHILD_DETACHED: /* Can't signal detached requests*/
93 fr_assert(!cr->request);
94 return;
95
96 case CHILD_CANCELLED:
97 case CHILD_FREED:
98 return;
99 }
100
101 request = cr->request->parent;
102 RDEBUG3("parallel - child %s (%d/%d) CANCELLED, previously %s",
103 cr->name, cr->num, state->num_children,
104 fr_table_str_by_value(unlang_child_states_table, child_state, "<INVALID>"));
105}
106
107/** Send a signal from parent request to all of it's children
108 *
109 */
111 unlang_stack_frame_t *frame, fr_signal_t action)
112{
113 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
114 unsigned int i;
115
116 /*
117 * Signal any runnable children to get them to exit
118 */
119 if (action == FR_SIGNAL_CANCEL) {
120 for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, &state->children[i]);
121
122 /*
123 * If we're cancelled, then we fail, just to be safe.
124 */
126 return;
127 }
128
129 /*
130 * Signal all of the runnable/running children.
131 */
132 for (i = 0; i < state->num_children; i++) {
133 if (state->children[i].state != CHILD_RUNNABLE) continue;
134
135 unlang_interpret_signal(state->children[i].request, action);
136 }
137}
138
139
141{
142 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
143 unsigned int i;
144
145 fr_assert(state->num_runnable == 0);
146
147 for (i = 0; i < state->num_children; i++) {
148 unlang_child_request_t *cr = &state->children[i];
149
150 if (state->children[i].state != CHILD_EXITED) continue;
151
153
154 RDEBUG3("parallel - child %s (%d/%d) DONE",
155 state->children[i].name,
156 i + 1, state->num_children);
157
158 state->children[i].state = CHILD_DONE;
159
160 /*
161 * Over-ride "return" and "reject". A "return"
162 * in a child of a parallel just stops the child.
163 * It doesn't stop the parent.
164 */
165 if (cr->result.priority == MOD_ACTION_RETURN) {
167
168 } else if (cr->result.priority == MOD_ACTION_REJECT) {
170
171 } else {
174 }
175
176 /*
177 * Do priority over-ride.
178 */
179 if (cr->result.priority > state->result.priority) {
180 RDEBUG4("** [%i] %s - overwriting existing result (%s %s) from higher priority to (%s %s)",
181 stack_depth_current(request), __FUNCTION__,
186 state->result = cr->result;
187 }
188 }
189
190 /*
191 * Reap the children....
192 */
193 for (i = 0; i < state->num_children; i++) {
194 if (!state->children[i].request) continue;
195
197
199
200 state->children[i].state = CHILD_FREED;
201 }
202
203 *p_result = state->result;
205}
206
208{
209 unlang_t const *instruction;
211 unlang_parallel_t *gext;
213 int i;
214 size_t num_children;
215
217
218 num_children = unlang_list_num_elements(&g->children);
219 if (num_children == 0) RETURN_UNLANG_NOOP;
220
221 gext = unlang_group_to_parallel(g);
222
223 /*
224 * Allocate an array for the children.
225 */
226 MEM(frame->state = state = _talloc_zero_pooled_object(request,
228 (sizeof(state->children[0]) * num_children),
229 "unlang_parallel_state_t",
230 num_children,
231 (talloc_array_length(request->name) * 2)));
232 (void) talloc_set_type(state, unlang_parallel_state_t);
234 state->detach = gext->detach;
235 state->clone = gext->clone;
236 state->num_children = unlang_list_num_elements(&g->children);
237
238 /*
239 * Initialize all of the children.
240 */
241 for (i = 0, instruction = unlang_list_head(&g->children);
242 instruction != NULL;
243 i++, instruction = unlang_list_next(&g->children, instruction)) {
244 request_t *child;
245 unlang_result_t *child_result;
246
247 MEM(child = unlang_io_subrequest_alloc(request,
248 request->proto_dict, state->detach));
249 child->packet->code = request->packet->code;
250
251 RDEBUG3("parallel - child %s (%d/%d) INIT",
252 child->name,
253 i + 1, state->num_children);
254
255 if (state->clone) {
256 /*
257 * Note that we do NOT copy the
258 * Session-State list! That
259 * contains state information for
260 * the parent.
261 */
262 if ((fr_pair_list_copy(child->request_ctx,
263 &child->request_pairs,
264 &request->request_pairs) < 0) ||
265 (fr_pair_list_copy(child->reply_ctx,
266 &child->reply_pairs,
267 &request->reply_pairs) < 0) ||
268 (fr_pair_list_copy(child->control_ctx,
269 &child->control_pairs,
270 &request->control_pairs) < 0)) {
271 REDEBUG("failed copying lists to child");
272 error:
273 talloc_free(child);
274
275 /*
276 * Remove all previously
277 * spawned children.
278 */
279 for (--i; i >= 0; i--) {
281 state->children[i].state = CHILD_FREED;
282 }
283
284 return UNLANG_ACTION_FAIL;
285 }
286 }
287
288 /*
289 * Initialise our frame state, and push the first
290 * instruction onto the child's stack.
291 *
292 * This instruction will mark the parent as runnable
293 * when it is executed.
294 *
295 * We only do this if the requests aren't detached.
296 * If they are detached, this repeat function would
297 * be immediately disabled, so no point...
298 */
299 if (!state->detach) {
300 if (unlang_child_request_init(state, &state->children[i], child, NULL, &state->num_runnable,
301 frame_current(request)->instruction, false) < 0) goto error;
302 fr_assert(state->children[i].state == CHILD_INIT);
303 child_result = &state->children[i].result;
305
306 } else {
307 state->children[i].num = i;
308 state->children[i].request = child;
309 child_result = NULL;
310 }
311
312 /*
313 * Push the first instruction for the child to run,
314 * which in case of parallel, is the child's
315 * subsection within the parallel block.
316 */
317 if (unlang_interpret_push(child_result, child,
318 instruction,
320 UNLANG_NEXT_STOP) < 0) {
322 state->children[i].state = CHILD_FREED;
323 child = NULL;
324 goto error;
325 }
326 }
327
328 /*
329 * Now we're sure all the children are initialised
330 * start them running.
331 */
332 if (state->detach) {
333 for (i = 0; i < (int)state->num_children; i++) {
334 if (RDEBUG_ENABLED3) {
335 request_t *parent = request;
336
337 request = state->children[i].request;
338 RDEBUG3("parallel - child %s (%d/%d) DETACHED",
339 request->name,
340 i + 1, state->num_children);
341 request = parent;
342 }
343
344 /*
345 * Adds to the runnable queue
346 */
348
349 /*
350 * Converts to a detached request
351 */
353
354 state->children[i].request = NULL;
355 state->children[i].state = CHILD_DETACHED;
356 }
357
358 /*
359 * We are now done, all the children are detached
360 * so we don't need to wait around for them to complete.
361 */
363 }
364
365 for (i = 0; i < (int)state->num_children; i++) {
366 /*
367 * Ensure we restore the session state information
368 * into the child.
369 */
370 if (state->children[i].config.session_unique_ptr) {
372 state->children[i].config.session_unique_ptr,
373 state->children[i].num);
374 }
375
376 /*
377 * Ensures the child is setup correctly and adds
378 * it into the runnable queue of whatever owns
379 * the interpreter.
380 */
382 state->children[i].state = CHILD_RUNNABLE;
383 }
384
385 /*
386 * Don't call this function again when the parent resumes,
387 * instead call a function to process the results
388 * of the children.
389 */
391
392 /*
393 * Yield to the children
394 *
395 * They scamper off to play on their own when they're all done,
396 * the last one tells the parent, so it can resume,
397 * and gather up the results, and mercilessly reap the children.
398 */
399 return UNLANG_ACTION_YIELD;
400}
401
403{
405 unlang_t *c;
406 char const *name2;
407
409 unlang_parallel_t *gext;
410
411 bool clone = true;
412 bool detach = false;
413
414 if (!cf_item_next(cs, NULL)) return UNLANG_IGNORE;
415
416 /*
417 * Parallel sections can create empty child requests, if
418 * the admin demands it. Otherwise, the principle of
419 * least surprise is to copy the whole request, reply,
420 * and config items.
421 */
422 name2 = cf_section_name2(cs);
423 if (name2) {
424 if (strcmp(name2, "empty") == 0) {
425 clone = false;
426
427 } else if (strcmp(name2, "detach") == 0) {
428 detach = true;
429
430 } else {
431 cf_log_err(cs, "Invalid argument '%s'", name2);
432 cf_log_err(ci, DOC_KEYWORD_REF(parallel));
433 return NULL;
434 }
435
436 }
437
438 /*
439 * We can do "if" in parallel with other "if", but we
440 * cannot do "else" in parallel with "if".
441 */
443 return NULL;
444 }
445
447 if (!c) return NULL;
448
450 gext = unlang_group_to_parallel(g);
451 gext->clone = clone;
452 gext->detach = detach;
453
454 return c;
455}
456
458{
460 .name = "parallel",
461 .type = UNLANG_TYPE_PARALLEL,
463
464 .compile = unlang_compile_parallel,
465 .interpret = unlang_parallel,
466 .signal = unlang_parallel_signal,
467
468 .unlang_size = sizeof(unlang_parallel_t),
469 .unlang_name = "unlang_parallel_t"
470 });
471}
Unlang interpreter actions.
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ UNLANG_ACTION_FAIL
Encountered an unexpected error.
Definition action.h:36
@ UNLANG_ACTION_CALCULATE_RESULT
Calculate a new section rlm_rcode_t value.
Definition action.h:37
@ UNLANG_ACTION_YIELD
Temporarily pause execution until an event occurs.
Definition action.h:41
#define RCSID(id)
Definition build.h:506
#define UNUSED
Definition build.h:336
Common header for all CONF_* types.
Definition cf_priv.h:49
A section grouping multiple CONF_PAIR.
Definition cf_priv.h:101
char const * cf_section_name2(CONF_SECTION const *cs)
Return the second identifier of a CONF_SECTION.
Definition cf_util.c:1184
char const * cf_section_name1(CONF_SECTION const *cs)
Return the first identifier of a CONF_SECTION.
Definition cf_util.c:1170
CONF_SECTION * cf_item_to_section(CONF_ITEM const *ci)
Cast a CONF_ITEM to a CONF_SECTION.
Definition cf_util.c:683
#define cf_log_err(_cf, _fmt,...)
Definition cf_util.h:285
#define cf_item_next(_parent, _curr)
Definition cf_util.h:89
fr_table_num_ordered_t const unlang_child_states_table[]
int unlang_child_request_init(TALLOC_CTX *ctx, unlang_child_request_t *out, request_t *child, unlang_result_t *p_result, unsigned int *sibling_count, void const *unique_session_ptr, bool free_child)
Initialize a child request.
struct unlang_child_request_t::@100 config
request_t * request
Child request. The actual request the child will run.
unlang_result_t result
The result of the child request.
char const * name
Cache the request name.
unlang_child_request_state_t state
State of the child.
unlang_child_request_state_t
Parallel child states.
@ CHILD_RUNNABLE
Running/runnable.
@ CHILD_DETACHED
Child has detached, we can't signal it or communicate with it anymore.
@ CHILD_INIT
Initial state, has no request allocated.
@ CHILD_EXITED
Child has run to completion, and is waiting to be reaped.
@ CHILD_CANCELLED
Child was cancelled.
@ CHILD_FREED
The child has been freed.
@ CHILD_DONE
The child has been processed by the parent the request should still exist, and should be freed.
int num
The child number.
Each child has a state, a number, a request, and a count of their siblings.
unlang_t * unlang_compile_section(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_SECTION *cs, unlang_type_t type)
Definition compile.c:1523
bool unlang_compile_limit_subsection(CONF_SECTION *cs, char const *name)
Definition compile.c:1601
fr_table_num_sorted_t const mod_rcode_table[]
Definition compile.c:74
#define MEM(x)
Definition debug.h:46
talloc_free(hp)
bool unlang_request_is_scheduled(request_t const *request)
Return whether a request is currently scheduled.
Definition interpret.c:1589
void unlang_interpret_signal(request_t *request, fr_signal_t action)
Send a signal (usually stop) to a request.
Definition interpret.c:1417
int unlang_interpret_push(unlang_result_t *p_result, request_t *request, unlang_t const *instruction, unlang_frame_conf_t const *conf, bool do_next_sibling)
Push a new frame onto the stack.
Definition interpret.c:278
Declarations for the unlang interpreter.
unlang_mod_action_t priority
The priority or action for that rcode.
Definition interpret.h:136
#define UNLANG_RESULT_NOT_SET
Definition interpret.h:139
#define FRAME_CONF(_default_rcode, _top_frame)
Definition interpret.h:152
#define UNLANG_SUB_FRAME
Definition interpret.h:37
rlm_rcode_t rcode
The current rcode, from executing the instruction or merging the result from a frame.
Definition interpret.h:134
#define UNLANG_TOP_FRAME
Definition interpret.h:36
#define UNLANG_RESULT_RCODE(_x)
Definition interpret.h:140
Private declarations for the unlang interpreter.
static void interpret_child_init(request_t *request)
#define RDEBUG_ENABLED3
True if request debug level 1-3 messages are enabled.
Definition log.h:347
#define RDEBUG3(fmt,...)
Definition log.h:355
#define RDEBUG4(fmt,...)
Definition log.h:356
static TALLOC_CTX * unlang_ctx
Definition base.c:71
void unlang_register(unlang_op_t *op)
Register an operation with the interpreter.
Definition base.c:56
request_t * unlang_io_subrequest_alloc(request_t *parent, fr_dict_t const *namespace, bool detachable)
Allocate a child request based on the parent.
Definition io.c:39
const char * mod_action_name[MOD_PRIORITY_MAX+1]
Definition mod_action.c:112
Unlang module actions.
@ MOD_ACTION_NOT_SET
default "not set by anything"
Definition mod_action.h:38
@ MOD_ACTION_RETURN
stop processing the section, and return the rcode with unset priority
Definition mod_action.h:41
@ MOD_ACTION_REJECT
change the rcode to REJECT, with unset priority
Definition mod_action.h:40
@ MOD_ACTION_RETRY
retry the instruction, MUST also set a retry config
Definition mod_action.h:39
#define MOD_ACTION_VALID(_x)
Definition mod_action.h:65
int fr_pair_list_copy(TALLOC_CTX *ctx, fr_pair_list_t *to, fr_pair_list_t const *from)
Duplicate a list of pairs.
Definition pair.c:2326
void unlang_parallel_init(void)
Definition parallel.c:457
static unlang_action_t unlang_parallel(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Definition parallel.c:207
static unlang_t * unlang_compile_parallel(unlang_t *parent, unlang_compile_ctx_t *unlang_ctx, CONF_ITEM const *ci)
Definition parallel.c:402
static void unlang_parallel_cancel_child(unlang_parallel_state_t *state, unlang_child_request_t *cr)
Cancel a specific child.
Definition parallel.c:51
static unlang_action_t unlang_parallel_resume(unlang_result_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Definition parallel.c:140
static void unlang_parallel_signal(UNUSED request_t *request, unlang_stack_frame_t *frame, fr_signal_t action)
Send a signal from parent request to all of it's children.
Definition parallel.c:110
Declarations for the unlang "parallel" keyword.
bool detach
are we creating the child detached
static unlang_parallel_t * unlang_group_to_parallel(unlang_group_t *g)
Cast a group structure to the parallel keyword extension.
unlang_result_t result
our result
unsigned int num_runnable
How many children are complete.
bool clone
are the children cloned
bool detach
are we creating the child detached
unlang_child_request_t children[]
Array of children.
unsigned int num_children
How many children are executing.
#define fr_assert(_expr)
Definition rad_assert.h:37
#define REDEBUG(fmt,...)
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:48
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:47
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:54
#define RETURN_UNLANG_NOOP
Definition rcode.h:69
#define REQUEST_VERIFY(_x)
Definition request.h:309
void fr_state_restore_from_parent(request_t *child, void const *unique_ptr, int unique_int)
Restore subrequest data from a parent request.
Definition state.c:958
fr_signal_t
Signals that can be generated/processed by request signal handlers.
Definition signal.h:38
@ FR_SIGNAL_CANCEL
Request has been cancelled.
Definition signal.h:40
@ FR_SIGNAL_DETACH
Request is being detached from its parent.
Definition signal.h:45
void unlang_subrequest_detach_and_free(request_t **child)
Free a child request, detaching it from its parent and freeing allocated memory.
Definition subrequest.c:287
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
Private interpreter structures and functions.
void * state
Stack frame specialisations.
#define UNLANG_NEXT_STOP
Definition unlang_priv.h:98
#define UNLANG_IGNORE
static unlang_group_t * unlang_generic_to_group(unlang_t const *p)
static unlang_stack_frame_t * frame_current(request_t *request)
unlang_list_t children
static int stack_depth_current(request_t *request)
@ UNLANG_TYPE_PARALLEL
execute statements in parallel
Definition unlang_priv.h:55
static void frame_repeat(unlang_stack_frame_t *frame, unlang_process_t process)
Mark the current stack frame up for repeat, and set a new process function.
unlang_t const * instruction
The unlang node we're evaluating.
@ UNLANG_OP_FLAG_NO_FORCE_UNWIND
Must not be cancelled.
@ UNLANG_OP_FLAG_DEBUG_BRACES
Print debug braces.
@ UNLANG_OP_FLAG_RCODE_SET
Set request->rcode to the result of this operation.
Generic representation of a grouping.
An unlang operation.
A node in a graph of unlang_op_t (s) that we execute.
Our interpreter stack, as distinct from the C stack.
static fr_slen_t parent
Definition pair.h:858
#define DOC_KEYWORD_REF(_x)
Definition version.h:89