Forum Discussion
Scope of variables when using call
- Dec 05, 2022
The proc does not inherit the variables from the caller. It will inherit all static variables and functions eg HTTP::path, but there are two ways to allow these variables in the procedure - either send them as calling variables, or use upvar.
Sending them as variables is more dependable - you don't need to worry about naming or suchlike but you can end up sending a lot of information. Add this to the procedure eg
proc process_URL {var1 var2 var3 {optionalvar "default value"} } { ... }
You can also only have a single variable but make it a list.
Upvar depends on naming of variables to import a specific variable into the proc scope. Very useful if you want to modify the original variable, but open to errors if the variable doesn't exist or suchlike
Hi AlexS,
let me give a brief demo what happens behind the scene and how to deal with variable:
Lets assume this...
when RULE_INIT {
log local0.debug "-- RULE_INIT --"
set variable_test_1 abc
log local0.debug "Exec Level: [info level]"
log local0.debug "Visible VAR: [info vars variable_test_*]"
}
when HTTP_REQUEST {
log local0.debug "-- HTTP_REQUEST (before PROC) --"
set variable_test_2 abc
log local0.debug "Exec Level: [info level]"
log local0.debug "Visible VAR: [info vars variable_test_*]"
call TESTPROC
log local0.debug "-- HTTP_REQUEST (after PROC) --"
log local0.debug "Exec Level: [info level]"
log local0.debug "Visible VAR: [info vars variable_test_*]"
}
proc TESTPROC { } {
log local0.debug "-- TEST PROC --"
set variable_test_3 abc
log local0.debug "Exec Level: [info level]"
log local0.debug "Visible VAR: [info vars variable_test_*]"
}
Directly after saving the iRule the RULE_INIT event gets triggered and creates you these log lines...
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm1[11166]: Rule /Common/TestRule <RULE_INIT>: -- RULE_INIT --
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm1[11166]: Rule /Common/TestRule <RULE_INIT>: Exec Level: 0
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm1[11166]: Rule /Common/TestRule <RULE_INIT>: Visible VAR: variable_test_1
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <RULE_INIT>: -- RULE_INIT --
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <RULE_INIT>: Exec Level: 0
Dec 5 11:34:10 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <RULE_INIT>: Visible VAR: variable_test_1
Note: I'm using 2 CPU Cores in my Dev Unit, so each of my CPUs running their own TMM process (tmm and tmm1) which will execute the TCL script. Thats why we see 2 times the log...
When I HTTP request to the virtual server the HTTP_REQUEST will become triggered, which calls the PROC. The HTTP_REQUEST and PROC will both write additional log lines...
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: -- HTTP_REQUEST (before PROC) --
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Exec Level: 2
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Visible VAR: variable_test_2
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: -- TEST PROC --
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Exec Level: 3
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Visible VAR: variable_test_3
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: -- HTTP_REQUEST (after PROC) --
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Exec Level: 2
Dec 5 11:43:55 kw-f5-dev.itacs.de debug tmm[11166]: Rule /Common/TestRule <HTTP_REQUEST>: Visible VAR: variable_test_2
Whats happening in the example above?
First of all try to think multidimensional!
We may have lots of CMP-enabled CPUs and each is executing an independent TCL environment. We have global stuff in each independent TCL environment. We have lots of per TCP/UDP connection related stuff going on. And then we have finally individual stuff happening within a PROC executions.
RULE_INIT scripts are getting executed in the highest TCL execution level (ID=0) and are used to define global stuff which affects all iRules and all Connections processed by your individual Virtual Servers. The purpose of this event is to define static stuff which may become readable by any child execution levels.
HTTP_REQUEST scripts are getting executed in the TCL execution level (ID=2) of the underlying TCP connection over which the request was send. Whatever you define here, is only affecting the individual TCP connection.
PROC script are getting executed in a child TCL execution level (ID=MotherLVL+1) of the calling TCL execution level. Whatever you define here, is only affecting the individual PROC itself.
How can I cross communicate between the TCL execution levels and TCL environments?
You can define some global stuff in a static::* variable during RULE_INIT. Remember that this code is getting executed once everytime you "Save" your iRule on the System.
when RULE_INIT {
set static::optionA "abc"
}
A HTTP_REQUEST event may read the static::* variable during its event execution.
when HTTP_REQUEST {
if { $static::optionA equals "abc" } then {
# Do some fancing things...
}
}
Note: Dont change those static::* varibales from events other than RULE_INIT, use them as Read-Only variables. Please simply dont do it... unless you know the impact of doing so... 😉
A HTTP_REQUEST event may call a PROC and pass arguments to it and the PROC may output a return value which can be stored in a variable.
when HTTP_REQUEST {
set my_variable "123"
set proc_return_value [call my_proc $my_variable]
if { $proc_return_value == "12345" } then {
# Do some fancing things...
}
}
proc my_proc { proc_input_variable } {
return "${proc_input_variable}45"
}
A slightly more advanced method to pass varibales between a PROC and its calling execution level would be to use [upvar] to map a varibale within a PROC to a varibale in the calling execution level.
when HTTP_REQUEST {
set x "Hello"
set y "World"
call build_string
log local0.debug $z
}
proc build_string { } {
upvar x local_x
upvar y local_y
upvar z local_z
set local_z "$local_x $local_y"
}
Note: Mapping many varibales between destinct TCL execution levels is annoying and also adds lots of CPU overhead. Use PROCs only if you get advantages by doing so and not because you are getting used to it from other coding languages. I for an example never use procs in TCL because they are almost always better (more flexible, faster, simpler, etc.) alternatives than using PROCs...
The last method I'd like to show you is how you could cross communicate between independent Connection and even TCL environments running on different CPUs by using the [table] command.
when HTTP_REQUEST {
set counter_value [table incr -mustexist "Count_[IP::client_addr]"]
if { $counter_value eq "" } then {
# Counter not available. Initialize a new table entry with timeout of 10 seconds.
set counter_value 1
table set "Count_[IP::client_addr]" $counter_value 10 indef
}
log local0.debug "Client [IP::client_addr] has requested $counter_value time in the last 10 seconds."
}
Note: The above example will work, even if the consecutive HTTP request where send acrross different TCP connection and processed by different CPUs.
Good source for the command in this POST:
HTH and Cheers, Kai
Thank you a very details and very usefil example
You talk about not using proc's
My need is to do the same thing (steps say 5 which includes a test ) in multiple places on a big switch statement based upon uri.
I can't group the URI together to write this code once and i don't want to duplicate it many times so i thought a call would be the way to do it.
you allude that there are better ways to do it. how would you do that ?
- Kai_WilkeDec 05, 2022MVP
Hi Alex,
Could you please write some pseudo code how the switch statement looks like and where the proc should be executed?
In most cases, you could probably devide your code structure in two destinct parts, the first part parameterizes settings based on conditions (aka. Switch statement assigns variables) and a second part reads parameters and then executes code conditional. This is probably the most used technique...
Instead of using [call] procedures you could use [eval] based macros. The difference between those two is that the [eval] macros are executed in the same execution level and can natively access the varibale from the caller. This is actually a slightly rare used technique...
There are many more options available to avoid the use of TCL procedures. It all depends what you are trying to achive...Cheers, Kai
- AlexS_ybDec 05, 2022Cirrocumulus
Hi
Pretty simple switch
based up uri and then it
sets pools or makes calls (sso hooks - I had an old sso I had to attach to)
But must of the work is done in the switch.
Thinking about it yeah I could actually just set switchs and process it at the end.
eval macros - do you have a link to these and how they work, sound interesting
A
- Kai_WilkeDec 06, 2022MVP
Hi AlexS,
> Thinking about it yeah I could actually just set switchs and process it at the end.
Inline code (even redundant code is far more effective within TCL), so yeah try to avoid [proc] as much as you can.
> eval macros - do you have a link to these and how they work, sound interesting
Below is an example of [eval] macros.
when RULE_INIT { set static::my_code_to_execute { # Define your code which needs to be executed in many places... append my_x "" append my_x "W" append my_x "o" append my_x "r" append my_x "l" append my_x "d" } } when HTTP_REQUEST { set my_x "Hello" # Insert the code above into your code and execute it... eval $static::my_code_to_execute log local0.debug $my_x }
Warning: Always define your macro code during RULE_INIT in a static::* variable (use unique names for unique macros). And execute the defined code as shown above. Avoid other [eval] usecases unless you understand the nasty side effects and security implications of [eval].
Note: The RULE_INIT event may be stored in iRule 1 and the HTTP_REQUEST event can be stored in iRule 2. You only need to attach the iRule containing the HTTP_REQUEST event to your Virtual Server. You can basically define global accesible code macros with the outlined technique...
Cheers, Kai
Recent Discussions
Related Content
* Getting Started on DevCentral
* Community Guidelines
* Community Terms of Use / EULA
* Community Ranking Explained
* Community Resources
* Contact the DevCentral Team
* Update MFA on account.f5.com