global
commandnsv
API (which supersedes ns_share
from the pre-3.0 API).
global
accesses script-global, not server-global, variables from
within a procedure. This distinction is important to understand in
order to use global
correctly when programming AOLserver.
Also, AOLserver purges all script-global variables in a thread (i.e., Tcl interpreter) between HTTP requests. If it didn't, that would affect (and complicate) our use of script-global variables dramatically, which would then be better described as thread-global variables. Given AOLserver's behaviour, however, "script-global" is a more appropriate term.
ns_schedule_proc
and ad_schedule_proc
each take
a -thread
flag to cause a scheduled procedure to run
asychronously, in its own thread. It almost always seems like a good idea
to specify this switch, but there's a problem.
It turns out that whenever a task scheduled with
ns_schedule_proc -thread
or ad_schedule_proc -thread t
is run, AOLserver creates a brand new thread and a brand new
interpreter, and reinitializes the procedure table
(essentially, loads all procedures that were
created during server initialization into the new interpreter). This happens
every time the task is executed - and it
is a very expensive process that should not be taken lightly!
The moral: if you have a lightweight scheduled procedure which runs
frequently, don't use the -thread
switch.
Note also that thread is initialized with a copy of what was installed during server startup, so if the procedure table have changed since startup (e.g. using the APM watch facility), that will not be reflected in the scheduled thread.
empty_string_p
returns a number: 1 or 0. Other functions need to return a composite
value. For instance, consider a function that looks up a user's name
and email address, given an ID. One way to implement this is to return
a three-element list and document that the first element contains the
name, and the second contains the email address. The problem with this
technique is that, because Tcl does not support constants, calling
procedures that returns lists in this way necessitates the use of
magic numbers, e.g.:
set user_info [ad_get_user_info $user_id] set first_name [lindex $user_info 0] set email [lindex $user_info 1]
AOLserver/Tcl generally has three mechanisms that we like, for returning more than one value from a function. When to use which depends on the circumstances.
array
get
-formatted list. It has all the nice properties of
pass-by-value, and it uses Tcl arrays, which have good native support.
You could also have done this by using an array internally and usingad_proc ad_get_user_info { user_id } { db_1row user_info { select first_names, last_name, email from users where user_id = :user_id } return [list \ name "$first_names $last_name" \ email $email \ namelink "<a href=\"/shared/community-member?user_id=[ns_urlencode $user_id]\">$first_names $last_name</a>" \ emaillink "<a href=\"mailto:$email\">$email</a>"] } array set user_info [ad_get_user_info $user_id] doc_body_append "$user_info(namelink) ($user_info(emaillink))"
array get
:
ad_proc ad_get_user_info { user_id } { db_1row user_info { select first_names, last_name, email from users where user_id = :user_id } set user_info(name) "$first_names $last_name" set user_info(email) $email set user_info(namelink) "<a href=\"/shared/community-member?user_id=[ns_urlencode $user_id]\">$first_names $last_name</a>" set user_info(emaillink) "<a href=\"mailto:$email\">$email</a>" return [array get user_info] }
The transformation of the array into a list and back to an array takes, in our test environment, approximately 10 microseconds per entry of 100 character's length. Thus you can process about 100 entries per milisecond. The time depends almost completely on the number of entries, and almost not at all on the size of the entries.You implement pass-by-reference in Tcl by taking the name of an array as an argument and
upvar
it.
ad_proc ad_get_user_info { -array:required user_id } { upvar $array user_info db_1row user_info { select first_names, last_name, email from users where user_id = :user_id } set user_info(name) "$first_names $last_name" set user_info(email) $email set user_info(namelink) "<a href=\"/shared/community-member?user_id=[ns_urlencode $user_id]\">$first_names $last_name</a>" set user_info(emaillink) "<a href=\"mailto:$email\">$email</a>" } ad_get_user_info -array user_info $user_id doc_body_append "$user_info(namelink) ($user_info(emaillink))"
We prefer pass-by-value over pass-by-reference. Pass-by-reference
makes the code harder to read and debug, because changing a value in
one place has side effects in other places. Especially if have a chain
of upvar
s through several layers of the call stack,
you'll have a hard time debugging.
ns_set
s and Pass-By-Reference
If your data can have multiple entries with the same key, you
should use the AOLserver built-in ns_set
.
You can also do a case-insensitive lookup on an ns_set
,
something you can't easily do on an array. This is especially useful
for things like HTTP headers, which happen to have these exact
properties.
You always use pass-by-reference with ns_set
s, since they
don't have any built-in way of generating and reconstructing
themselves from a string representation. Instead, you pass the handle
to the set.
We don't recommendad_proc ad_get_user_info { -set:required user_id } { db_1row user_info { select first_names, last_name, email from users where user_id = :user_id } ns_set put $set name "$first_names $last_name" ns_set put $set email $email ns_set put $set namelink "<a href=\"/shared/community-member?user_id=[ns_urlencode $user_id]\">$first_names $last_name</a>" ns_set put $set emaillink "<a href=\"mailto:$email\">$email</a>" } set user_info [ns_set create] ad_get_user_info -set $user_info $user_id doc_body_append "[ns_set get $user_info namelink] ([ns_set get $user_info emaillink])"
ns_set
as a general mechanism for
passing sets (as opposed to multisets) of data. Not only do they
inherently use pass-by-reference, which we dis-like, they're also
somewhat clumsy to use, since Tcl doesn't have built-in syntactic
support for them.
Consider for example a loop over the entries in a ns_set
as compared to an array:
And this example of constructing a value:# ns_set variant set size [ns_set size $myset] for { set i 0 } { $i < $size } { incr i } { puts "[ns_set key $myset $i] = [ns_set value $myset $i]" } # array variant foreach name [array names myarray] { puts "$myarray($name) = $myarray($name)" }
# ns_set variant set myset [ns_set create] ns_set put $myset foo $foo ns_set put $myset baz $baz return $myset # array variant return [list foo $foo baz $baz ]
ns_set
s are designed to be lightweight, so
memory consumption should not be a problem. However, when using
ns_set get
to perform lookup by name, they perform a
linear lookup, whereas arrays use a hash table, so
ns_set
s are slower than arrays when the number of
entries is large.