Is there a way to manipulate non-global variables from a fileevent handler? Consider the following minimal server:
proc initState {stateName} {
upvar $stateName state
set state(foo) bar
set state(baz) bla
# ...
return
}
proc handleConnection {stateName newsock clientAddress clientPort} {
upvar $stateName state
fconfigure $newsock -blocking 0
fconfigure $newsock -buffering line
fileevent $newsock readable [list handleData $newsock]
return
}
proc handleData {f} {
if {[eof $f]} {
fileevent $f readable {}
close $f
return
}
gets $f line
puts $f ok
# need to modify state here...
return
}
proc runServer {port} {
array set state {}
initState state
socket -server {handleConnection state} $port
vwait forever
}
runServer 1234
Is there any possibility to manipulate the state array created in the scope of runServer or is the only way to do this making state a global variable?
I’m pretty new to Tcl, if I were using C I would simply pass a pointer to state into the event handler but unfortunately Tcl does not allow that. Am I doing anything weird here, is there a more Tcl-ish way?
That’s simply not going to work. The issue is that Tcl’s stack frames do not persist in the way that what you want would require.
The standard options to work around this are:
Keep the state in a global array that is indexed by a “connection token” (e.g., the name of the channel). Remember that arrays are indexed by strings; composite keys like “
sock42,hostname” are quite legal.Keep the state in a namespace named after the connection token. If you’re using Tcl 8.5, the
namespace upvarcommand makes this much easier.Keep the state in a TclOO object (requires Tcl 8.6 or the separate TclOO package for 8.5) or use a different object system (e.g., [incr Tcl], XOTcl; these are available for many Tcl versions).
Keep the state in a coroutine (requires Tcl 8.6). This effectively gives you a named stack (and lets you write your code so it is apparently “straight line” instead of driven by callback) but its version requirement is strict.