MSW

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