The parallel Statement
parallel [ empty | detach ] {
[ statements ]
}
The parallel
section runs multiple child subsections in parallel.
Once all of the statements in the parallel
section have finished
execution, the parallel section exits. The return code of the
parallel section is the same as if each subsection was run
sequentially.
The parallel
section is most useful for running modules which call
external systems and wait for a reply. When modules are run in
series, the first module must run to completion, and only then can the
second and subsequent modules be executed. With parallel, the first
module is run, and then as soon as that module yeilds in order to wait
for an event, the second module can immediately run.
Each child subsection is executed using a subrequest. Please see the next subsection for more information about subrequests.
Despite the "parallel" name, the section will only run one module at a
time. This limitation ensures that there are no issues with
multi-threaded race conditions, locking, etc. The "parallel" name is
used in order to highlight the fact that multiple events are being
handled at the "same time" inside of a parallel
section. The name
does not indicate that there are multiple threads of execution.
There are no limits as to the number of subsections which can be
placed inside of a parallel
section.
The following section sends a RADIUS packet to radius1
, and then
immediately another RADIUS packet to radius2
. The parallel section
returns only when both modules have returned. i.e. Either via
timeouts, or by receiving replies to the requests.
Each module radius
and radius2
is handed a child request which is
an identical copy of the parent request.
parallel {
radius1
radius2
}
Child Requests are Independent
Each module or subsection runs as a new child request, i.e. a
subrequest. Each child request is an identical
copy of the parent request. Policies in the child can update the
original parent by referencing &parent.request
, or
&parent.reply
. Please see the list syntax for a
more complete description of how to refer to parent requests.
The child requests are required because each subsection is run independently, with independent events, timers, etc. That is, each child request has its own state, independent of any other request. This independence is required in order to fully implement the "parallel" nature of this keyword.
Once the child requests are finished, they are freed. Any information
stored in them is discarded. Information that is needed after the
parallel
section is run should be saved in the parent request.
When using complex policies inside of a parallel
section, each
subsection should be enclosed in a group keyword.
parallel {
group {
radius1
if (fail) {
linelog1
&parent.reply += {
&Reply-Message = "radius1 failed"
}
}
}
group {
radius2
if (fail) {
linelog2
&parent.reply += {
&Reply-Message = "radius2 failed"
}
}
}
}
Subrequests are Synchronous
Execution of the parent request is paused while each child request is running. The parent request continues execution once all of the the child requests have finished.
Unlike the subrequest keyword, the child
requests of a parallel
section cannot be made asynchronous via the
detach keyword.
parallel empty
The parallel empty { … }
syntax creates empty child requests.
These requests contain no attributes. Attributes in the child request
must be added manually via an edit statement.
The empty
keyword is most useful when it is necessary to manually
determine which attributes go into a child request.
The empty
keyword cannot be used with the detach
keyword. There
is no purpose to creating independent child requests which contain
nothing.
In this example, the radius1
module sees a User-Name
which is
modified from the parent User-Name
, and a User-Password
which is
static. The radius1
module sees a User-Name
which is modified in
a different way from the parent User-Name
, and it sees a
User-Password
which is a copy of the parents User-Password
.
parallel empty {
group {
&request := {
&User-Name = "%{&parent.request.User-Name}@example.org"
&User-Password = "hello"
}
radius1
}
group {
&request := {
&User-Name = "%{&parent.request.User-Name}@example.com"
&User-Password = &parent.request.User-Password
}
radius2
}
}
parallel detach
The parallel detach { … }
syntax creates child requests which are
immediately detached from the parent request. Each child request
contains copies of all attributes in the parent request.
The parent request continues immediately, and independently of the child requests.
The detach
keyword cannot be used with the empty
keyword. There
is no purpose to creating independent child requests which contain
nothing.
The return code from the parallel detach { … }
section is noop
.
Since all of the child requests are independent of the parent, they
cannot convey any information back to the parent. Therefore, the
return code is always noop
.
The detach
keyword is most useful when it is necessary to send
packets to multiple destinations, but where there is no need to wait
for an answer.
In this example, the parallel detach { … }
syntax is used to send
packets to four different destinations at the same time, in a "fire
and forget" manner.
parallel detach {
radius1
radius2
radius3
radius4
}
Exiting Early from a Parallel Section
In some situations, it may be useful to exit early from a parallel section. For example, to proxy a packet to multiple destinations, and then return as soon as any one of the destinations returns a reply.
The return keyword in a child is used to return from
the parallel
section, and to stop the execution of all children.
parallel {
group {
radius1
if (ok) {
return
}
}
group {
radius2
if (ok) {
return
}
}
group {
radius3
if (ok) {
return
}
}
group {
radius4
if (ok) {
return
}
}
}