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: f3ec4650a1b416fee4686f8e66c35f41b26b1727 $
19 *
20 * @file unlang/parallel.c
21 * @brief Implementation of the unlang "parallel" keyword.
22 *
23 * @copyright 2006-2019 The FreeRADIUS server project
24 */
25RCSID("$Id: f3ec4650a1b416fee4686f8e66c35f41b26b1727 $")
26
27#include "function.h"
28#include "interpret_priv.h"
29#include "module_priv.h"
30#include "parallel_priv.h"
31#include "subrequest_priv.h"
32
33
34/** Cancel a specific child
35 *
36 */
37static inline CC_HINT(always_inline) void unlang_parallel_cancel_child(unlang_parallel_state_t *state, int i)
38{
39 request_t *child = state->children[i].request;
40 request_t *request = child->parent; /* For debug messages */
41
42 switch (state->children[i].state) {
43 case CHILD_INIT:
44 state->children[i].state = CHILD_CANCELLED;
45 fr_assert(!state->children[i].request);
46 break;
47
48 case CHILD_EXITED:
49 state->children[i].state = CHILD_CANCELLED;
50 TALLOC_FREE(state->children[i].request);
51 break;
52
53 case CHILD_RUNNABLE: /* Don't check runnable_id, may be yielded */
54 /*
55 * Signal the child to stop
56 *
57 * The signal function cleans up the request
58 * and signals anything that was tracking it
59 * that it's now complete.
60 */
62
63 /*
64 * Free it.
65 */
66 TALLOC_FREE(state->children[i].request);
67 break;
68
69 case CHILD_DONE:
70 fr_assert(!fr_heap_entry_inserted(child->runnable_id));
71
72 /*
73 * Completed children just get freed
74 */
75 TALLOC_FREE(state->children[i].request);
76 break;
77
78 case CHILD_DETACHED:
79 case CHILD_CANCELLED:
80 return;
81 }
82
83 RDEBUG3("parallel - child %s (%d/%d) CANCELLED",
84 state->children[i].name,
85 i + 1, state->num_children);
86 state->children[i].state = CHILD_CANCELLED;
87}
88
89#if 0
90/** Cancel all the child's siblings
91 *
92 * Siblings will be excluded from final result calculation for the parallel section.
93 */
94static void unlang_parallel_cancel_siblings(request_t *request)
95{
96 unlang_stack_t *stack = request->parent->stack;
97 unlang_stack_frame_t *frame = &stack->frame[stack->depth];
98 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
99 int i;
100
101 for (i = 0; i < state->num_children; i++) {
102 if (state->children[i].request == request) continue; /* Don't cancel this one */
103
105 }
106}
107#endif
108
109/** Signal handler to deal with UNLANG_ACTION_DETACH
110 *
111 * When a request detaches we need
112 */
113static void unlang_parallel_child_signal(request_t *request, UNUSED fr_signal_t action, void *uctx)
114{
115 unlang_parallel_child_t *child = uctx;
118
119 frame = frame_current(request->parent);
120 state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
121
122 RDEBUG3("parallel - child %s (%d/%d) DETACHED",
123 request->name,
124 child->num + 1, state->num_children);
125
126 child->state = CHILD_DETACHED;
127 child->request = NULL;
128 state->num_complete++;
129
130 /*
131 * All children exited, resume the parent
132 */
133 if (state->num_complete == state->num_children) {
134 RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
135 unlang_interpret_mark_runnable(request->parent);
136 }
137
138 return;
139}
140
141/** When the chld is done, tell the parent that we've exited.
142 *
143 */
144static unlang_action_t unlang_parallel_child_done(UNUSED rlm_rcode_t *p_result, UNUSED int *p_priority, request_t *request, void *uctx)
145{
146 unlang_parallel_child_t *child = uctx;
147
148 /*
149 * If we have a parent, then we're running synchronously
150 * with it. Tell the parent that we've exited, and it
151 * can continue.
152 *
153 * Otherwise we're a detached child, and we don't tell
154 * the parent anything. Because we have that kind of
155 * relationship.
156 */
157 if (child->state == CHILD_RUNNABLE) {
158 /*
159 * Reach into the parent to get the unlang_parallel_state_t
160 * for the whole parallel block.
161 */
162 unlang_stack_frame_t *frame = frame_current(request->parent);
163 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
164
165 RDEBUG3("parallel - child %s (%d/%d) EXITED",
166 request->name,
167 child->num + 1, state->num_children);
168
169 child->state = CHILD_EXITED;
170 state->num_complete++;
171
172 /*
173 * All children exited, resume the parent
174 */
175 if (state->num_complete == state->num_children) {
176 RDEBUG3("Signalling parent %s that all children have EXITED or DETACHED", request->parent->name);
177 unlang_interpret_mark_runnable(request->parent);
178 }
179 }
180
181 /*
182 * Don't change frame->result, it's the result of the child.
183 */
185}
186
188{
189 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
190
191 int i, priority;
192 rlm_rcode_t result;
193
194 for (i = 0; i < state->num_children; i++) {
195 if (!state->children[i].request) continue;
196
197 fr_assert(state->children[i].state == CHILD_EXITED);
199
200 RDEBUG3("parallel - child %s (%d/%d) DONE",
201 state->children[i].name,
202 i + 1, state->num_children);
203
204 state->children[i].state = CHILD_DONE;
205
206 priority = ((unlang_stack_t *)state->children[i].request->stack)->priority;
207 result = ((unlang_stack_t *)state->children[i].request->stack)->result;
208
209 /*
210 * Return isn't allowed to make it back
211 * to the parent... Not sure this is
212 * the correct behaviour, but it's what
213 * was there before.
214 */
215 if (priority == MOD_ACTION_RETURN) {
216 priority = 0;
217 } else if (priority == MOD_ACTION_REJECT) {
218 result = RLM_MODULE_REJECT;
219 priority = 0;
220 }
221
222 /*
223 * Do priority over-ride.
224 */
225 if (priority > state->priority) {
226 state->result = result;
227 state->priority = priority;
228
229 RDEBUG4("** [%i] %s - over-riding result from higher priority to (%s %d)",
230 stack_depth_current(request), __FUNCTION__,
231 fr_table_str_by_value(mod_rcode_table, result, "<invalid>"),
232 priority);
233 }
234 }
235
236 /*
237 * Reap the children....
238 */
239 for (i = 0; i < state->num_children; i++) {
240 if (!state->children[i].request) continue;
241
242 fr_assert(!fr_heap_entry_inserted(state->children[i].request->runnable_id));
243 TALLOC_FREE(state->children[i].request);
244 }
245
246 *p_result = state->result;
248}
249
250/** Run one or more sub-sections from the parallel section.
251 *
252 */
254{
255 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
256 request_t *child;
257 int i;
258
259 /*
260 * If the children should be created detached, we return
261 * "noop". This function then creates the children,
262 * detaches them, and returns.
263 */
264 if (state->detach) {
265 state->priority = 0;
266 state->result = RLM_MODULE_NOOP;
267 }
268
269 /*
270 * Loop over all the children.
271 *
272 * We always service the parallel section from top to
273 * bottom, and we always service all of it.
274 */
275 for (i = 0; i < state->num_children; i++) {
276 fr_assert(state->children[i].instruction != NULL);
277 child = unlang_io_subrequest_alloc(request,
278 request->dict, state->detach);
279 child->packet->code = request->packet->code;
280
281 RDEBUG3("parallel - child %s (%d/%d) INIT",
282 child->name,
283 i + 1, state->num_children);
284
285 if (state->clone) {
286 /*
287 * Note that we do NOT copy the
288 * Session-State list! That
289 * contains state information for
290 * the parent.
291 */
292 if ((fr_pair_list_copy(child->request_ctx,
293 &child->request_pairs,
294 &request->request_pairs) < 0) ||
295 (fr_pair_list_copy(child->reply_ctx,
296 &child->reply_pairs,
297 &request->reply_pairs) < 0) ||
298 (fr_pair_list_copy(child->control_ctx,
299 &child->control_pairs,
300 &request->control_pairs) < 0)) {
301 REDEBUG("failed copying lists to clone");
302 error:
303 /*
304 * Detached children which have
305 * already been created are
306 * allowed to continue.
307 */
308 if (!state->detach) {
309 /*
310 * Remove the current child
311 *
312 * Should also free detached children
313 */
315
316 /*
317 * Remove all previously
318 * spawned children.
319 */
320 for (--i; i >= 0; i--) unlang_interpret_request_done(child);
321 }
322
324 }
325 }
326
327 /*
328 * Child starts detached, the parent knows
329 * and can exit immediately once all
330 * the children are initialised.
331 */
332 if (state->detach) {
333 if (RDEBUG_ENABLED3) {
334 request_t *parent = request;
335
336 request = child;
337 RDEBUG3("parallel - child %s (%d/%d) DETACHED",
338 request->name,
339 i + 1, state->num_children);
340 request = parent;
341 }
342
343 state->children[i].state = CHILD_DETACHED;
344
345 /*
346 * Detach the child, and insert
347 * it into the backlog.
348 */
349 if ((unlang_subrequest_lifetime_set(child) < 0) || (request_detach(child) < 0)) {
350 request = child;
351 RPEDEBUG("Failed detaching request");
352 talloc_free(child);
353
355 }
356 /*
357 * If the children don't start detached
358 * push a function onto the stack to
359 * notify the parent when the child is
360 * done.
361 */
362 } else {
363 unlang_stack_frame_t *child_frame;
364
365 if (unlang_function_push(child,
366 NULL,
371 &state->children[i]) < 0) goto error;
372 child_frame = frame_current(child);
373 return_point_set(child_frame); /* Don't unwind this frame */
374
375 state->children[i].num = i;
376 state->children[i].name = talloc_bstrdup(state, child->name);
377 state->children[i].request = child;
378 state->children[i].state = CHILD_RUNNABLE;
379
380 }
382
383 /*
384 * Push the first instruction for
385 * the child to run.
386 */
387 if (unlang_interpret_push(child,
390 state->detach ? UNLANG_TOP_FRAME : UNLANG_SUB_FRAME) < 0) goto error;
391 }
392
393 /*
394 * If all children start detached,
395 * then we're done.
396 */
397 if (state->detach) return UNLANG_ACTION_CALCULATE_RESULT;
398
399 /*
400 * Don't call this function again when
401 * the parent resumes, instead call
402 * a function to process the results
403 * of the children.
404 */
406
407 /*
408 * Yield to the children
409 *
410 * They scamper off to play on their
411 * own when they're all done, the last
412 * one tells the parent, so it can resume,
413 * and gather up all the results.
414 */
415 return UNLANG_ACTION_YIELD;
416}
417
418/** Send a signal from parent request to all of it's children
419 *
420 */
422 unlang_stack_frame_t *frame, fr_signal_t action)
423{
424 unlang_parallel_state_t *state = talloc_get_type_abort(frame->state, unlang_parallel_state_t);
425 int i;
426
427 if (action == FR_SIGNAL_CANCEL) {
428 for (i = 0; i < state->num_children; i++) unlang_parallel_cancel_child(state, i);
429
430 return;
431 }
432
433 /*
434 * Signal all of the runnable/running children.
435 */
436 for (i = 0; i < state->num_children; i++) {
437 if (state->children[i].state != CHILD_RUNNABLE) continue;
438
439 unlang_interpret_signal(state->children[i].request, action);
440 }
441}
442
444{
445 unlang_t const *instruction;
447 unlang_parallel_t *gext;
449
450 int i;
451
453 if (!g->num_children) {
454 *p_result = RLM_MODULE_NOOP;
456 }
457
458 gext = unlang_group_to_parallel(g);
459
460 /*
461 * Allocate an array for the children.
462 */
463 MEM(frame->state = state = _talloc_zero_pooled_object(request,
465 (sizeof(state->children[0]) * g->num_children),
466 "unlang_parallel_state_t",
467 g->num_children,
468 (talloc_array_length(request->name) * 2)));
469 if (!state) {
470 *p_result = RLM_MODULE_FAIL;
472 }
473
474 (void) talloc_set_type(state, unlang_parallel_state_t);
475 state->result = RLM_MODULE_FAIL;
476 state->priority = -1; /* as-yet unset */
477 state->detach = gext->detach;
478 state->clone = gext->clone;
479 state->num_children = g->num_children;
480
481 /*
482 * Initialize all of the children.
483 */
484 for (i = 0, instruction = g->children; instruction != NULL; i++, instruction = instruction->next) {
485 state->children[i].state = CHILD_INIT;
486 state->children[i].instruction = instruction;
487 }
488
490 return unlang_parallel_process(p_result, request, frame);
491}
492
494{
496 &(unlang_op_t){
497 .name = "parallel",
498 .interpret = unlang_parallel,
499 .signal = unlang_parallel_signal,
500 .rcode_set = true,
501 .debug_braces = true
502 });
503}
unlang_action_t
Returned by unlang_op_t calls, determine the next action of the interpreter.
Definition action.h:35
@ 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:42
#define RCSID(id)
Definition build.h:483
#define UNUSED
Definition build.h:315
fr_table_num_sorted_t const mod_rcode_table[]
Definition compile.c:82
#define MEM(x)
Definition debug.h:36
#define unlang_function_push(_request, _func, _repeat, _signal, _sigmask, _top_frame, _uctx)
Push a generic function onto the unlang stack.
Definition function.h:111
static bool fr_heap_entry_inserted(fr_heap_index_t heap_idx)
Check if an entry is inserted into a heap.
Definition heap.h:124
void unlang_interpret_request_done(request_t *request)
Indicate to the caller of the interpreter that this request is complete.
Definition interpret.c:1072
void unlang_interpret_mark_runnable(request_t *request)
Mark a request as resumable.
Definition interpret.c:1359
int unlang_interpret_push(request_t *request, unlang_t const *instruction, rlm_rcode_t default_rcode, bool do_next_sibling, bool top_frame)
Push a new frame onto the stack.
Definition interpret.c:161
void unlang_interpret_signal(request_t *request, fr_signal_t action)
Send a signal (usually stop) to a request.
Definition interpret.c:1196
#define UNLANG_SUB_FRAME
Definition interpret.h:36
#define UNLANG_TOP_FRAME
Definition interpret.h:35
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:335
#define RDEBUG3(fmt,...)
Definition log.h:343
#define RPEDEBUG(fmt,...)
Definition log.h:376
#define RDEBUG4(fmt,...)
Definition log.h:344
void unlang_register(int type, unlang_op_t *op)
Register an operation with the interpreter.
Definition base.c:63
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
talloc_free(reap)
static char * stack[MAX_STACK]
Definition radmin.c:158
@ MOD_ACTION_RETURN
Definition mod_action.h:40
@ MOD_ACTION_REJECT
Definition mod_action.h:41
Declarations for the unlang module interface.
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:2319
static unlang_action_t unlang_parallel(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Definition parallel.c:443
void unlang_parallel_init(void)
Definition parallel.c:493
static unlang_action_t unlang_parallel_resume(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Definition parallel.c:187
static unlang_action_t unlang_parallel_child_done(UNUSED rlm_rcode_t *p_result, UNUSED int *p_priority, request_t *request, void *uctx)
When the chld is done, tell the parent that we've exited.
Definition parallel.c:144
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:421
static unlang_action_t unlang_parallel_process(rlm_rcode_t *p_result, request_t *request, unlang_stack_frame_t *frame)
Run one or more sub-sections from the parallel section.
Definition parallel.c:253
static void unlang_parallel_child_signal(request_t *request, UNUSED fr_signal_t action, void *uctx)
Signal handler to deal with UNLANG_ACTION_DETACH.
Definition parallel.c:113
static void unlang_parallel_cancel_child(unlang_parallel_state_t *state, int i)
Cancel a specific child.
Definition parallel.c:37
Declarations for the unlang "parallel" keyword.
bool detach
are we creating the child detached
char * name
Cache the request name.
unlang_parallel_child_t children[]
Array of children.
static unlang_parallel_t * unlang_group_to_parallel(unlang_group_t *g)
Cast a group structure to the parallel keyword extension.
int num_complete
How many children are complete.
int num_children
How many children are executing.
request_t * request
Child request.
int num
The child number.
bool clone
are the children cloned
unlang_t const * instruction
broken out of g->children
bool detach
are we creating the child detached
@ CHILD_RUNNABLE
Running/runnable.
@ CHILD_DETACHED
Child has detached.
@ CHILD_INIT
Initial state.
@ CHILD_EXITED
Child has exited.
@ CHILD_CANCELLED
Child was cancelled.
@ CHILD_DONE
The child has completed.
unlang_parallel_child_state_t state
State of the child.
Each parallel child has a state, and an associated request.
#define fr_assert(_expr)
Definition rad_assert.h:38
#define REDEBUG(fmt,...)
Definition radclient.h:52
#define RETURN_MODULE_FAIL
Definition rcode.h:56
rlm_rcode_t
Return codes indicating the result of the module call.
Definition rcode.h:40
@ RLM_MODULE_FAIL
Module failed, don't reply.
Definition rcode.h:42
@ RLM_MODULE_REJECT
Immediately reject the request.
Definition rcode.h:41
@ RLM_MODULE_NOOP
Module succeeded without doing anything.
Definition rcode.h:48
int request_detach(request_t *child)
Unlink a subrequest from its parent.
Definition request.c:668
#define REQUEST_VERIFY(_x)
Definition request.h:276
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
int unlang_subrequest_lifetime_set(request_t *request)
Initialize a detached child.
#define fr_table_str_by_value(_table, _number, _def)
Convert an integer to a string.
Definition table.h:772
char * talloc_bstrdup(TALLOC_CTX *ctx, char const *in)
Binary safe strdup function.
Definition talloc.c:536
unlang_t * next
Next node (executed on UNLANG_ACTION_EXECUTE_NEXT et al).
void * state
Stack frame specialisations.
#define UNLANG_NEXT_STOP
Definition unlang_priv.h:92
static unlang_group_t * unlang_generic_to_group(unlang_t const *p)
static unlang_stack_frame_t * frame_current(request_t *request)
static void return_point_set(unlang_stack_frame_t *frame)
static int stack_depth_current(request_t *request)
@ UNLANG_TYPE_PARALLEL
execute statements in parallel
Definition unlang_priv.h:52
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_process_t process
function to call for interpreting this stack frame
unlang_t * children
Children beneath this group.
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.
An unlang stack associated with a request.
static fr_slen_t parent
Definition pair.h:851