Martin S. Weber
Ephaeton at gmx dot net a person who's been away from Tcl for too long...
Wiki stuff:
Next to come:
Other:
This will be moved to another page once I find a good name for it.
msw suchenwi: I managed to shape up the script which is giving me vwait problems
* suchenwi can't look - busy with paywork .}
msw have a look at wiki://MSW, there's code and output from a run showing what I'm getting mad about :)
msw ...once you have time that is.
msw with tclsh running you just don't get the code to fuck up ... with tk it's piece of cake
msw Suppose you'd need to tokenize each run with own target variable etc. so it doesn't try to be multiply in the same proc
msw you others are well invited to have a look and tell me what I did wrong, too :)
arjen I am sorry: ight now I have my own things to get mad about ;)
msw oh, there's no pressure here, I've found a(n uberugly) workaround for my problem
msw Just for the usual cursing/"what's NOT possible"/things to avoid - notebook..
suchenwi msw: maybe you should just disable thew button when clicked, and re-enable it when bexec is done...
msw suchenwi: that's what I'm doing (a bit different, using a grab on a label saying have patience)
suchenwi As a single global variable bexec_result holds the results, two "parallel" bexecs are only guaranteed to make trouble.
msw suchenwi: but it's definitely a (n ugly) workaround
msw suchenwi: that's why I'm locking
msw suchenwi: and the locking is exposing the actual problem..
msw suchenwi: after the command is done, and the vwait returns, the proc is twice at the same position - before the acquire-lock part
suchenwi Maybe just to use ::bexec_runs to signal whether bexec is ain progress: if $::bexec_runs return
msw suchenwi: version #1 was doing that kind of ...
suchenwi Initializi to 0, set to 1 in the first bexec call, reset to 0 after the vwait.
msw if {$bexec_runs} then {vwait bexec_runs} ..
msw I should maybe add after idle to the vwait
suchenwi You can't vwait in two places for the same variable.
msw that's the dilemma :)
suchenwi But you can check its value and return early.
msw so I need to tokenize if I want to have the stuff be able to run in parallel
msw like vwait token-var, append result to token-var etc.
suchenwi Yes - sort of like the http package does it.
msw my manpage doesn't mention that you cannot vwait on the same var twice btw.
msw (8.3)
suchenwi Hm.. maybe you can, but it seems conceptually unclear to me - which vwait fires first, etc.
* suchenwi meeting
msw basically it should be fifo
msw but well, some core-savvy wants to comment ? :)
msw 8.4 vwait man doesn't mention it either
nem vwaits stack - I would assume that it would be a filo - last vwait would fire before first
msw well, event _queue_, would've supposed an append each time, but that doesn't matter ...
msw I wouldn't care about the order ...
msw if the events would be ok, but it seems broken (check the output on wiki.tcl.tk/msw, bottom of page)
* dkf back
suchenwi Donal: can two vwaits wait for the same variable?
dkf Yes
suchenwi And in what order are they released?
dkf Strict stack order
suchenwi But with "parallel" events, is there a stack order?
dkf There's always a stack order
dkf 'Cos we're all implemented in C...
suchenwi Ah, right :)
dkf The problem with msw's code is that it is trying to use stack-based operation with event handling
msw I've the feeling all I need is a closure and it'll work :p
dkf This is the sort of thing that https://wiki.tcl-lang.org/1255 warns against
dkf Actually, you need continuations ;)
msw ok, gimme :)
dkf But in their absence, what you need to do is to restructure your code so that instead of bexec returning the result, it takes a script which it hands off the result to when the background execution is done.
dkf With that done, you can rewrite your code to be completely free of calls to vwait and have it work.
msw ok, what I called "tokenize".
msw er wait, I need one vwait at least ... (should work in tcl, too)
dkf If it is to work in standard Tcl, then you add a vwait forever in your main script
msw hmm but it's used like ..
msw < synchronous > ... use "bgexec" < .. more synchronous >
suchenwi postprocess bexec $input
msw ah ok, when I have to pass a script anyways ..
msw grmpf, mental recursion coming up.
msw it's actually <synch> (bgexec + synch) * n <synch> .. the * n is going to be fun.
msw pity I need both stdout and stderr, split and "live" while the command runs... minus that requirement, would be dead easy..
* msw figures that if it was obvious, wouldn't need CODE to realise.
dkf Sorry it took so long to write, but there was quite a bit to read ;)
dkf s/read/write
nem is the if {gets $f line == -1} { .. ok? I usually use if {catch {gets $f} line || eof $f} { ...
nem I seem to remember being taught to do that a long long time ago
dkf the gets line really is ok
dkf Since we're not blocking
nem Ah, ok
dkf And eof results in -1 result
msw was just thinking the same, but the old version used read first then checked with eof
dkf Technically, it's the close that could fail
dkf The gets version should work as well
nem Thinking about it the catch/eof combo might possibly fail to read the last line
dkf And I'm singularly unworried about malicious code; we're running a general command here after all ;)
dkf nem: Do you do incomplete last lines in your files?
nem not usually - but it's a possibility
dkf Notice the total lack of vwait in that code
nem vwait is eeeevil.
msw yeah, I noticed.
msw if it's eeeevil, tcl should kick it out :)
dkf vwait is only evil if you have reentrant code, but reentrant vwait is a real problem
dkf Sometimes you need it
dkf (e.g. modal dialog boxes)
nem modal dialog boxes are eeevil
dkf Won't argue with that ;)
nem tk_messageBox usage of vwait internally has bitten me several times (to the point where I don't use it any more)
dkf But sometimes the client wants their soul sold to the devil anyway
dkf It's OK, but you have to take care to stop *two* simultaneous runs of the same code
msw vwait should be expanded so that this works too :)
MSW (broken) version
set ::bexec_runs 0 set ::bexec_result "" set ::bnum 0 proc bexec what { global bexec_runs bexec_result bnum incr bnum set fname [file join $::env(HOME) tmp cmd_run_lock_$::env(USER)_[pid]] while {[catch {set fp [open $fname {WRONLY CREAT EXCL}]}]} { puts stderr "($bnum) Lock busy, will retry." after 200 "set ::retry 0" vwait ::retry } puts stderr "($bnum)Got the Lock for \"[string range $what 0 99]...\"." # reset results set ::bexec_result "" puts stderr "Executing $what..." set bexec_runs 1 set f [open |$what] #fconfigure $f -translation none fileevent $f readable [list bexec_read $f] vwait bexec_runs file delete -force [file join $::env(HOME) tmp cmd_run_lock_$::env(USER)_[pid]] puts stderr "($bnum)Released the Lock for \"[string range $what 0 99]...\"." return $bexec_result } proc bexec_read {f} { set data [read $f] if [eof $f] { close $f set ::bexec_runs 0 } append ::bexec_result $data } # demo1: set tfile [open tst.sh w] puts $tfile {#!/bin/sh echo "output1" sleep 3 echo "output2"} close $tfile puts [bexec "sh tst.sh"] puts [bexec "sh tst.sh"] ;# <-- this one will wait. # demo2: pack [button .b -text "run" -command {puts [bexec "sh tst.sh"]}] -expand 1 -fill both
Output:
(1)Got the Lock for "sh tst.sh...". (1)Executing sh tst.sh... (1)Released the Lock for "sh tst.sh...". output1 output2 (2)Got the Lock for "sh tst.sh...". (2)Executing sh tst.sh... (2)Released the Lock for "sh tst.sh...". output1 output2 (3)Got the Lock for "sh tst.sh...". (3)Executing sh tst.sh... (3)Released the Lock for "sh tst.sh...". output1 output2 (4)Got the Lock for "sh tst.sh...". (4)Executing sh tst.sh... (5) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. (6) Lock busy, will retry. # ad infitum... lockfile never goes away.
DKF VERSION
set ::bexec_runs 0 set ::bexec_result "" set ::bnum 0 proc bexec {what handler {uid -1}} { global bexec_accum bnum if {$uid == -1} { set uid [incr bnum] } set fname [file join ~ tmp cmd_run_lock_[pid]] if {[catch {open $fname {WRONLY CREAT EXCL}} fp]} { puts stderr "($uid) Lock busy, will retry." after 200 [list bexec $what $handler $uid] return } set f [open |$what] set bexec_accum($uid) {} fileevent $f readable [list bexec_read $f $uid $fname $handler] } proc bexec_read {f uid fname handler} { global bexec_accum if {[gets $f line] == -1} { close $f file delete -force $fname uplevel #0 $handler [list $bexec_accum($uid)] unset bexec_accum($uid) return } append bexec_accum($uid) $line \n } # demo1: set tfile [open tst.sh w] puts $tfile {#!/bin/sh echo "output1" sleep 3 echo "output2"} close $tfile bexec "sh tst.sh" puts bexec "sh tst.sh" puts ;# <-- this one will wait. # demo2: pack [button .b -text "run" -command {bexec "sh tst.sh" puts}] -expand 1 -fill both
Output:
(2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. (2) Lock busy, will retry. output1 output2 output1 output2 (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. (4) Lock busy, will retry. output1 output2 (5) Lock busy, will retry. (5) Lock busy, will retry. (5) Lock busy, will retry. output1 output2 output1 output2