i tried the following way... but everytime it waits for the after
inside proc... i want to ensure not to wait more time than actually
taken for proc, whats wrong here, pls help?
# Initialise the state
set state timeout
set id [after 5000 set state timeout]
proc ept {args} {
global state id
set id [after 3000 set state $args]
# do processing
}
ept "accepted"
# Wait for something to happen
vwait state
# Clean up events that could have happened
after cancel $id
# Do something based on how the vwait finished...
switch $state {
timeout {
puts "timedout"
}
accepted {
puts "yes its accepted"
}
}
> i need to ensure a timeout is returned if procedure failed to execute
> in less than 5 seconds....
>
> i tried the following way... but everytime it waits for the after
> inside proc... i want to ensure not to wait more time than actually
> taken for proc, whats wrong here, pls help?
>
> set state timeout
> set id [after 5000 set state timeout]
Huh? $state always has the same value???
> proc ept {args} {
> global state id
> set id [after 3000 set state $args]
> # do processing
>
> }
The critical part is what you left out under the "do processing".
Since you are using the event loop to signal the timeout, you
need to be sure that the event loop is allowed to run at frequent
intervals during the processing. Is the processing number crunching
or I/O? If the former then you will need to sprinkle "update"
into it, or refactor it to run in re-scheduled pieces or in a
coroutine. If you can't exercise the event loop in the processing,
then you need to do the processing in a separate thread or process.
(For separate process, see the open command for an execution pipe.)
> ept "accepted"
>
> # Wait for something to happen
>
> vwait state
Oh no! That is just wrong! You run ept until it finishes, and
*then* start waiting for your variable! This scheme ensures that
you wait *at least* 3 seconds.
You don't show anything to stop the processing when a timeout
occurs. Does your processing, by any chance, test the value
of $state periodically? For number-crunching it should
periodically do:
update
if {$state eq "timeout"} abort_my_processing
Or, as mentioned, do the processing in a separate thread or process.
Donald Arseneau as...@triumf.ca
You can't run you proc in the background, which means, while it is
running, it is blocking and nothing else can run, including the
"after" code. You will either need to use a coroutine and yield
periodically (if possible) in your "do processing" code or run your
"do processing" code in a thread.
package require Thread;
set t [thread::create];
thread::send -async {#do processing; #return "accepted"} state;
after 5000 [list set state timeout];
vwait state;
small typo in that code:
thread::send -async {#do processing; #return "accepted"} state;
should be
thread::send -async $t {#do processing; #return "accepted"} state;
Donald Arseneau mentioned other things you could do.
Basically, you probably do not have 8.6 installed, so coroutines are
out. Threads are out. You have these options:
1. In your "#do processing" code, do periodic [update]; right after
the update, check the status variable, if set to "timeout", abort "#do
processing". Using [update] is not always recommended http://wiki.tcl.tk/1255
but if it is the only thing you are doing, it will most likely work.
The problem here is that it might not be possible to periodically
interrupt "#do processing".
2. As Donald suggested, use [open "|file.tcl"] to run your "#do
processing" in a different process. http://www.tcl.tk/man/tcl8.5/TclCmd/open.htm#M21
3. Use "comm" package, much more involved. http://tcllib.sourceforge.net/doc/comm.html
4. Use "Expect" package, might be a bit overwhelming.
http://www.tcl.tk/man/expect5.31/expect.1.html
Instead of using the event loop, you could set a target time for your
timeout, then periodically check to see whether that time has been
exceeded or not. For example:
proc ept {args} {
variable state
set maxtime [expr {[clock milliseconds] + 5000}]
# processing follows
while {$condition} {
if {[clock milliseconds] > $maxtime} {
set state timeout
return
}
# do rest of processing
}
set state $args
}
Obviously you have to adapt the above to meet your needs, but the idea
is to check the time at regular intervals during processing, and to
abort if the target time has been surpassed. There's nothing elegant
about this kind of solution, but it shouldn't require anything you
don't have. (If you don't have [clock milliseconds], hopefully you
have [clock clicks -milliseconds], which does the same thing.)
If you have 8.5+ (don't you?) you can use the built-in limit for slave
interpreters, and run your proc inside a time-limited interpreter.
Something along these lines:
set timeout 1 ;# timeout in seconds!
# set up the interpreter and a sample proc
set i [interp create]
$i eval {
proc foo {n} {
for {set i 0} {$i < $n} {incr i} {
list a b c
}
}
}
# test
foreach n {1000 10000000} {
puts "running proc foo with n=$n inside $i"
interp limit $i time -seconds [clock add [clock seconds] $timeout
seconds]
if {[catch {interp eval $i [list foo $n]}]} {
puts "Timeout"
} else {
puts "Accepted"
}
}
Regards
Emiliano