00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015
00016
00017
00018
00019
00020
00021
00022
00023
00024
00025
00026
00027 package require Tcl 8.3
00028 package require snit ;
00029
00030 namespace ::comm {
00031 namespace export comm comm_send
00032
00033 variable comm
00034 array comm = {}
00035
00036 if {![info exists comm(chans)]} {
00037 array comm = {
00038 debug 0 chans {} localhost 127.0.0.1
00039 connecting,hook 1
00040 connected,hook 1
00041 incoming,hook 1
00042 eval,hook 1
00043 callback,hook 1
00044 reply,hook 1
00045 lost,hook 1
00046 offerVers {3 2}
00047 acceptVers {3 2}
00048 defVers 2
00049 defaultEncoding "utf-8"
00050 defaultSilent 0
00051 }
00052 comm = (lastport) [expr {[pid] % 32768 + 9999}]
00053
00054 foreach comm(_x) $comm(acceptVers) {
00055 comm = ($comm(_x),vers) 1
00056 }
00057 catch {un comm = (_x)}
00058 }
00059
00060
00061
00062
00063
00064
00065
00066
00067
00068
00069
00070
00071
00072
00073
00074
00075
00076
00077
00078
00079
00080
00081
00082
00083
00084
00085
00086
00087
00088
00089
00090 if {0} {
00091
00092
00093 global errorInfo errorCode
00094 code = [catch [concat commSend $args] res]
00095 return -code $code -errorinfo $errorInfo -errorcode $errorCode $res
00096 }
00097 }
00098
00099
00100
00101
00102
00103
00104
00105
00106
00107
00108
00109
00110
00111 ret ::comm::comm_send () {
00112 proc send {args} {
00113 # Use pure lists to speed this up.
00114 uplevel 1 [linsert $args 0 ::comm::comm send]
00115 }
00116 rename winfo tk_winfo
00117 proc winfo {cmd args} {
00118 if {![string match in* $cmd]} {
00119 # Use pure lists to speed this up ...
00120 return [uplevel 1 [linsert $args 0 tk_winfo $cmd]]
00121 }
00122 return [::comm::comm interps]
00123 }
00124 proc ::comm::comm_send {} {}
00125 }
00126
00127
00128
00129
00130
00131
00132
00133
00134
00135
00136
00137
00138
00139
00140 ret ::comm::comm (type cmd , type args) {
00141 set method [info commands ::comm::comm_cmd_$cmd*]
00142
00143 if {[llength $method] == 1} {
00144 set chan ::comm::comm; # passed to methods
00145 return [uplevel 1 [linsert $args 0 $method $chan]]
00146 } else {
00147 foreach c [info commands ::comm::comm_cmd_*] {
00148 # remove ::comm::comm_cmd_
00149 lappend cmds [string range $c 17 end]
00150 }
00151 return -code error "unknown subcommand \"$cmd\":\
00152 must be one of [join [lsort $cmds] {, }]"
00153 }
00154 }
00155
00156 ret ::comm::comm_cmd_connect (type chan , type args) {
00157 uplevel 1 [linsert $args 0 [namespace current]::commConnect $chan]
00158 }
00159 ret ::comm::comm_cmd_self (type chan , type args) {
00160 variable comm
00161 return $comm($chan,port)
00162 }
00163 ret ::comm::comm_cmd_channels (type chan , type args) {
00164 variable comm
00165 return $comm(chans)
00166 }
00167 ret ::comm::comm_cmd_configure (type chan , type args) {
00168 uplevel 1 [linsert $args 0 [namespace current]::commConfigure $chan 0]
00169 }
00170 ret ::comm::comm_cmd_ids (type chan , type args) {
00171 variable comm
00172 set res $comm($chan,port)
00173 foreach {i id} [array get comm $chan,fids,*] {lappend res $id}
00174 return $res
00175 }
00176 interp alias {} ::comm::comm_cmd_interps {} ::comm::comm_cmd_ids
00177 ret ::comm::comm_cmd_remoteid (type chan , type args) {
00178 variable comm
00179 if {[info exists comm($chan,remoteid)]} {
00180 set comm($chan,remoteid)
00181 } else {
00182 return -code error "No remote commands processed yet"
00183 }
00184 }
00185 ret ::comm::comm_cmd_debug (type chan , type bool) {
00186 variable comm
00187 return [set comm(debug) [string is true -strict $bool]]
00188 }
00189
00190
00191
00192
00193
00194
00195
00196
00197 ret ::comm::comm_cmd_return_async (type chan) {
00198 variable comm
00199
00200 if {![info exists comm(current,async)]} {
00201 return -code error "No remote commands processed yet"
00202 }
00203 if {$comm(current,async)} {
00204 # Return the same future which were generated by the first
00205 # call.
00206 return $comm(current,state)
00207 }
00208
00209 foreach {cmdchan cmdfid cmd ser} $comm(current,state) break
00210
00211 # Assert that the channel performing the request and the channel
00212 # the current command came in are identical. Panic if not.
00213
00214 if {![string equal $chan $cmdchan]} {
00215 return -code error "Internal error: Trying to activate\
00216 async return for a command on a different channel"
00217 }
00218
00219 # Establish the future for the command and return a handle for
00220 # it. Remember the outstanding futures for a peer, so that we can
00221 # cancel them if the peer is lost before the promise implicit in
00222 # the future is redeemed.
00223
00224 set future [::comm::future %AUTO% $chan $cmdfid $cmd $ser]
00225
00226 lappend comm(future,fid,$cmdfid) $future
00227 set comm(current,state) $future
00228
00229 # Mark the current command as using async result return. We do
00230 # this last to ensure that all errors in this method are reported
00231 # through the regular channels.
00232
00233 set comm(current,async) 1
00234
00235 return $future
00236 }
00237
00238
00239
00240
00241
00242
00243
00244
00245
00246
00247
00248
00249 ret ::comm::comm_cmd_hook (type chan , type hook , optional script =+) {
00250 variable comm
00251 if {![info exists comm($hook,hook)]} {
00252 return -code error "Unknown hook invoked"
00253 }
00254 if {!$comm($hook,hook)} {
00255 return -code error "Unimplemented hook invoked"
00256 }
00257 if {[string equal + $script]} {
00258 if {[catch {set comm($chan,hook,$hook)} ret]} {
00259 return
00260 }
00261 return $ret
00262 }
00263 if {[string match +* $script]} {
00264 append comm($chan,hook,$hook) \n [string range $script 1 end]
00265 } else {
00266 set comm($chan,hook,$hook) $script
00267 }
00268 return
00269 }
00270
00271
00272
00273
00274
00275
00276
00277
00278
00279
00280
00281
00282 ret ::comm::comm_cmd_abort (type chan) {
00283 variable comm
00284
00285 foreach pid [array names comm $chan,peers,*] {
00286 commLostConn $chan $comm($pid) "Connection aborted by request"
00287 }
00288 }
00289
00290
00291
00292
00293
00294
00295
00296
00297
00298
00299
00300
00301 ret ::comm::comm_cmd_destroy (type chan) {
00302 variable comm
00303 catch {close $comm($chan,socket)}
00304 comm_cmd_abort $chan
00305 if {$comm($chan,interp) != {}} {
00306 interp delete $comm($chan,interp)
00307 }
00308 catch {unset comm($chan,port)}
00309 catch {unset comm($chan,local)}
00310 catch {unset comm($chan,silent)}
00311 catch {unset comm($chan,interp)}
00312 catch {unset comm($chan,events)}
00313 catch {unset comm($chan,socket)}
00314 catch {unset comm($chan,remoteid)}
00315 unset comm($chan,serial)
00316 unset comm($chan,chan)
00317 unset comm($chan,encoding)
00318 unset comm($chan,listen)
00319 # array unset would have been nicer, but is not available in
00320 # 8.2/8.3
00321 foreach pattern {hook,* interp,* vers,*} {
00322 foreach k [array names comm $chan,$pattern] {unset comm($k)}
00323 }
00324 set pos [lsearch -exact $comm(chans) $chan]
00325 set comm(chans) [lreplace $comm(chans) $pos $pos]
00326 if {![string equal ::comm::comm $chan]} {
00327 rename $chan {}
00328 }
00329 return
00330 }
00331
00332
00333
00334
00335
00336
00337
00338
00339
00340
00341
00342
00343 ret ::comm::comm_cmd_shutdown (type chan , type id) {
00344 variable comm
00345
00346 if {[info exists comm($chan,peers,$id)]} {
00347 commLostConn $chan $comm($chan,peers,$id) \
00348 "Connection shutdown by request"
00349 }
00350 }
00351
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364 ret ::comm::comm_cmd_new (type chan , type ch , type args) {
00365 variable comm
00366
00367 if {[lsearch -exact $comm(chans) $ch] >= 0} {
00368 return -code error "Already existing channel: $ch"
00369 }
00370 if {([llength $args] % 2) != 0} {
00371 return -code error "Must have an even number of config arguments"
00372 }
00373 # ensure that the new channel name is fully qualified
00374 set ch ::[string trimleft $ch :]
00375 if {[string equal ::comm::comm $ch]} {
00376 # allow comm to be recreated after destroy
00377 } elseif {[string equal $ch [info commands $ch]]} {
00378 return -code error "Already existing command: $ch"
00379 } else {
00380 # Create the new channel with fully qualified proc name
00381 proc $ch {cmd args} {
00382 set method [info commands ::comm::comm_cmd_$cmd*]
00383
00384 if {[llength $method] == 1} {
00385 # this should work right even if aliased
00386 # it is passed to methods to identify itself
00387 set chan [namespace origin [lindex [info level 0] 0]]
00388 return [uplevel 1 [linsert $args 0 $method $chan]]
00389 } else {
00390 foreach c [info commands ::comm::comm_cmd_*] {
00391 # remove ::comm::comm_cmd_
00392 lappend cmds [string range $c 17 end]
00393 }
00394 return -code error "unknown subcommand \"$cmd\":\
00395 must be one of [join [lsort $cmds] {, }]"
00396 }
00397 }
00398 }
00399 lappend comm(chans) $ch
00400 set chan $ch
00401 set comm($chan,serial) 0
00402 set comm($chan,chan) $chan
00403 set comm($chan,port) 0
00404 set comm($chan,listen) 0
00405 set comm($chan,socket) ""
00406 set comm($chan,local) 1
00407 set comm($chan,silent) $comm(defaultSilent)
00408 set comm($chan,encoding) $comm(defaultEncoding)
00409 set comm($chan,interp) {}
00410 set comm($chan,events) {}
00411
00412 if {[llength $args] > 0} {
00413 if {[catch [linsert $args 0 commConfigure $chan 1] err]} {
00414 comm_cmd_destroy $chan
00415 return -code error $err
00416 }
00417 }
00418 return $chan
00419 }
00420
00421
00422
00423
00424
00425
00426
00427
00428
00429
00430
00431
00432 ret ::comm::comm_cmd_send (type chan , type args) {
00433 variable comm
00434
00435 set cmd send
00436
00437 # args = ?-async | -command command? id cmd ?arg arg ...?
00438 set i 0
00439 set opt [lindex $args $i]
00440 if {[string equal -async $opt]} {
00441 set cmd async
00442 incr i
00443 } elseif {[string equal -command $opt]} {
00444 set cmd command
00445 set callback [lindex $args [incr i]]
00446 incr i
00447 }
00448 # args = id cmd ?arg arg ...?
00449
00450 set id [lindex $args $i]
00451 incr i
00452 set args [lrange $args $i end]
00453
00454 if {![info complete $args]} {
00455 return -code error "Incomplete command"
00456 }
00457 if {![llength $args]} {
00458 return -code error \
00459 "wrong # args: should be \"send ?-async? id arg ?arg ...?\""
00460 }
00461 if {[catch {commConnect $chan $id} fid]} {
00462 return -code error "Connect to remote failed: $fid"
00463 }
00464
00465 set ser [incr comm($chan,serial)]
00466 # This is unneeded - wraps from 2147483647 to -2147483648
00467 ### if {$comm($chan,serial) == 0x7fffffff} {set comm($chan,serial) 0}
00468
00469 commDebug {puts stderr "<$chan> send <[list [list $cmd $ser $args]]>"}
00470
00471 # The double list assures that the command is a single list when read.
00472 puts $fid [list [list $cmd $ser $args]]
00473 flush $fid
00474
00475 commDebug {puts stderr "<$chan> sent"}
00476
00477 # wait for reply if so requested
00478
00479 if {[string equal command $cmd]} {
00480 # In this case, don't wait on the command result. Set the callback
00481 # in the return and that will be invoked by the result.
00482 lappend comm($chan,pending,$id) [list $ser callback]
00483 set comm($chan,return,$ser) $callback
00484 return $ser
00485 } elseif {[string equal send $cmd]} {
00486 upvar 0 comm($chan,pending,$id) pending ;# shorter variable name
00487
00488 lappend pending $ser
00489 set comm($chan,return,$ser) "" ;# we're waiting
00490
00491 commDebug {puts stderr "<$chan> --<<waiting $ser>>--"}
00492 vwait ::comm::comm($chan,result,$ser)
00493
00494 # if connection was lost, pending is gone
00495 if {[info exists pending]} {
00496 set pos [lsearch -exact $pending $ser]
00497 set pending [lreplace $pending $pos $pos]
00498 }
00499
00500 commDebug {
00501 puts stderr "<$chan> result\
00502 <$comm($chan,return,$ser);$comm($chan,result,$ser)>"
00503 }
00504
00505 array set return $comm($chan,return,$ser)
00506 unset comm($chan,return,$ser)
00507 set thisres $comm($chan,result,$ser)
00508 unset comm($chan,result,$ser)
00509 switch -- $return(-code) {
00510 "" - 0 {return $thisres}
00511 1 {
00512 return -code $return(-code) \
00513 -errorinfo $return(-errorinfo) \
00514 -errorcode $return(-errorcode) \
00515 $thisres
00516 }
00517 default {return -code $return(-code) $thisres}
00518 }
00519 }
00520 }
00521
00522 /* */
00523
00524 /* ::comm::commDebug --*/
00525 /* */
00526 /* Internal command. Conditionally executes debugging*/
00527 /* statements. Currently this are only puts commands logging the*/
00528 /* various interactions. These could be replaced with calls into*/
00529 /* the 'log' module.*/
00530 /* */
00531 /* Arguments:*/
00532 /* arg Tcl script to execute.*/
00533 /* */
00534 /* Results:*/
00535 /* None.*/
00536
00537 ret ::comm::commDebug (type cmd) {
00538 variable comm
00539 if {$comm(debug)} {
00540 uplevel 1 $cmd
00541 }
00542 }
00543
00544 /* ::comm::commConfVars --*/
00545 /* */
00546 /* Internal command. Used to declare configuration options.*/
00547 /* */
00548 /* Arguments:*/
00549 /* v Name of configuration option.*/
00550 /* t Default value.*/
00551 /* */
00552 /* Results:*/
00553 /* None.*/
00554
00555 ret ::comm::commConfVars (type v , type t) {
00556 variable comm
00557 set comm($v,var) $t
00558 set comm(vars) {}
00559 foreach c [array names comm *,var] {
00560 lappend comm(vars) [lindex [split $c ,] 0]
00561 }
00562 return
00563 }
00564 ::comm::commConfVars port p
00565 ::comm::commConfVars local b
00566 ::comm::commConfVars listen b
00567 ::comm::commConfVars socket ro
00568 ::comm::commConfVars chan ro
00569 ::comm::commConfVars serial ro
00570 ::comm::commConfVars encoding enc
00571 ::comm::commConfVars silent b
00572 ::comm::commConfVars interp interp
00573 ::comm::commConfVars events ev
00574
00575 /* ::comm::commConfigure --*/
00576 /* */
00577 /* Internal command. Implements 'comm configure'.*/
00578 /* */
00579 /* Arguments:*/
00580 /* force Boolean flag. If set the socket is reinitialized.*/
00581 /* args New configuration, as -option value pairs.*/
00582 /* */
00583 /* Results:*/
00584 /* None.*/
00585
00586 ret ::comm::commConfigure (type chan , optional force =0 , type args) {
00587 variable comm
00588
00589 # query
00590 if {[llength $args] == 0} {
00591 foreach v $comm(vars) {lappend res -$v $comm($chan,$v)}
00592 return $res
00593 } elseif {[llength $args] == 1} {
00594 set arg [lindex $args 0]
00595 set var [string range $arg 1 end]
00596 if {![string match -* $arg] || ![info exists comm($var,var)]} {
00597 return -code error "Unknown configuration option: $arg"
00598 }
00599 return $comm($chan,$var)
00600 }
00601
00602 # set
00603 set opt 0
00604 foreach arg $args {
00605 incr opt
00606 if {[info exists skip]} {unset skip; continue}
00607 set var [string range $arg 1 end]
00608 if {![string match -* $arg] || ![info exists comm($var,var)]} {
00609 return -code error "Unknown configuration option: $arg"
00610 }
00611 set optval [lindex $args $opt]
00612 switch $comm($var,var) {
00613 ev {
00614 if {![string equal $optval ""]} {
00615 set err 0
00616 if {[catch {
00617 foreach ev $optval {
00618 if {[lsearch -exact {connecting connected incoming eval callback reply lost} $ev] < 0} {
00619 set err 1
00620 break
00621 }
00622 }
00623 }]} {
00624 set err 1
00625 }
00626 if {$err} {
00627 return -code error \
00628 "Non-event to configuration option: -$var"
00629 }
00630 }
00631 # FRINK: nocheck
00632 set $var $optval
00633 set skip 1
00634 }
00635 interp {
00636 if {
00637 ![string equal $optval ""] &&
00638 ![interp exists $optval]
00639 } {
00640 return -code error \
00641 "Non-interpreter to configuration option: -$var"
00642 }
00643 # FRINK: nocheck
00644 set $var $optval
00645 set skip 1
00646 }
00647 b {
00648 # FRINK: nocheck
00649 set $var [string is true -strict $optval]
00650 set skip 1
00651 }
00652 v {
00653 # FRINK: nocheck
00654 set $var $optval
00655 set skip 1
00656 }
00657 p {
00658 if {
00659 ![string equal $optval ""] &&
00660 ![string is integer $optval]
00661 } {
00662 return -code error \
00663 "Non-port to configuration option: -$var"
00664 }
00665 # FRINK: nocheck
00666 set $var $optval
00667 set skip 1
00668 }
00669 i {
00670 if {![string is integer $optval]} {
00671 return -code error \
00672 "Non-integer to configuration option: -$var"
00673 }
00674 # FRINK: nocheck
00675 set $var $optval
00676 set skip 1
00677 }
00678 enc {
00679 # to configure encodings, we will need to extend the
00680 # protocol to allow for handshaked encoding changes
00681 return -code error "encoding not configurable"
00682 if {[lsearch -exact [encoding names] $optval] == -1} {
00683 return -code error \
00684 "Unknown encoding to configuration option: -$var"
00685 }
00686 set $var $optval
00687 set skip 1
00688 }
00689 ro { return -code error "Readonly configuration option: -$var" }
00690 }
00691 }
00692 if {[info exists skip]} {
00693 return -code error "Missing value for option: $arg"
00694 }
00695
00696 foreach var {port listen local} {
00697 # FRINK: nocheck
00698 if {[info exists $var] && [set $var] != $comm($chan,$var)} {
00699 incr force
00700 # FRINK: nocheck
00701 set comm($chan,$var) [set $var]
00702 }
00703 }
00704
00705 foreach var {silent interp events} {
00706 # FRINK: nocheck
00707 if {[info exists $var] && ([set $var] != $comm($chan,$var))} {
00708 # FRINK: nocheck
00709 set comm($chan,$var) [set ip [set $var]]
00710 if {[string equal $var "interp"] && ($ip != "")} {
00711 # Interrogate the interp about its capabilities.
00712 #
00713 # Like: set, array set, uplevel present ?
00714 # Or: The above, hidden ?
00715 #
00716 # This is needed to decide how to execute hook scripts
00717 # and regular scripts in this interpreter.
00718 set comm($chan,interp,set) [Capability $ip set]
00719 set comm($chan,interp,aset) [Capability $ip array]
00720 set comm($chan,interp,upl) [Capability $ip uplevel]
00721 }
00722 }
00723 }
00724
00725 if {[info exists encoding] &&
00726 ![string equal $encoding $comm($chan,encoding)]} {
00727 # This should not be entered yet
00728 set comm($chan,encoding) $encoding
00729 fconfigure $comm($chan,socket) -encoding $encoding
00730 foreach {i sock} [array get comm $chan,peers,*] {
00731 fconfigure $sock -encoding $encoding
00732 }
00733 }
00734
00735 # do not re-init socket
00736 if {!$force} {return ""}
00737
00738 # User is recycling object, possibly to change from local to !local
00739 if {[info exists comm($chan,socket)]} {
00740 comm_cmd_abort $chan
00741 catch {close $comm($chan,socket)}
00742 unset comm($chan,socket)
00743 }
00744
00745 set comm($chan,socket) ""
00746 if {!$comm($chan,listen)} {
00747 set comm($chan,port) 0
00748 return ""
00749 }
00750
00751 if {[info exists port] && [string equal "" $comm($chan,port)]} {
00752 set nport [incr comm(lastport)]
00753 } else {
00754 set userport 1
00755 set nport $comm($chan,port)
00756 }
00757 while {1} {
00758 set cmd [list socket -server [list ::comm::commIncoming $chan]]
00759 if {$comm($chan,local)} {
00760 lappend cmd -myaddr $comm(localhost)
00761 }
00762 lappend cmd $nport
00763 if {![catch $cmd ret]} {
00764 break
00765 }
00766 if {[info exists userport] || ![string match "*already in use" $ret]} {
00767 # don't eradicate the class
00768 if {![string equal ::comm::comm $chan]} {
00769 rename $chan {}
00770 }
00771 return -code error $ret
00772 }
00773 set nport [incr comm(lastport)]
00774 }
00775 set comm($chan,socket) $ret
00776 fconfigure $ret -translation lf -encoding $comm($chan,encoding)
00777
00778 # If port was 0, system allocated it for us
00779 set comm($chan,port) [lindex [fconfigure $ret -sockname] 2]
00780 return ""
00781 }
00782
00783
00784
00785
00786
00787
00788
00789 ret ::comm::Capability (type interp , type cmd) {
00790 if {[lsearch -exact [interp hidden $interp] $cmd] >= 0} {
00791 # The command is present, although hidden.
00792 return hidden
00793 }
00794
00795 # The command is not a hidden command. Use info to determine if it
00796 # is present as regular command. Note that the 'info' command
00797 # itself might be hidden.
00798
00799 if {[catch {
00800 set has [llength [interp eval $interp [list info commands $cmd]]]
00801 }] && [catch {
00802 set has [llength [interp invokehidden $interp info commands $cmd]]
00803 }]} {
00804 # Unable to interogate the interpreter in any way. Assume that
00805 # the command is not present.
00806 set has 0
00807 }
00808 return [expr {$has ? "ok" : "no"}]
00809 }
00810
00811
00812
00813
00814
00815
00816
00817
00818
00819
00820
00821
00822
00823
00824 ret ::comm::commConnect (type chan , type id) {
00825 variable comm
00826
00827 commDebug {puts stderr "<$chan> commConnect $id"}
00828
00829 # process connecting hook now
00830 CommRunHook $chan connecting
00831
00832 if {[info exists comm($chan,peers,$id)]} {
00833 return $comm($chan,peers,$id)
00834 }
00835 if {[lindex $id 0] == 0} {
00836 return -code error "Remote comm is anonymous; cannot connect"
00837 }
00838
00839 if {[llength $id] > 1} {
00840 set host [lindex $id 1]
00841 } else {
00842 set host $comm(localhost)
00843 }
00844 set port [lindex $id 0]
00845 set fid [socket $host $port]
00846
00847 # process connected hook now
00848 if {[catch {
00849 CommRunHook $chan connected
00850 } err]} {
00851 global errorInfo
00852 set ei $errorInfo
00853 close $fid
00854 error $err $ei
00855 }
00856
00857 # commit new connection
00858 commNewConn $chan $id $fid
00859
00860 # send offered protocols versions and id to identify ourselves to remote
00861 puts $fid [list $comm(offerVers) $comm($chan,port)]
00862 set comm($chan,vers,$id) $comm(defVers) ;# default proto vers
00863 flush $fid
00864 return $fid
00865 }
00866
00867
00868
00869
00870
00871
00872
00873
00874
00875
00876
00877
00878
00879
00880
00881 ret ::comm::commIncoming (type chan , type fid , type addr , type remport) {
00882 variable comm
00883
00884 commDebug {puts stderr "<$chan> commIncoming $fid $addr $remport"}
00885
00886 # process incoming hook now
00887 if {[catch {
00888 CommRunHook $chan incoming
00889 } err]} {
00890 global errorInfo
00891 set ei $errorInfo
00892 close $fid
00893 error $err $ei
00894 }
00895
00896 # a list of offered proto versions is the first word of first line
00897 # remote id is the second word of first line
00898 # rest of first line is ignored
00899 set protoline [gets $fid]
00900 set offeredvers [lindex $protoline 0]
00901 set remid [lindex $protoline 1]
00902
00903 commDebug {puts stderr "<$chan> offered <$protoline>"}
00904
00905 # use the first supported version in the offered list
00906 foreach v $offeredvers {
00907 if {[info exists comm($v,vers)]} {
00908 set vers $v
00909 break
00910 }
00911 }
00912 if {![info exists vers]} {
00913 close $fid
00914 if {[info exists comm($chan,silent)] &&
00915 [string is true -strict $comm($chan,silent)]} then return
00916 error "Unknown offered protocols \"$protoline\" from $addr/$remport"
00917 }
00918
00919 # If the remote host addr isn't our local host addr,
00920 # then add it to the remote id.
00921 if {[string equal [lindex [fconfigure $fid -sockname] 0] $addr]} {
00922 set id $remid
00923 } else {
00924 set id [list $remid $addr]
00925 }
00926
00927 # Detect race condition of two comms connecting to each other
00928 # simultaneously. It is OK when we are talking to ourselves.
00929
00930 if {[info exists comm($chan,peers,$id)] && $id != $comm($chan,port)} {
00931
00932 puts stderr "commIncoming race condition: $id"
00933 puts stderr "peers=$comm($chan,peers,$id) port=$comm($chan,port)"
00934
00935 # To avoid the race, we really want to terminate one connection.
00936 # However, both sides are committed to using it.
00937 # commConnect needs to be synchronous and detect the close.
00938 # close $fid
00939 # return $comm($chan,peers,$id)
00940 }
00941
00942 # Make a protocol response. Avoid any temptation to use {$vers > 2}
00943 # - this forces forwards compatibility issues on protocol versions
00944 # that haven't been invented yet. DON'T DO IT! Instead, test for
00945 # each supported version explicitly. I.e., {$vers >2 && $vers < 5} is OK.
00946
00947 switch $vers {
00948 3 {
00949 # Respond with the selected version number
00950 puts $fid [list [list vers $vers]]
00951 flush $fid
00952 }
00953 }
00954
00955 # commit new connection
00956 commNewConn $chan $id $fid
00957 set comm($chan,vers,$id) $vers
00958 }
00959
00960
00961
00962
00963
00964
00965
00966
00967
00968
00969
00970
00971 ret ::comm::commNewConn (type chan , type id , type fid) {
00972 variable comm
00973
00974 commDebug {puts stderr "<$chan> commNewConn $id $fid"}
00975
00976 # There can be a race condition two where comms connect to each other
00977 # simultaneously. This code favors our outgoing connection.
00978
00979 if {[info exists comm($chan,peers,$id)]} {
00980 # abort this connection, use the existing one
00981 # close $fid
00982 # return -code return $comm($chan,peers,$id)
00983 } else {
00984 set comm($chan,pending,$id) {}
00985 set comm($chan,peers,$id) $fid
00986 }
00987 set comm($chan,fids,$fid) $id
00988 fconfigure $fid -translation lf -encoding $comm($chan,encoding) -blocking 0
00989 fileevent $fid readable [list ::comm::commCollect $chan $fid]
00990 }
00991
00992
00993
00994
00995
00996
00997
00998
00999
01000
01001
01002
01003
01004
01005 ret ::comm::commLostConn (type chan , type fid , type reason) {
01006 variable comm
01007
01008 commDebug {puts stderr "<$chan> commLostConn $fid $reason"}
01009
01010 catch {close $fid}
01011
01012 set id $comm($chan,fids,$fid)
01013
01014 # Invoke the callbacks of all commands which have such and are
01015 # still waiting for a response from the lost peer. Use an
01016 # appropriate error.
01017
01018 foreach s $comm($chan,pending,$id) {
01019 if {[string equal "callback" [lindex $s end]]} {
01020 set ser [lindex $s 0]
01021 if {[info exists comm($chan,return,$ser)]} {
01022 set args [list -id $id \
01023 -serial $ser \
01024 -chan $chan \
01025 -code -1 \
01026 -errorcode NONE \
01027 -errorinfo "" \
01028 -result $reason \
01029 ]
01030 if {[catch {uplevel \#0 $comm($chan,return,$ser) $args} err]} {
01031 commBgerror $err
01032 }
01033 }
01034 } else {
01035 set comm($chan,return,$s) {-code error}
01036 set comm($chan,result,$s) $reason
01037 }
01038 }
01039 unset comm($chan,pending,$id)
01040 unset comm($chan,fids,$fid)
01041 catch {unset comm($chan,peers,$id)} ;# race condition
01042 catch {unset comm($chan,buf,$fid)}
01043
01044 # Cancel all outstanding futures for requests which were made by
01045 # the lost peer, if there are any. This does not destroy
01046 # them. They will stay around until the long-running operations
01047 # they belong too kill them.
01048
01049 CancelFutures $fid
01050
01051 # process lost hook now
01052 catch {CommRunHook $chan lost}
01053
01054 return $reason
01055 }
01056
01057 ret ::comm::commBgerror (type err) {
01058 # SF Tcllib Patch #526499
01059 # (See http://sourceforge.net/tracker/?func=detail&aid=526499&group_id=12883&atid=312883
01060 # for initial request and comments)
01061 #
01062 # Error in async call. Look for [bgerror] to report it. Same
01063 # logic as in Tcl itself. Errors thrown by bgerror itself get
01064 # reported to stderr.
01065 if {[catch {bgerror $err} msg]} {
01066 puts stderr "bgerror failed to handle background error."
01067 puts stderr " Original error: $err"
01068 puts stderr " Error in bgerror: $msg"
01069 flush stderr
01070 }
01071 }
01072
01073
01074
01075
01076
01077
01078 ret ::comm::CancelFutures (type fid) {
01079 variable comm
01080 if {![info exists comm(future,fid,$fid)]} return
01081
01082 commDebug {puts stderr "\tCanceling futures: [join $comm(future,fid,$fid) \
01083 "\n\t : "]"}
01084
01085 foreach future $comm(future,fid,$fid) {
01086 $future Cancel
01087 }
01088
01089 unset comm(future,fid,$fid)
01090 return
01091 }
01092
01093
01094
01095
01096
01097
01098
01099
01100
01101
01102
01103
01104
01105
01106
01107
01108 ret ::comm::commCollect (type chan , type fid) {
01109 variable comm
01110 upvar #0 comm($chan,buf,$fid) data
01111
01112 # Tcl8 may return an error on read after a close
01113 if {[catch {read $fid} nbuf] || [eof $fid]} {
01114 commDebug {puts stderr "<$chan> collect/lost eof $fid = [eof $fid]"}
01115 commDebug {puts stderr "<$chan> collect/lost nbuf = <$nbuf>"}
01116 commDebug {puts stderr "<$chan> collect/lost [fconfigure $fid]"}
01117
01118 fileevent $fid readable {} ;# be safe
01119 commLostConn $chan $fid "target application died or connection lost"
01120 return
01121 }
01122 append data $nbuf
01123
01124 commDebug {puts stderr "<$chan> collect <$data>"}
01125
01126 # If data contains at least one complete command, we will
01127 # be able to take off the first element, which is a list holding
01128 # the command. This is true even if data isn't a well-formed
01129 # list overall, with unmatched open braces. This works because
01130 # each command in the protocol ends with a newline, thus allowing
01131 # lindex and lreplace to work.
01132 #
01133 # This isn't true with Tcl8.0, which will return an error until
01134 # the whole buffer is a valid list. This is probably OK, although
01135 # it could potentially cause a deadlock.
01136
01137 while {![catch {set cmd [lindex $data 0]}]} {
01138 commDebug {puts stderr "<$chan> cmd <$data>"}
01139 if {[string equal "" $cmd]} break
01140 if {[info complete $cmd]} {
01141 set data [lreplace $data 0 0]
01142 after idle \
01143 [list ::comm::commExec $chan $fid $comm($chan,fids,$fid) $cmd]
01144 }
01145 }
01146 }
01147
01148
01149
01150
01151
01152
01153
01154
01155
01156
01157
01158
01159
01160
01161
01162
01163 ret ::comm::commExec (type chan , type fid , type remoteid , type buf) {
01164 variable comm
01165
01166 # buffer should contain:
01167 # send # {cmd} execute cmd and send reply with serial #
01168 # async # {cmd} execute cmd but send no reply
01169 # reply # {cmd} execute cmd as reply to serial #
01170
01171 # these variables are documented in the hook interface
01172 set cmd [lindex $buf 0]
01173 set ser [lindex $buf 1]
01174 set buf [lrange $buf 2 end]
01175 set buffer [lindex $buf 0]
01176
01177 # Save remoteid for "comm remoteid". This will only be valid
01178 # if retrieved before any additional events occur on this channel.
01179 # N.B. we could have already lost the connection to remote, making
01180 # this id be purely informational!
01181 set comm($chan,remoteid) [set id $remoteid]
01182
01183 # Save state for possible async result generation
01184 AsyncPrepare $chan $fid $cmd $ser
01185
01186 commDebug {puts stderr "<$chan> exec <$cmd,$ser,$buf>"}
01187
01188 switch -- $cmd {
01189 send - async - command {}
01190 callback {
01191 if {![info exists comm($chan,return,$ser)]} {
01192 commDebug {puts stderr "<$chan> No one waiting for serial \"$ser\""}
01193 return
01194 }
01195
01196 # Decompose reply command to assure it only uses "return"
01197 # with no side effects.
01198
01199 array set return {-code "" -errorinfo "" -errorcode ""}
01200 set ret [lindex $buffer end]
01201 set len [llength $buffer]
01202 incr len -2
01203 foreach {sw val} [lrange $buffer 1 $len] {
01204 if {![info exists return($sw)]} continue
01205 set return($sw) $val
01206 }
01207
01208 catch {CommRunHook $chan callback}
01209
01210 # this wakes up the sender
01211 commDebug {puts stderr "<$chan> --<<wakeup $ser>>--"}
01212
01213 # the return holds the callback command
01214 # string map the optional %-subs
01215 set args [list -id $id \
01216 -serial $ser \
01217 -chan $chan \
01218 -code $return(-code) \
01219 -errorcode $return(-errorcode) \
01220 -errorinfo $return(-errorinfo) \
01221 -result $ret \
01222 ]
01223 set code [catch {uplevel \#0 $comm($chan,return,$ser) $args} err]
01224 catch {unset comm($chan,return,$ser)}
01225
01226 # remove pending serial
01227 upvar 0 comm($chan,pending,$id) pending
01228 if {[info exists pending]} {
01229 set pos [lsearch -exact $pending [list $ser callback]]
01230 if {$pos != -1} {
01231 set pending [lreplace $pending $pos $pos]
01232 }
01233 }
01234 if {$code} {
01235 commBgerror $err
01236 }
01237 return
01238 }
01239 reply {
01240 if {![info exists comm($chan,return,$ser)]} {
01241 commDebug {puts stderr "<$chan> No one waiting for serial \"$ser\""}
01242 return
01243 }
01244
01245 # Decompose reply command to assure it only uses "return"
01246 # with no side effects.
01247
01248 array set return {-code "" -errorinfo "" -errorcode ""}
01249 set ret [lindex $buffer end]
01250 set len [llength $buffer]
01251 incr len -2
01252 foreach {sw val} [lrange $buffer 1 $len] {
01253 if {![info exists return($sw)]} continue
01254 set return($sw) $val
01255 }
01256
01257 catch {CommRunHook $chan reply}
01258
01259 # this wakes up the sender
01260 commDebug {puts stderr "<$chan> --<<wakeup $ser>>--"}
01261 set comm($chan,result,$ser) $ret
01262 set comm($chan,return,$ser) [array get return]
01263 return
01264 }
01265 vers {
01266 set ::comm::comm($chan,vers,$id) $ser
01267 return
01268 }
01269 default {
01270 commDebug {puts stderr "<$chan> unknown command; discard \"$cmd\""}
01271 return
01272 }
01273 }
01274
01275 # process eval hook now
01276 set done 0
01277 set err 0
01278 if {[info exists comm($chan,hook,eval)]} {
01279 set err [catch {CommRunHook $chan eval} ret]
01280 commDebug {puts stderr "<$chan> eval hook res <$err,$ret>"}
01281 switch $err {
01282 1 {
01283 # error
01284 set done 1
01285 }
01286 2 - 3 {
01287 # return / break
01288 set err 0
01289 set done 1
01290 }
01291 }
01292 }
01293
01294 commDebug {puts stderr "<$chan> hook(eval) done=$done, err=$err"}
01295
01296 # exec command
01297 if {!$done} {
01298 commDebug {puts stderr "<$chan> exec ($buffer)"}
01299
01300 # Sadly, the uplevel needs to be in the catch to access the local
01301 # variables buffer and ret. These cannot simply be global because
01302 # commExec is reentrant (i.e., they could be linked to an allocated
01303 # serial number).
01304
01305 if {$comm($chan,interp) == {}} {
01306 # Main interpreter
01307 set thecmd [concat [list uplevel \#0] $buffer]
01308 set err [catch $thecmd ret]
01309 } else {
01310 # Redirect execution into the configured slave
01311 # interpreter. The exact command used depends on the
01312 # capabilities of the interpreter. A best effort is made
01313 # to execute the script in the global namespace.
01314 set interp $comm($chan,interp)
01315
01316 if {$comm($chan,interp,upl) == "ok"} {
01317 set thecmd [concat [list uplevel \#0] $buffer]
01318 set err [catch {interp eval $interp $thecmd} ret]
01319 } elseif {$comm($chan,interp,aset) == "hidden"} {
01320 set thecmd [linsert $buffer 0 interp invokehidden $interp uplevel \#0]
01321 set err [catch $thecmd ret]
01322 } else {
01323 set thecmd [concat [list interp eval $interp] $buffer]
01324 set err [catch $thecmd ret]
01325 }
01326 }
01327 }
01328
01329 # Check and handle possible async result generation.
01330 if {[AsyncCheck]} return
01331
01332 commSendReply $chan $fid $cmd $ser $err $ret
01333 return
01334 }
01335
01336
01337
01338
01339
01340
01341
01342
01343
01344
01345
01346
01347
01348
01349
01350
01351 ret ::comm::commSendReply (type chan , type fid , type cmd , type ser , type err , type ret) {
01352 variable comm
01353
01354 commDebug {puts stderr "<$chan> res <$err,$ret> /$cmd"}
01355
01356 # The double list assures that the command is a single list when read.
01357 if {[string equal send $cmd] || [string equal command $cmd]} {
01358 # The catch here is just in case we lose the target. Consider:
01359 # comm send $other comm send [comm self] exit
01360 catch {
01361 set return [list return -code $err]
01362 # send error or result
01363 if {$err == 1} {
01364 global errorInfo errorCode
01365 lappend return -errorinfo $errorInfo -errorcode $errorCode
01366 }
01367 lappend return $ret
01368 if {[string equal send $cmd]} {
01369 set reply reply
01370 } else {
01371 set reply callback
01372 }
01373 puts $fid [list [list $reply $ser $return]]
01374 flush $fid
01375 }
01376 commDebug {puts stderr "<$chan> reply sent"}
01377 }
01378
01379 if {$err == 1} {
01380 commBgerror $ret
01381 }
01382 commDebug {puts stderr "<$chan> exec complete"}
01383 return
01384 }
01385
01386 ret ::comm::CommRunHook (type chan , type event) {
01387 variable comm
01388
01389 # The documentation promises the hook scripts to have access to a
01390 # number of internal variables. For a regular hook we simply
01391 # execute it in the calling level to fulfill this. When the hook
01392 # is redirected into an interpreter however we do a best-effort
01393 # copying of the variable values into the interpreter. Best-effort
01394 # because the 'set' command may not be available in the
01395 # interpreter, not even hidden.
01396
01397 if {![info exists comm($chan,hook,$event)]} return
01398 set cmd $comm($chan,hook,$event)
01399 set interp $comm($chan,interp)
01400 commDebug {puts stderr "<$chan> hook($event) run <$cmd>"}
01401
01402 if {
01403 ($interp != {}) &&
01404 ([lsearch -exact $comm($chan,events) $event] >= 0)
01405 } {
01406 # Best-effort to copy the context into the interpreter for
01407 # access by the hook script.
01408 set vars {
01409 addr buffer chan cmd fid host
01410 id port reason remport ret var
01411 }
01412
01413 if {$comm($chan,interp,set) == "ok"} {
01414 foreach v $vars {
01415 upvar 1 $v V
01416 if {![info exists V]} continue
01417 interp eval $interp [list set $v $V]
01418 }
01419 } elseif {$comm($chan,interp,set) == "hidden"} {
01420 foreach v $vars {
01421 upvar 1 $v V
01422 if {![info exists V]} continue
01423 interp invokehidden $interp set $v $V
01424 }
01425 }
01426 upvar 1 return AV
01427 if {[info exists AV]} {
01428 if {$comm($chan,interp,aset) == "ok"} {
01429 interp eval $interp [list array set return [array get AV]]
01430 } elseif {$comm($chan,interp,aset) == "hidden"} {
01431 interp invokehidden $interp array set return [array get AV]
01432 }
01433 }
01434
01435 commDebug {puts stderr "<$chan> /interp $interp"}
01436 set code [catch {interp eval $interp $cmd} res]
01437 } else {
01438 commDebug {puts stderr "<$chan> /main"}
01439 set code [catch {uplevel 1 $cmd} res]
01440 }
01441
01442 # Perform the return code propagation promised
01443 # to the hook scripts.
01444 switch -exact -- $code {
01445 0 {}
01446 1 {
01447 return -errorinfo $::errorInfo -errorcode $::errorCode -code error $res
01448 }
01449 3 {return}
01450 4 {}
01451 default {return -code $code $res}
01452 }
01453 return
01454 }
01455
01456
01457
01458
01459
01460
01461
01462
01463
01464 ret ::comm::AsyncPrepare (type chan , type fid , type cmd , type ser) {
01465 variable comm
01466 set comm(current,async) 0
01467 set comm(current,state) [list $chan $fid $cmd $ser]
01468 return
01469 }
01470
01471 ret ::comm::AsyncCheck () {
01472 # Check if the executed command notified us of an async return. If
01473 # not we let the regular return processing handle the end of the
01474 # script. Otherwise we stop the caller from proceeding, preventing
01475 # a regular return.
01476
01477 variable comm
01478 if {!$comm(current,async)} {return 0}
01479 return 1
01480 }
01481
01482
01483
01484
01485
01486 ret comm::FutureDone (type future , type chan , type fid , type cmd , type sid , type rcode , type rvalue) {
01487 variable comm
01488 commSendReply $chan $fid $cmd $sid $rcode $rvalue
01489
01490 set pos [lsearch -exact $comm(future,fid,$fid) $future]
01491 set comm(future,fid,$fid) [lreplace $comm(future,fid,$fid) $pos $pos]
01492 return
01493 }
01494
01495
01496
01497
01498
01499
01500
01501
01502
01503
01504 ret ::comm::Vwait (type varname) {
01505 variable ::comm::comm
01506
01507 set hasstate [info exists comm(current,async)]
01508 set hasremote 0
01509 if {$hasstate} {
01510 set chan [lindex $comm(current,state) 0]
01511 set async $comm(current,async)
01512 set state $comm(current,state)
01513 set hasremote [info exists comm($chan,remoteid)]
01514 if {$hasremote} {
01515 set remoteid $comm($chan,remoteid)
01516 }
01517 }
01518
01519 set code [catch {uplevel 1 [list ::comm::VwaitOrig $varname]} res]
01520
01521 if {$hasstate} {
01522 set comm(current,async) $async
01523 set comm(current,state) $state
01524 }
01525 if {$hasremote} {
01526 set comm($chan,remoteid) $remoteid
01527 }
01528
01529 return -code $code $res
01530 }
01531
01532 ret ::comm::Update (type args) {
01533 variable ::comm::comm
01534
01535 set hasstate [info exists comm(current,async)]
01536 set hasremote 0
01537 if {$hasstate} {
01538 set chan [lindex $comm(current,state) 0]
01539 set async $comm(current,async)
01540 set state $comm(current,state)
01541
01542 set hasremote [info exists comm($chan,remoteid)]
01543 if {$hasremote} {
01544 set remoteid $comm($chan,remoteid)
01545 }
01546 }
01547
01548 set code [catch {uplevel 1 [linsert $args 0 ::comm::UpdateOrig]} res]
01549
01550 if {$hasstate} {
01551 set comm(current,async) $async
01552 set comm(current,state) $state
01553 }
01554 if {$hasremote} {
01555 set comm($chan,remoteid) $remoteid
01556 }
01557
01558 return -code $code $res
01559 }
01560
01561
01562
01563 ret ::comm::InitWrappers () {
01564 rename ::vwait ::comm::VwaitOrig
01565 rename ::comm::Vwait ::vwait
01566
01567 rename ::update ::comm::UpdateOrig
01568 rename ::comm::Update ::update
01569
01570 proc ::comm::InitWrappers {} {}
01571 return
01572 }
01573
01574
01575
01576
01577 snit::type comm::future {
01578 option -command -default {}
01579
01580 constructor {chan fid cmd ser} {
01581 xfid = $fid
01582 xcmd = $cmd
01583 xser = $ser
01584 xchan = $chan
01585 return
01586 }
01587
01588 destructor {
01589 if {!$canceled} {
01590 return -code error \
01591 "Illegal attempt to destroy unresolved future \"$self\""
01592 }
01593 }
01594
01595 ret return (type args) {
01596 # Syntax: | 0
01597 # : -code x | 2
01598 # : -code x val | 3
01599 # : val | 4
01600 # Allowing multiple -code settings, last one is taken.
01601
01602 set rcode 0
01603 set rvalue {}
01604
01605 while {[lindex $args 0] == "-code"} {
01606 set rcode [lindex $args 1]
01607 set args [lrange $args 2 end]
01608 }
01609 if {[llength $args] > 1} {
01610 return -code error "wrong\#args, expected \"?-code errcode? ?result?\""
01611 }
01612 if {[llength $args] == 1} {
01613 set rvalue [lindex $args 0]
01614 }
01615
01616 if {!$canceled} {
01617 comm::FutureDone $self $xchan $xfid $xcmd $xser $rcode $rvalue
01618 set canceled 1
01619 }
01620 # assert: canceled == 1
01621 $self destroy
01622 return
01623 }
01624
01625 variable xfid {}
01626 variable xcmd {}
01627 variable xser {}
01628 variable xchan {}
01629 variable canceled 0
01630
01631
01632
01633
01634 ret Cancel () {
01635 set canceled 1
01636 if {![llength $options(-command)]} {return}
01637 uplevel #0 [linsert $options(-command) end $self]
01638 return
01639 }
01640 }
01641
01642
01643
01644 ::comm::InitWrappers
01645
01646
01647
01648
01649
01650
01651 if {![info exists ::comm::comm(comm,port)]} {
01652 if {[string equal macintosh $tcl_platform(platform)]} {
01653 ::comm::comm new ::comm::comm -port 0 -local 0 -listen 1
01654 ::comm = ::comm(localhost) \
01655 [lindex [fconfigure $::comm::comm(::comm::comm,socket) -sockname] 0]
01656 ::comm::comm config -local 1
01657 } else {
01658 ::comm::comm new ::comm::comm -port 0 -local 1 -listen 1
01659 }
01660 }
01661
01662
01663 package provide comm 4.5.6
01664