comm.tcl

Go to the documentation of this file.
00001 /*  comm.tcl --*/
00002 /* */
00003 /*  socket-based 'send'ing of commands between interpreters.*/
00004 /* */
00005 /*  %%_OSF_FREE_COPYRIGHT_%%*/
00006 /*  Copyright (C) 1995-1998 The Open Group.   All Rights Reserved.*/
00007 /*  (Please see the file "comm.LICENSE" that accompanied this source,*/
00008 /*   or http://www.opengroup.org/www/dist_client/caubweb/COPYRIGHT.free.html)*/
00009 /*  Copyright (c) 2003-2007 ActiveState Corporation*/
00010 /* */
00011 /*  This is the 'comm' package written by Jon Robert LoVerso, placed*/
00012 /*  into its own namespace during integration into tcllib.*/
00013 /* */
00014 /*  Note that the actual code was changed in several places (Reordered,*/
00015 /*  eval speedup)*/
00016 /*  */
00017 /*  comm works just like Tk's send, except that it uses sockets.*/
00018 /*  These commands work just like "send" and "winfo interps":*/
00019 /* */
00020 /*      comm send ?-async? <id> <cmd> ?<arg> ...?*/
00021 /*      comm interps*/
00022 /* */
00023 /*  See the manual page comm.n for further details on this package.*/
00024 /* */
00025 /*  RCS: @(#) $Id: comm.tcl,v 1.30 2007/08/15 21:33:37 andreas_kupries Exp $*/
00026 
00027 package require Tcl 8.3
00028 package require snit ; /*  comm::future objects.*/
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     /*  fast check for acceptable versions*/
00054     foreach comm(_x) $comm(acceptVers) {
00055          comm = ($comm(_x),vers) 1
00056     }
00057     catch {un comm = (_x)}
00058     }
00059 
00060     /*  Class variables:*/
00061     /*  lastport        saves last default listening port allocated*/
00062     /*  debug           enable debug output*/
00063     /*  chans           list of allocated channels*/
00064     /*    future,fid,$fid         List of futures a specific peer is waiting for.*/
00065     /* */
00066     /*  Channel instance variables:*/
00067     /*  comm()*/
00068     /*  $ch,port        listening port (our id)*/
00069     /*  $ch,socket      listening socket*/
00070     /*    $ch,silent      boolean to indicate whether to throw error on*/
00071     /*                    protocol negotiation failure*/
00072     /*  $ch,local       boolean to indicate if port is local*/
00073     /*  $ch,interp      interpreter to run received scripts in.*/
00074     /*              If not empty we own it! = We destroy it*/
00075     /*              with the channel*/
00076     /*  $ch,events      List of hoks to run in the 'interp', if defined*/
00077     /*  $ch,serial      next serial number for commands*/
00078     /* */
00079     /*  $ch,hook,$hook      script for hook $hook*/
00080     /* */
00081     /*  $ch,peers,$id       open connections to peers; ch,id=>fid*/
00082     /*  $ch,fids,$fid       reverse mapping for peers; ch,fid=>id*/
00083     /*  $ch,vers,$id        negotiated protocol version for id*/
00084     /*  $ch,pending,$id     list of outstanding send serial numbers for id*/
00085     /* */
00086     /*  $ch,buf,$fid        buffer to collect incoming data*/
00087     /*  $ch,result,$serial  result value set here to wake up sender*/
00088     /*  $ch,return,$serial  return codes to go along with result*/
00089 
00090     if {0} {
00091     /*  Propagate result, code, and errorCode.  Can't just eval*/
00092     /*  otherwise TCL_BREAK gets turned into TCL_ERROR.*/
00093     global errorInfo errorCode
00094      code =  [catch [concat commSend $args] res]
00095     return -code $code -errorinfo $errorInfo -errorcode $errorCode $res
00096     }
00097 }
00098 
00099 /*  ::comm::comm_send --*/
00100 /* */
00101 /*  Convenience command. Replaces Tk 'send' and 'winfo' with*/
00102 /*  versions using the 'comm' variants. Multiple calls are*/
00103 /*  allowed, only the first one will have an effect.*/
00104 /* */
00105 /*  Arguments:*/
00106 /*  None.*/
00107 /* */
00108 /*  Results:*/
00109 /*  None.*/
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 /*  ::comm::comm --*/
00128 /* */
00129 /*  See documentation for public methods of "comm".*/
00130 /*  This procedure is followed by the definition of*/
00131 /*  the public methods themselves.*/
00132 /* */
00133 /*  Arguments:*/
00134 /*  cmd Invoked method*/
00135 /*  args    Arguments to method.*/
00136 /* */
00137 /*  Results:*/
00138 /*  As of the invoked method.*/
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 /*  API: Setup async result generation for a remotely invoked command.*/
00192 
00193 /*  (future,fid,<fid>) -> list (future)*/
00194 /*  (current,async)    -> bool (default 0) */
00195 /*  (current,state)    -> list (chan fid cmd ser)*/
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 /*  hook --*/
00239 /* */
00240 /*  Internal command. Implements 'comm hook'.*/
00241 /* */
00242 /*  Arguments:*/
00243 /*  hook    hook to modify*/
00244 /*  script  Script to add/remove to/from the hook*/
00245 /* */
00246 /*  Results:*/
00247 /*  None.*/
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 /*  abort --*/
00272 /* */
00273 /*  Close down all peer connections.*/
00274 /*  Implements the 'comm abort' method.*/
00275 /* */
00276 /*  Arguments:*/
00277 /*  None.*/
00278 /* */
00279 /*  Results:*/
00280 /*  None.*/
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 /*  destroy --*/
00291 /* */
00292 /*  Destroy the channel invoking it.*/
00293 /*  Implements the 'comm destroy' method.*/
00294 /* */
00295 /*  Arguments:*/
00296 /*  None.*/
00297 /* */
00298 /*  Results:*/
00299 /*  None.*/
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 /*  shutdown --*/
00333 /* */
00334 /*  Close down a peer connection.*/
00335 /*  Implements the 'comm shutdown' method.*/
00336 /* */
00337 /*  Arguments:*/
00338 /*  id  Reference to the remote interp*/
00339 /* */
00340 /*  Results:*/
00341 /*  None.*/
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 /*  new --*/
00353 /* */
00354 /*  Create a new comm channel/instance.*/
00355 /*  Implements the 'comm new' method.*/
00356 /* */
00357 /*  Arguments:*/
00358 /*  ch  Name of the new channel*/
00359 /*  args    Configuration, in the form of -option value pairs.*/
00360 /* */
00361 /*  Results:*/
00362 /*  None.*/
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 /*  send --*/
00422 /* */
00423 /*  Send command to a specified channel.*/
00424 /*  Implements the 'comm send' method.*/
00425 /* */
00426 /*  Arguments:*/
00427 /*  args    see inside*/
00428 /* */
00429 /*  Results:*/
00430 /*  varies.*/
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 /*  ::comm::Capability --*/
00784 /* */
00785 /*  Internal command. Interogate an interp for*/
00786 /*  the commands needed to execute regular and*/
00787 /*  hook scripts.*/
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 /*  ::comm::commConnect --*/
00812 /* */
00813 /*  Internal command. Called to connect to a remote interp*/
00814 /* */
00815 /*  Arguments:*/
00816 /*  id  Specification of the location of the remote interp.*/
00817 /*      A list containing either one or two elements.*/
00818 /*      One element = port, host is localhost.*/
00819 /*      Two elements = port and host, in this order.*/
00820 /* */
00821 /*  Results:*/
00822 /*  fid channel handle of the socket the connection goes through.*/
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 /*  ::comm::commIncoming --*/
00868 /* */
00869 /*  Internal command. Called for an incoming new connection.*/
00870 /*  Handles connection setup and initialization.*/
00871 /* */
00872 /*  Arguments:*/
00873 /*  chan    logical channel handling the connection.*/
00874 /*  fid channel handle of the socket running the connection.*/
00875 /*  addr    ip address of the socket channel 'fid'*/
00876 /*  remport remote port for the socket channel 'fid'*/
00877 /* */
00878 /*  Results:*/
00879 /*  None.*/
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 /*  ::comm::commNewConn --*/
00961 /* */
00962 /*  Internal command. Common new connection processing*/
00963 /* */
00964 /*  Arguments:*/
00965 /*  id  Reference to the remote interp*/
00966 /*  fid channel handle of the socket running the connection.*/
00967 /* */
00968 /*  Results:*/
00969 /*  None.*/
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 /*  ::comm::commLostConn --*/
00993 /* */
00994 /*  Internal command. Called to tidy up a lost connection,*/
00995 /*  including aborting ongoing sends. Each send should clean*/
00996 /*  themselves up in pending/result.*/
00997 /* */
00998 /*  Arguments:*/
00999 /*  fid Channel handle of the socket which got lost.*/
01000 /*  reason  Message describing the reason of the loss.*/
01001 /* */
01002 /*  Results:*/
01003 /*  reason*/
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 /*  CancelFutures: Mark futures associated with a comm channel as*/
01074 /*  expired, done when the connection to the peer has been lost. The*/
01075 /*  marked futures will not generate result anymore. They will also stay*/
01076 /*  around until destroyed by the script they belong to.*/
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 /*  ::comm::commCollect --*/
01096 /* */
01097 /*  Internal command. Called from the fileevent to read from fid*/
01098 /*  and append to the buffer. This continues until we get a whole*/
01099 /*  command, which we then invoke.*/
01100 /* */
01101 /*  Arguments:*/
01102 /*  chan    logical channel collecting the data*/
01103 /*  fid channel handle of the socket we collect.*/
01104 /* */
01105 /*  Results:*/
01106 /*  None.*/
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 /*  ::comm::commExec --*/
01149 /* */
01150 /*  Internal command. Receives and executes a remote command,*/
01151 /*  returning the result and/or error. Unknown protocol commands*/
01152 /*  are silently discarded*/
01153 /* */
01154 /*  Arguments:*/
01155 /*  chan        logical channel collecting the data*/
01156 /*  fid     channel handle of the socket we collect.*/
01157 /*  remoteid    id of the other side.*/
01158 /*  buf     buffer containing the command to execute.*/
01159 /* */
01160 /*  Results:*/
01161 /*  None.*/
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 /*  ::comm::commSendReply --*/
01337 /* */
01338 /*  Internal command. Executed to construct and send the reply*/
01339 /*  for a command.*/
01340 /* */
01341 /*  Arguments:*/
01342 /*  fid     channel handle of the socket we are replying to.*/
01343 /*  cmd     The type of request (send, command) we are replying to.*/
01344 /*  ser     Serial number of the request the reply is for.*/
01345 /*  err     result code to place into the reply.*/
01346 /*  ret     result value to place into the reply.*/
01347 /* */
01348 /*  Results:*/
01349 /*  None.*/
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 /*  Hooks to link async return and future processing into the regular*/
01458 /*  system.*/
01459 
01460 /*  AsyncPrepare, AsyncCheck: Initialize state information for async*/
01461 /*  return upon start of a remote invokation, and checking the state for*/
01462 /*  async return.*/
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 /*  FutureDone: Action taken by an uncanceled future to deliver the*/
01483 /*  generated result to the proper invoker. This also removes the future*/
01484 /*  from the list of pending futures for the comm channel.*/
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 /*  Hooks to save command state across nested eventloops a remotely*/
01497 /*  invoked command may run before finally activating async result*/
01498 /*  generation.*/
01499 
01500 /*  DANGER !! We have to refer to comm internals using fully-qualified*/
01501 /*  names because the wrappers will execute in the global namespace*/
01502 /*  after their installation.*/
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 /*  Install the wrappers.*/
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 /*  API: Future objects.*/
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     /*  Internal method for use by comm channels. Marks the future as*/
01632     /*  expired, no peer to return a result back to.*/
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 /*  Setup*/
01644 ::comm::InitWrappers
01645 
01646 /* */
01647 /* */
01648 /*  Finish creating "comm" using the default port for this interp.*/
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 /* eof*/
01663 package provide comm 4.5.6
01664 

Generated on 21 Sep 2010 for Gui by  doxygen 1.6.1