summaryrefslogtreecommitdiffstats
path: root/tcl8.6/pkgs/thread2.8.4/tcl
diff options
context:
space:
mode:
Diffstat (limited to 'tcl8.6/pkgs/thread2.8.4/tcl')
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/README32
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl310
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/phttpd/index.htm5
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/phttpd/phttpd.tcl686
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/phttpd/uhttpd.tcl416
-rw-r--r--tcl8.6/pkgs/thread2.8.4/tcl/tpool/tpool.tcl576
6 files changed, 2025 insertions, 0 deletions
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/README b/tcl8.6/pkgs/thread2.8.4/tcl/README
new file mode 100644
index 0000000..15e1edd
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/README
@@ -0,0 +1,32 @@
+
+Software here is provided as example of making some interesting
+things and applications using the Tcl threading extension.
+
+Currently, following packages are supplied:
+
+ tpool/ Example Tcl-only implementation of thread pools.
+ The threading extension includes an efficient
+ threadpool implementation in C. This file is
+ provided as a fully functional example on how this
+ functionality could be implemented in Tcl alone.
+
+ phttpd/ MT-enabled httpd server. It uses threadpool to
+ distribute incoming requests among several worker
+ threads in the threadpool. This way blocking
+ requests may be handled much better, w/o halting
+ the event loop of the main responder thread.
+ In this directory you will also find the uhttpd.
+ This is the same web-server but operating in the
+ event-loop mode alone, no threadpool support.
+ This is good for comparison purposes.
+
+ cmdsrv/ Socket command-line server. Each new connection
+ gets new thread, thus allowing multiple outstanding
+ blocking calls without halting the event loop.
+
+To play around with above packages, change to the corresponding
+directory and source files in the Tcl8.4 (or later) Tcl shell.
+Be sure to have the latest Tcl threading extension installed in
+your package path.
+
+- EOF
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl b/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl
new file mode 100644
index 0000000..01ec508
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/cmdsrv/cmdsrv.tcl
@@ -0,0 +1,310 @@
+#
+# cmdsrv.tcl --
+#
+# Simple socket command server. Supports many simultaneous sessions.
+# Works in thread mode with each new connection receiving a new thread.
+#
+# Usage:
+# cmdsrv::create port ?-idletime value? ?-initcmd cmd?
+#
+# port Tcp port where the server listens
+# -idletime # of sec to idle before tearing down socket (def: 300 sec)
+# -initcmd script to initialize new worker thread (def: empty)
+#
+# Example:
+#
+# # tclsh8.4
+# % source cmdsrv.tcl
+# % cmdsrv::create 5000 -idletime 60
+# % vwait forever
+#
+# Starts the server on the port 5000, sets idle timer to 1 minute.
+# You can now use "telnet" utility to connect.
+#
+# Copyright (c) 2002 by Zoran Vasiljevic.
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -----------------------------------------------------------------------------
+
+package require Tcl 8.4
+package require Thread 2.5
+
+namespace eval cmdsrv {
+ variable data; # Stores global configuration options
+}
+
+#
+# cmdsrv::create --
+#
+# Start the server on the given Tcp port.
+#
+# Arguments:
+# port Port where the server is listening
+# args Variable number of arguments
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::create {port args} {
+
+ variable data
+
+ if {[llength $args] % 2} {
+ error "wrong \# arguments, should be: key1 val1 key2 val2..."
+ }
+
+ #
+ # Setup default pool data.
+ #
+
+ array set data {
+ -idletime 300000
+ -initcmd {source cmdsrv.tcl}
+ }
+
+ #
+ # Override with user-supplied data
+ #
+
+ foreach {arg val} $args {
+ switch -- $arg {
+ -idletime {set data($arg) [expr {$val*1000}]}
+ -initcmd {append data($arg) \n $val}
+ default {
+ error "unsupported pool option \"$arg\""
+ }
+ }
+ }
+
+ #
+ # Start the server on the given port. Note that we wrap
+ # the actual accept with a helper after/idle callback.
+ # This is a workaround for a well-known Tcl bug.
+ #
+
+ socket -server [namespace current]::_Accept -myaddr 127.0.0.1 $port
+}
+
+#
+# cmdsrv::_Accept --
+#
+# Helper procedure to solve Tcl shared channel bug when responding
+# to incoming socket connection and transfering the channel to other
+# thread(s).
+#
+# Arguments:
+# s incoming socket
+# ipaddr IP address of the remote peer
+# port Tcp port used for this connection
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::_Accept {s ipaddr port} {
+ after idle [list [namespace current]::Accept $s $ipaddr $port]
+}
+
+#
+# cmdsrv::Accept --
+#
+# Accepts the incoming socket connection, creates the worker thread.
+#
+# Arguments:
+# s incoming socket
+# ipaddr IP address of the remote peer
+# port Tcp port used for this connection
+#
+# Side Effects:
+# Creates new worker thread.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::Accept {s ipaddr port} {
+
+ variable data
+
+ #
+ # Configure socket for sane operation
+ #
+
+ fconfigure $s -blocking 0 -buffering none -translation {auto crlf}
+
+ #
+ # Emit the prompt
+ #
+
+ puts -nonewline $s "% "
+
+ #
+ # Create worker thread and transfer socket ownership
+ #
+
+ set tid [thread::create [append data(-initcmd) \n thread::wait]]
+ thread::transfer $tid $s ; # This flushes the socket as well
+
+ #
+ # Start event-loop processing in the remote thread
+ #
+
+ thread::send -async $tid [subst {
+ array set [namespace current]::data {[array get data]}
+ fileevent $s readable {[namespace current]::Read $s}
+ proc exit args {[namespace current]::SockDone $s}
+ [namespace current]::StartIdleTimer $s
+ }]
+}
+
+#
+# cmdsrv::Read --
+#
+# Event loop procedure to read data from socket and collect the
+# command to execute. If the command read from socket is complete
+# it executes the command are prints the result back.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::Read {s} {
+
+ variable data
+
+ StopIdleTimer $s
+
+ #
+ # Cover client closing connection
+ #
+
+ if {[eof $s] || [catch {read $s} line]} {
+ return [SockDone $s]
+ }
+ if {$line == "\n" || $line == ""} {
+ if {[catch {puts -nonewline $s "% "}]} {
+ return [SockDone $s]
+ }
+ return [StartIdleTimer $s]
+ }
+
+ #
+ # Construct command line to eval
+ #
+
+ append data(cmd) $line
+ if {[info complete $data(cmd)] == 0} {
+ if {[catch {puts -nonewline $s "> "}]} {
+ return [SockDone $s]
+ }
+ return [StartIdleTimer $s]
+ }
+
+ #
+ # Run the command
+ #
+
+ catch {uplevel \#0 $data(cmd)} ret
+ if {[catch {puts $s $ret}]} {
+ return [SockDone $s]
+ }
+ set data(cmd) ""
+ if {[catch {puts -nonewline $s "% "}]} {
+ return [SockDone $s]
+ }
+ StartIdleTimer $s
+}
+
+#
+# cmdsrv::SockDone --
+#
+# Tears down the thread and closes the socket if the remote peer has
+# closed his side of the comm channel.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# Worker thread gets released.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::SockDone {s} {
+
+ catch {close $s}
+ thread::release
+}
+
+#
+# cmdsrv::StopIdleTimer --
+#
+# Cancel the connection idle timer.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# After event gets cancelled.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::StopIdleTimer {s} {
+
+ variable data
+
+ if {[info exists data(idleevent)]} {
+ after cancel $data(idleevent)
+ unset data(idleevent)
+ }
+}
+
+#
+# cmdsrv::StartIdleTimer --
+#
+# Initiates the connection idle timer.
+#
+# Arguments:
+# s incoming socket
+#
+# Side Effects:
+# After event gets posted.
+#
+# Results:
+# None.
+#
+
+proc cmdsrv::StartIdleTimer {s} {
+
+ variable data
+
+ set data(idleevent) \
+ [after $data(-idletime) [list [namespace current]::SockDone $s]]
+}
+
+# EOF $RCSfile: cmdsrv.tcl,v $
+
+# Emacs Setup Variables
+# Local Variables:
+# mode: Tcl
+# indent-tabs-mode: nil
+# tcl-basic-offset: 4
+# End:
+
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/index.htm b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/index.htm
new file mode 100644
index 0000000..324f1f7
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/index.htm
@@ -0,0 +1,5 @@
+<html>
+<body>
+<h3>Hallo World</h3>
+</body>
+</html>
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/phttpd.tcl b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/phttpd.tcl
new file mode 100644
index 0000000..8f0c42d
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/phttpd.tcl
@@ -0,0 +1,686 @@
+#
+# phttpd.tcl --
+#
+# Simple Sample httpd/1.0 server in 250 lines of Tcl.
+# Stephen Uhler / Brent Welch (c) 1996 Sun Microsystems.
+#
+# Modified to use namespaces, direct url-to-procedure access
+# and thread pool package. Grown little larger since ;)
+#
+# Usage:
+# phttpd::create port
+#
+# port Tcp port where the server listens
+#
+# Example:
+#
+# # tclsh8.4
+# % source phttpd.tcl
+# % phttpd::create 5000
+# % vwait forever
+#
+# Starts the server on the port 5000. Also, look at the Httpd array
+# definition in the "phttpd" namespace declaration to find out
+# about other options you may put on the command line.
+#
+# You can use: http://localhost:5000/monitor URL to test the
+# server functionality.
+#
+# Copyright (c) 2002 by Zoran Vasiljevic.
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -----------------------------------------------------------------------------
+
+package require Tcl 8.4
+package require Thread 2.5
+
+#
+# Modify the following in order to load the
+# example Tcl implementation of threadpools.
+# Per default, the C-level threadpool is used.
+#
+
+if {0} {
+ eval [set TCL_TPOOL {source ../tpool/tpool.tcl}]
+}
+
+namespace eval phttpd {
+
+ variable Httpd; # Internal server state and config params
+ variable MimeTypes; # Cache of file-extension/mime-type
+ variable HttpCodes; # Portion of well-known http return codes
+ variable ErrorPage; # Format of error response page in html
+
+ array set Httpd {
+ -name phttpd
+ -vers 1.0
+ -root "."
+ -index index.htm
+ }
+ array set HttpCodes {
+ 400 "Bad Request"
+ 401 "Not Authorized"
+ 404 "Not Found"
+ 500 "Server error"
+ }
+ array set MimeTypes {
+ {} "text/plain"
+ .txt "text/plain"
+ .htm "text/html"
+ .htm "text/html"
+ .gif "image/gif"
+ .jpg "image/jpeg"
+ .png "image/png"
+ }
+ set ErrorPage {
+ <title>Error: %1$s %2$s</title>
+ <h1>%3$s</h1>
+ <p>Problem in accessing "%4$s" on this server.</p>
+ <hr>
+ <i>%5$s/%6$s Server at %7$s Port %8$s</i>
+ }
+}
+
+#
+# phttpd::create --
+#
+# Start the server by listening for connections on the desired port.
+#
+# Arguments:
+# port
+# args
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::create {port args} {
+
+ variable Httpd
+
+ set arglen [llength $args]
+ if {$arglen} {
+ if {$arglen % 2} {
+ error "wrong \# args, should be: key1 val1 key2 val2..."
+ }
+ set opts [array names Httpd]
+ foreach {arg val} $args {
+ if {[lsearch $opts $arg] == -1} {
+ error "unknown option \"$arg\""
+ }
+ set Httpd($arg) $val
+ }
+ }
+
+ #
+ # Create thread pool with max 8 worker threads.
+ #
+
+ if {[info exists ::TCL_TPOOL] == 0} {
+ #
+ # Using the internal C-based thread pool
+ #
+ set initcmd "source ../phttpd/phttpd.tcl"
+ } else {
+ #
+ # Using the Tcl-level hand-crafted thread pool
+ #
+ append initcmd "source ../phttpd/phttpd.tcl" \n $::TCL_TPOOL
+ }
+
+ set Httpd(tpid) [tpool::create -maxworkers 8 -initcmd $initcmd]
+
+ #
+ # Start the server on the given port. Note that we wrap
+ # the actual accept with a helper after/idle callback.
+ # This is a workaround for a well-known Tcl bug.
+ #
+
+ socket -server [namespace current]::_Accept $port
+}
+
+#
+# phttpd::_Accept --
+#
+# Helper procedure to solve Tcl shared-channel bug when responding
+# to incoming connection and transfering the channel to other thread(s).
+#
+# Arguments:
+# sock incoming socket
+# ipaddr IP address of the remote peer
+# port Tcp port used for this connection
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc phttpd::_Accept {sock ipaddr port} {
+ after idle [list [namespace current]::Accept $sock $ipaddr $port]
+}
+
+#
+# phttpd::Accept --
+#
+# Accept a new connection from the client.
+#
+# Arguments:
+# sock
+# ipaddr
+# port
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Accept {sock ipaddr port} {
+
+ variable Httpd
+
+ #
+ # Setup the socket for sane operation
+ #
+
+ fconfigure $sock -blocking 0 -translation {auto crlf}
+
+ #
+ # Detach the socket from current interpreter/tnread.
+ # One of the worker threads will attach it again.
+ #
+
+ thread::detach $sock
+
+ #
+ # Send the work ticket to threadpool.
+ #
+
+ tpool::post -detached $Httpd(tpid) [list [namespace current]::Ticket $sock]
+}
+
+#
+# phttpd::Ticket --
+#
+# Job ticket to run in the thread pool thread.
+#
+# Arguments:
+# sock
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Ticket {sock} {
+
+ thread::attach $sock
+ fileevent $sock readable [list [namespace current]::Read $sock]
+
+ #
+ # End of processing is signalized here.
+ # This will release the worker thread.
+ #
+
+ vwait [namespace current]::done
+}
+
+
+#
+# phttpd::Read --
+#
+# Read data from client and parse incoming http request.
+#
+# Arguments:
+# sock
+#
+# Side Effects:
+# None.
+#
+# Results:
+# None.
+#
+
+proc phttpd::Read {sock} {
+
+ variable Httpd
+ variable data
+
+ set data(sock) $sock
+
+ while {1} {
+ if {[catch {gets $data(sock) line} readCount] || [eof $data(sock)]} {
+ return [Done]
+ }
+ if {![info exists data(state)]} {
+ set pat {(POST|GET) ([^?]+)\??([^ ]*) HTTP/1\.[0-9]}
+ if {[regexp $pat $line x data(proto) data(url) data(query)]} {
+ set data(state) mime
+ continue
+ } else {
+ Log error "bad request line: (%s)" $line
+ Error 400
+ return [Done]
+ }
+ }
+
+ # string compare $readCount 0 maps -1 to -1, 0 to 0, and > 0 to 1
+
+ set state [string compare $readCount 0],$data(state),$data(proto)
+ switch -- $state {
+ "0,mime,GET" - "0,query,POST" {
+ Respond
+ return [Done]
+ }
+ "0,mime,POST" {
+ set data(state) query
+ set data(query) ""
+ }
+ "1,mime,POST" - "1,mime,GET" {
+ if [regexp {([^:]+):[ ]*(.*)} $line dummy key value] {
+ set data(mime,[string tolower $key]) $value
+ }
+ }
+ "1,query,POST" {
+ append data(query) $line
+ set clen $data(mime,content-length)
+ if {($clen - [string length $data(query)]) <= 0} {
+ Respond
+ return [Done]
+ }
+ }
+ default {
+ if [eof $data(sock)] {
+ Log error "unexpected eof; client closed connection"
+ return [Done]
+ } else {
+ Log error "bad http protocol state: %s" $state
+ Error 400
+ return [Done]
+ }
+ }
+ }
+ }
+}
+
+#
+# phttpd::Done --
+#
+# Close the connection socket
+#
+# Arguments:
+# s
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Done {} {
+
+ variable done
+ variable data
+
+ close $data(sock)
+
+ if {[info exists data]} {
+ unset data
+ }
+
+ set done 1 ; # Releases the request thread (See Ticket procedure)
+}
+
+#
+# phttpd::Respond --
+#
+# Respond to the query.
+#
+# Arguments:
+# s
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Respond {} {
+
+ variable data
+
+ if {[info commands $data(url)] == $data(url)} {
+
+ #
+ # Service URL-procedure
+ #
+
+ if {[catch {
+ puts $data(sock) "HTTP/1.0 200 OK"
+ puts $data(sock) "Date: [Date]"
+ puts $data(sock) "Last-Modified: [Date]"
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ return
+ }
+ if {[catch {$data(url) data} err]} {
+ Log error "%s: %s" $data(url) $err
+ }
+
+ } else {
+
+ #
+ # Service regular file path
+ #
+
+ set mypath [Url2File $data(url)]
+ if {![catch {open $mypath} i]} {
+ if {[catch {
+ puts $data(sock) "HTTP/1.0 200 OK"
+ puts $data(sock) "Date: [Date]"
+ puts $data(sock) "Last-Modified: [Date [file mtime $mypath]]"
+ puts $data(sock) "Content-Type: [ContentType $mypath]"
+ puts $data(sock) "Content-Length: [file size $mypath]"
+ puts $data(sock) ""
+ fconfigure $data(sock) -translation binary -blocking 0
+ fconfigure $i -translation binary
+ fcopy $i $data(sock)
+ close $i
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ }
+ } else {
+ Log error "%s: %s" $data(url) $i
+ Error 404
+ }
+ }
+}
+
+#
+# phttpd::ContentType --
+#
+# Convert the file suffix into a mime type.
+#
+# Arguments:
+# path
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::ContentType {path} {
+
+ # @c Convert the file suffix into a mime type.
+
+ variable MimeTypes
+
+ set type "text/plain"
+ catch {set type $MimeTypes([file extension $path])}
+
+ return $type
+}
+
+#
+# phttpd::Error --
+#
+# Emit error page
+#
+# Arguments:
+# s
+# code
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Error {code} {
+
+ variable Httpd
+ variable HttpCodes
+ variable ErrorPage
+ variable data
+
+ append data(url) ""
+ set msg \
+ [format $ErrorPage \
+ $code \
+ $HttpCodes($code) \
+ $HttpCodes($code) \
+ $data(url) \
+ $Httpd(-name) \
+ $Httpd(-vers) \
+ [info hostname] \
+ 80 \
+ ]
+ if {[catch {
+ puts $data(sock) "HTTP/1.0 $code $HttpCodes($code)"
+ puts $data(sock) "Date: [Date]"
+ puts $data(sock) "Content-Length: [string length $msg]"
+ puts $data(sock) ""
+ puts $data(sock) $msg
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ }
+}
+
+#
+# phttpd::Date --
+#
+# Generate a date string in HTTP format.
+#
+# Arguments:
+# seconds
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Date {{seconds 0}} {
+
+ # @c Generate a date string in HTTP format.
+
+ if {$seconds == 0} {
+ set seconds [clock seconds]
+ }
+ clock format $seconds -format {%a, %d %b %Y %T %Z} -gmt 1
+}
+
+#
+# phttpd::Log --
+#
+# Log an httpd transaction.
+#
+# Arguments:
+# reason
+# format
+# args
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Log {reason format args} {
+
+ set messg [eval format [list $format] $args]
+ set stamp [clock format [clock seconds] -format "%d/%b/%Y:%H:%M:%S"]
+
+ puts stderr "\[$stamp\]\[-thread[thread::id]-\] $reason: $messg"
+}
+
+#
+# phttpd::Url2File --
+#
+# Convert a url into a pathname.
+#
+# Arguments:
+# url
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::Url2File {url} {
+
+ variable Httpd
+
+ lappend pathlist $Httpd(-root)
+ set level 0
+
+ foreach part [split $url /] {
+ set part [CgiMap $part]
+ if [regexp {[:/]} $part] {
+ return ""
+ }
+ switch -- $part {
+ "." { }
+ ".." {incr level -1}
+ default {incr level}
+ }
+ if {$level <= 0} {
+ return ""
+ }
+ lappend pathlist $part
+ }
+
+ set file [eval file join $pathlist]
+
+ if {[file isdirectory $file]} {
+ return [file join $file $Httpd(-index)]
+ } else {
+ return $file
+ }
+}
+
+#
+# phttpd::CgiMap --
+#
+# Decode url-encoded strings.
+#
+# Arguments:
+# data
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::CgiMap {data} {
+
+ regsub -all {\+} $data { } data
+ regsub -all {([][$\\])} $data {\\\1} data
+ regsub -all {%([0-9a-fA-F][0-9a-fA-F])} $data {[format %c 0x\1]} data
+
+ return [subst $data]
+}
+
+#
+# phttpd::QueryMap --
+#
+# Decode url-encoded query into key/value pairs.
+#
+# Arguments:
+# query
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc phttpd::QueryMap {query} {
+
+ set res [list]
+
+ regsub -all {[&=]} $query { } query
+ regsub -all { } $query { {} } query; # Othewise we lose empty values
+
+ foreach {key val} $query {
+ lappend res [CgiMap $key] [CgiMap $val]
+ }
+ return $res
+}
+
+#
+# monitor --
+#
+# Procedure used to test the phttpd server. It responds on the
+# http://<hostname>:<port>/monitor
+#
+# Arguments:
+# array
+#
+# Side Effects:
+# None..
+#
+# Results:
+# None.
+#
+
+proc /monitor {array} {
+
+ upvar $array data ; # Holds the socket to remote client
+
+ #
+ # Emit headers
+ #
+
+ puts $data(sock) "HTTP/1.0 200 OK"
+ puts $data(sock) "Date: [phttpd::Date]"
+ puts $data(sock) "Content-Type: text/html"
+ puts $data(sock) ""
+
+ #
+ # Emit body
+ #
+
+ puts $data(sock) [subst {
+ <html>
+ <body>
+ <h3>[clock format [clock seconds]]</h3>
+ }]
+
+ after 1 ; # Simulate blocking call
+
+ puts $data(sock) [subst {
+ </body>
+ </html>
+ }]
+}
+
+# EOF $RCSfile: phttpd.tcl,v $
+# Emacs Setup Variables
+# Local Variables:
+# mode: Tcl
+# indent-tabs-mode: nil
+# tcl-basic-offset: 4
+# End:
+
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/uhttpd.tcl b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/uhttpd.tcl
new file mode 100644
index 0000000..b44338a
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/phttpd/uhttpd.tcl
@@ -0,0 +1,416 @@
+#
+# uhttpd.tcl --
+#
+# Simple Sample httpd/1.0 server in 250 lines of Tcl.
+# Stephen Uhler / Brent Welch (c) 1996 Sun Microsystems.
+#
+# Modified to use namespaces and direct url-to-procedure access (zv).
+# Eh, due to this, and nicer indenting, it's now 150 lines longer :-)
+#
+# Usage:
+# phttpd::create port
+#
+# port Tcp port where the server listens
+#
+# Example:
+#
+# # tclsh8.4
+# % source uhttpd.tcl
+# % uhttpd::create 5000
+# % vwait forever
+#
+# Starts the server on the port 5000. Also, look at the Httpd array
+# definition in the "uhttpd" namespace declaration to find out
+# about other options you may put on the command line.
+#
+# You can use: http://localhost:5000/monitor URL to test the
+# server functionality.
+#
+# Copyright (c) Stephen Uhler / Brent Welch (c) 1996 Sun Microsystems.
+# Copyright (c) 2002 by Zoran Vasiljevic.
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -----------------------------------------------------------------------------
+
+namespace eval uhttpd {
+
+ variable Httpd; # Internal server state and config params
+ variable MimeTypes; # Cache of file-extension/mime-type
+ variable HttpCodes; # Portion of well-known http return codes
+ variable ErrorPage; # Format of error response page in html
+
+ array set Httpd {
+ -name uhttpd
+ -vers 1.0
+ -root ""
+ -index index.htm
+ }
+ array set HttpCodes {
+ 400 "Bad Request"
+ 401 "Not Authorized"
+ 404 "Not Found"
+ 500 "Server error"
+ }
+ array set MimeTypes {
+ {} "text/plain"
+ .txt "text/plain"
+ .htm "text/html"
+ .htm "text/html"
+ .gif "image/gif"
+ .jpg "image/jpeg"
+ .png "image/png"
+ }
+ set ErrorPage {
+ <title>Error: %1$s %2$s</title>
+ <h1>%3$s</h1>
+ <p>Problem in accessing "%4$s" on this server.</p>
+ <hr>
+ <i>%5$s/%6$s Server at %7$s Port %8$s</i>
+ }
+}
+
+proc uhttpd::create {port args} {
+
+ # @c Start the server by listening for connections on the desired port.
+
+ variable Httpd
+ set arglen [llength $args]
+
+ if {$arglen} {
+ if {$arglen % 2} {
+ error "wrong \# arguments, should be: key1 val1 key2 val2..."
+ }
+ set opts [array names Httpd]
+ foreach {arg val} $args {
+ if {[lsearch $opts $arg] == -1} {
+ error "unknown option \"$arg\""
+ }
+ set Httpd($arg) $val
+ }
+ }
+
+ set Httpd(port) $port
+ set Httpd(host) [info hostname]
+
+ socket -server [namespace current]::Accept $port
+}
+
+proc uhttpd::respond {s status contype data {length 0}} {
+
+ puts $s "HTTP/1.0 $status"
+ puts $s "Date: [Date]"
+ puts $s "Content-Type: $contype"
+
+ if {$length} {
+ puts $s "Content-Length: $length"
+ } else {
+ puts $s "Content-Length: [string length $data]"
+ }
+
+ puts $s ""
+ puts $s $data
+}
+
+proc uhttpd::Accept {newsock ipaddr port} {
+
+ # @c Accept a new connection from the client.
+
+ variable Httpd
+ upvar \#0 [namespace current]::Httpd$newsock data
+
+ fconfigure $newsock -blocking 0 -translation {auto crlf}
+
+ set data(ipaddr) $ipaddr
+ fileevent $newsock readable [list [namespace current]::Read $newsock]
+}
+
+proc uhttpd::Read {s} {
+
+ # @c Read data from client
+
+ variable Httpd
+ upvar \#0 [namespace current]::Httpd$s data
+
+ if {[catch {gets $s line} readCount] || [eof $s]} {
+ return [Done $s]
+ }
+ if {$readCount == -1} {
+ return ;# Insufficient data on non-blocking socket !
+ }
+ if {![info exists data(state)]} {
+ set pat {(POST|GET) ([^?]+)\??([^ ]*) HTTP/1\.[0-9]}
+ if {[regexp $pat $line x data(proto) data(url) data(query)]} {
+ return [set data(state) mime]
+ } else {
+ Log error "bad request line: %s" $line
+ Error $s 400
+ return [Done $s]
+ }
+ }
+
+ # string compare $readCount 0 maps -1 to -1, 0 to 0, and > 0 to 1
+
+ set state [string compare $readCount 0],$data(state),$data(proto)
+ switch -- $state {
+ "0,mime,GET" - "0,query,POST" {
+ Respond $s
+ }
+ "0,mime,POST" {
+ set data(state) query
+ set data(query) ""
+ }
+ "1,mime,POST" - "1,mime,GET" {
+ if [regexp {([^:]+):[ ]*(.*)} $line dummy key value] {
+ set data(mime,[string tolower $key]) $value
+ }
+ }
+ "1,query,POST" {
+ append data(query) $line
+ set clen $data(mime,content-length)
+ if {($clen - [string length $data(query)]) <= 0} {
+ Respond $s
+ }
+ }
+ default {
+ if [eof $s] {
+ Log error "unexpected eof; client closed connection"
+ return [Done $s]
+ } else {
+ Log error "bad http protocol state: %s" $state
+ Error $s 400
+ return [Done $s]
+ }
+ }
+ }
+}
+
+proc uhttpd::Done {s} {
+
+ # @c Close the connection socket and discard token
+
+ close $s
+ unset [namespace current]::Httpd$s
+}
+
+proc uhttpd::Respond {s} {
+
+ # @c Respond to the query.
+
+ variable Httpd
+ upvar \#0 [namespace current]::Httpd$s data
+
+ if {[uplevel \#0 info proc $data(url)] == $data(url)} {
+
+ #
+ # Service URL-procedure first
+ #
+
+ if {[catch {
+ puts $s "HTTP/1.0 200 OK"
+ puts $s "Date: [Date]"
+ puts $s "Last-Modified: [Date]"
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ return [Done $s]
+ }
+ set data(sock) $s
+ if {[catch {$data(url) data} err]} {
+ Log error "%s: %s" $data(url) $err
+ }
+
+ } else {
+
+ #
+ # Service regular file path next.
+ #
+
+ set mypath [Url2File $data(url)]
+ if {![catch {open $mypath} i]} {
+ if {[catch {
+ puts $s "HTTP/1.0 200 OK"
+ puts $s "Date: [Date]"
+ puts $s "Last-Modified: [Date [file mtime $mypath]]"
+ puts $s "Content-Type: [ContentType $mypath]"
+ puts $s "Content-Length: [file size $mypath]"
+ puts $s ""
+ fconfigure $s -translation binary -blocking 0
+ fconfigure $i -translation binary
+ fcopy $i $s
+ close $i
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ }
+ } else {
+ Log error "%s: %s" $data(url) $i
+ Error $s 404
+ }
+ }
+
+ Done $s
+}
+
+proc uhttpd::ContentType {path} {
+
+ # @c Convert the file suffix into a mime type.
+
+ variable MimeTypes
+
+ set type "text/plain"
+ catch {set type $MimeTypes([file extension $path])}
+
+ return $type
+}
+
+proc uhttpd::Error {s code} {
+
+ # @c Emit error page.
+
+ variable Httpd
+ variable HttpCodes
+ variable ErrorPage
+
+ upvar \#0 [namespace current]::Httpd$s data
+
+ append data(url) ""
+ set msg \
+ [format $ErrorPage \
+ $code \
+ $HttpCodes($code) \
+ $HttpCodes($code) \
+ $data(url) \
+ $Httpd(-name) \
+ $Httpd(-vers) \
+ $Httpd(host) \
+ $Httpd(port) \
+ ]
+ if {[catch {
+ puts $s "HTTP/1.0 $code $HttpCodes($code)"
+ puts $s "Date: [Date]"
+ puts $s "Content-Length: [string length $msg]"
+ puts $s ""
+ puts $s $msg
+ } err]} {
+ Log error "client closed connection prematurely: %s" $err
+ }
+}
+
+proc uhttpd::Date {{seconds 0}} {
+
+ # @c Generate a date string in HTTP format.
+
+ if {$seconds == 0} {
+ set seconds [clock seconds]
+ }
+ clock format $seconds -format {%a, %d %b %Y %T %Z} -gmt 1
+}
+
+proc uhttpd::Log {reason format args} {
+
+ # @c Log an httpd transaction.
+
+ set messg [eval format [list $format] $args]
+ set stamp [clock format [clock seconds] -format "%d/%b/%Y:%H:%M:%S"]
+
+ puts stderr "\[$stamp\] $reason: $messg"
+}
+
+proc uhttpd::Url2File {url} {
+
+ # @c Convert a url into a pathname (this is probably not right)
+
+ variable Httpd
+
+ lappend pathlist $Httpd(-root)
+ set level 0
+
+ foreach part [split $url /] {
+ set part [CgiMap $part]
+ if [regexp {[:/]} $part] {
+ return ""
+ }
+ switch -- $part {
+ "." { }
+ ".." {incr level -1}
+ default {incr level}
+ }
+ if {$level <= 0} {
+ return ""
+ }
+ lappend pathlist $part
+ }
+
+ set file [eval file join $pathlist]
+
+ if {[file isdirectory $file]} {
+ return [file join $file $Httpd(-index)]
+ } else {
+ return $file
+ }
+}
+
+proc uhttpd::CgiMap {data} {
+
+ # @c Decode url-encoded strings
+
+ regsub -all {\+} $data { } data
+ regsub -all {([][$\\])} $data {\\\1} data
+ regsub -all {%([0-9a-fA-F][0-9a-fA-F])} $data {[format %c 0x\1]} data
+
+ return [subst $data]
+}
+
+proc uhttpd::QueryMap {query} {
+
+ # @c Decode url-encoded query into key/value pairs
+
+ set res [list]
+
+ regsub -all {[&=]} $query { } query
+ regsub -all { } $query { {} } query; # Othewise we lose empty values
+
+ foreach {key val} $query {
+ lappend res [CgiMap $key] [CgiMap $val]
+ }
+ return $res
+}
+
+proc /monitor {array} {
+
+ upvar $array data ; # Holds the socket to remote client
+
+ #
+ # Emit headers
+ #
+
+ puts $data(sock) "HTTP/1.0 200 OK"
+ puts $data(sock) "Date: [uhttpd::Date]"
+ puts $data(sock) "Content-Type: text/html"
+ puts $data(sock) ""
+
+ #
+ # Emit body
+ #
+
+ puts $data(sock) [subst {
+ <html>
+ <body>
+ <h3>[clock format [clock seconds]]</h3>
+ }]
+
+ after 1 ; # Simulate blocking call
+
+ puts $data(sock) [subst {
+ </body>
+ </html>
+ }]
+}
+
+# EOF $RCSfile: uhttpd.tcl,v $
+# Emacs Setup Variables
+# Local Variables:
+# mode: Tcl
+# indent-tabs-mode: nil
+# tcl-basic-offset: 4
+# End:
+
diff --git a/tcl8.6/pkgs/thread2.8.4/tcl/tpool/tpool.tcl b/tcl8.6/pkgs/thread2.8.4/tcl/tpool/tpool.tcl
new file mode 100644
index 0000000..021e231
--- /dev/null
+++ b/tcl8.6/pkgs/thread2.8.4/tcl/tpool/tpool.tcl
@@ -0,0 +1,576 @@
+#
+# tpool.tcl --
+#
+# Tcl implementation of a threadpool paradigm in pure Tcl using
+# the Tcl threading extension 2.5 (or higher).
+#
+# This file is for example purposes only. The efficient C-level
+# threadpool implementation is already a part of the threading
+# extension starting with 2.5 version. Both implementations have
+# the same Tcl API so both can be used interchangeably. Goal of
+# this implementation is to serve as an example of using the Tcl
+# extension to implement some very common threading paradigms.
+#
+# Beware: with time, as improvements are made to the C-level
+# implementation, this Tcl one might lag behind.
+# Please consider this code as a working example only.
+#
+#
+#
+# Copyright (c) 2002 by Zoran Vasiljevic.
+#
+# See the file "license.terms" for information on usage and
+# redistribution of this file, and for a DISCLAIMER OF ALL WARRANTIES.
+# -----------------------------------------------------------------------------
+
+package require Thread 2.5
+set thisScript [info script]
+
+namespace eval tpool {
+
+ variable afterevent "" ; # Idle timer event for worker threads
+ variable result ; # Stores result from the worker thread
+ variable waiter ; # Waits for an idle worker thread
+ variable jobsdone ; # Accumulates results from worker threads
+
+ #
+ # Create shared array with a single element.
+ # It is used for automatic pool handles creation.
+ #
+
+ set ns [namespace current]
+ tsv::lock $ns {
+ if {[tsv::exists $ns count] == 0} {
+ tsv::set $ns count 0
+ }
+ tsv::set $ns count -1
+ }
+ variable thisScript [info script]
+}
+
+#
+# tpool::create --
+#
+# Creates instance of a thread pool.
+#
+# Arguments:
+# args Variable number of key/value arguments, as follows:
+#
+# -minworkers minimum # of worker threads (def:0)
+# -maxworkers maximum # of worker threads (def:4)
+# -idletime # of sec worker is idle before exiting (def:0 = never)
+# -initcmd script used to initialize new worker thread
+# -exitcmd script run at worker thread exit
+#
+# Side Effects:
+# Might create many new threads if "-minworkers" option is > 0.
+#
+# Results:
+# The id of the newly created thread pool. This id must be used
+# in all other tpool::* commands.
+#
+
+proc tpool::create {args} {
+
+ variable thisScript
+
+ #
+ # Get next threadpool handle and create the pool array.
+ #
+
+ set usage "wrong \# args: should be \"[lindex [info level 1] 0]\
+ ?-minworkers count? ?-maxworkers count?\
+ ?-initcmd script? ?-exitcmd script?\
+ ?-idletime seconds?\""
+
+ set ns [namespace current]
+ set tpid [namespace tail $ns][tsv::incr $ns count]
+
+ tsv::lock $tpid {
+ tsv::set $tpid name $tpid
+ }
+
+ #
+ # Setup default pool data.
+ #
+
+ tsv::array set $tpid {
+ thrworkers ""
+ thrwaiters ""
+ jobcounter 0
+ refcounter 0
+ numworkers 0
+ -minworkers 0
+ -maxworkers 4
+ -idletime 0
+ -initcmd ""
+ -exitcmd ""
+ }
+
+ tsv::set $tpid -initcmd "source $thisScript"
+
+ #
+ # Override with user-supplied data
+ #
+
+ if {[llength $args] % 2} {
+ error $usage
+ }
+
+ foreach {arg val} $args {
+ switch -- $arg {
+ -minworkers -
+ -maxworkers {tsv::set $tpid $arg $val}
+ -idletime {tsv::set $tpid $arg [expr {$val*1000}]}
+ -initcmd {tsv::append $tpid $arg \n $val}
+ -exitcmd {tsv::append $tpid $arg \n $val}
+ default {
+ error $usage
+ }
+ }
+ }
+
+ #
+ # Start initial (minimum) number of worker threads.
+ #
+
+ for {set ii 0} {$ii < [tsv::set $tpid -minworkers]} {incr ii} {
+ Worker $tpid
+ }
+
+ return $tpid
+}
+
+#
+# tpool::names --
+#
+# Returns list of currently created threadpools
+#
+# Arguments:
+# None.
+#
+# Side Effects:
+# None.
+#
+# Results
+# List of active threadpoool identifiers or empty if none found
+#
+#
+
+proc tpool::names {} {
+ tsv::names [namespace tail [namespace current]]*
+}
+
+#
+# tpool::post --
+#
+# Submits the new job to the thread pool. The caller might pass
+# the job in two modes: synchronous and asynchronous.
+# For the synchronous mode, the pool implementation will retain
+# the result of the passed script until the caller collects it
+# using the "thread::get" command.
+# For the asynchronous mode, the result of the script is ignored.
+#
+# Arguments:
+# args Variable # of arguments with the following syntax:
+# tpool::post ?-detached? tpid script
+#
+# -detached flag to turn the async operation (ignore result)
+# tpid the id of the thread pool
+# script script to pass to the worker thread for execution
+#
+# Side Effects:
+# Depends on the passed script.
+#
+# Results:
+# The id of the posted job. This id is used later on to collect
+# result of the job and set local variables accordingly.
+# For asynchronously posted jobs, the return result is ignored
+# and this function returns empty result.
+#
+
+proc tpool::post {args} {
+
+ #
+ # Parse command arguments.
+ #
+
+ set ns [namespace current]
+ set usage "wrong \# args: should be \"[lindex [info level 1] 0]\
+ ?-detached? tpoolId script\""
+
+ if {[llength $args] == 2} {
+ set detached 0
+ set tpid [lindex $args 0]
+ set cmd [lindex $args 1]
+ } elseif {[llength $args] == 3} {
+ if {[lindex $args 0] != "-detached"} {
+ error $usage
+ }
+ set detached 1
+ set tpid [lindex $args 1]
+ set cmd [lindex $args 2]
+ } else {
+ error $usage
+ }
+
+ #
+ # Find idle (or create new) worker thread. This is relatively
+ # a complex issue, since we must honour the limits about number
+ # of allowed worker threads imposed to us by the caller.
+ #
+
+ set tid ""
+
+ while {$tid == ""} {
+ tsv::lock $tpid {
+ set tid [tsv::lpop $tpid thrworkers]
+ if {$tid == "" || [catch {thread::preserve $tid}]} {
+ set tid ""
+ tsv::lpush $tpid thrwaiters [thread::id] end
+ if {[tsv::set $tpid numworkers]<[tsv::set $tpid -maxworkers]} {
+ Worker $tpid
+ }
+ }
+ }
+ if {$tid == ""} {
+ vwait ${ns}::waiter
+ }
+ }
+
+ #
+ # Post the command to the worker thread
+ #
+
+ if {$detached} {
+ set j ""
+ thread::send -async $tid [list ${ns}::Run $tpid 0 $cmd]
+ } else {
+ set j [tsv::incr $tpid jobcounter]
+ thread::send -async $tid [list ${ns}::Run $tpid $j $cmd] ${ns}::result
+ }
+
+ variable jobsdone
+ set jobsdone($j) ""
+
+ return $j
+}
+
+#
+# tpool::wait --
+#
+# Waits for jobs sent with "thread::post" to finish.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+# jobList List of job id's done.
+# jobLeft List of jobs still pending.
+#
+# Side Effects:
+# Might eventually enter the event loop while waiting
+# for the job result to arrive from the worker thread.
+# It ignores bogus job ids.
+#
+# Results:
+# Result of the job. If the job resulted in error, it sets
+# the global errorInfo and errorCode variables accordingly.
+#
+
+proc tpool::wait {tpid jobList {jobLeft ""}} {
+
+ variable result
+ variable jobsdone
+
+ if {$jobLeft != ""} {
+ upvar $jobLeft jobleft
+ }
+
+ set retlist ""
+ set jobleft ""
+
+ foreach j $jobList {
+ if {[info exists jobsdone($j)] == 0} {
+ continue ; # Ignore (skip) bogus job ids
+ }
+ if {$jobsdone($j) != ""} {
+ lappend retlist $j
+ } else {
+ lappend jobleft $j
+ }
+ }
+ if {[llength $retlist] == 0 && [llength $jobList]} {
+ #
+ # No jobs found; wait for the first one to get ready.
+ #
+ set jobleft $jobList
+ while {1} {
+ vwait [namespace current]::result
+ set doneid [lindex $result 0]
+ set jobsdone($doneid) $result
+ if {[lsearch $jobList $doneid] >= 0} {
+ lappend retlist $doneid
+ set x [lsearch $jobleft $doneid]
+ set jobleft [lreplace $jobleft $x $x]
+ break
+ }
+ }
+ }
+
+ return $retlist
+}
+
+#
+# tpool::get --
+#
+# Waits for a job sent with "thread::post" to finish.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+# jobid Id of the previously posted job.
+#
+# Side Effects:
+# None.
+#
+# Results:
+# Result of the job. If the job resulted in error, it sets
+# the global errorInfo and errorCode variables accordingly.
+#
+
+proc tpool::get {tpid jobid} {
+
+ variable jobsdone
+
+ if {[lindex $jobsdone($jobid) 1] != 0} {
+ eval error [lrange $jobsdone($jobid) 2 end]
+ }
+
+ return [lindex $jobsdone($jobid) 2]
+}
+
+#
+# tpool::preserve --
+#
+# Increments the reference counter of the threadpool, reserving it
+# for the private usage..
+#
+# Arguments:
+# tpid Name of the pool shared array.
+#
+# Side Effects:
+# None.
+#
+# Results:
+# Current number of threadpool reservations.
+#
+
+proc tpool::preserve {tpid} {
+ tsv::incr $tpid refcounter
+}
+
+#
+# tpool::release --
+#
+# Decrements the reference counter of the threadpool, eventually
+# tearing the pool down if this was the last reservation.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+#
+# Side Effects:
+# If the number of reservations drops to zero or below
+# the threadpool is teared down.
+#
+# Results:
+# Current number of threadpool reservations.
+#
+
+proc tpool::release {tpid} {
+
+ tsv::lock $tpid {
+ if {[tsv::incr $tpid refcounter -1] <= 0} {
+ # Release all workers threads
+ foreach t [tsv::set $tpid thrworkers] {
+ thread::release -wait $t
+ }
+ tsv::unset $tpid ; # This is not an error; it works!
+ }
+ }
+}
+
+#
+# Private procedures, not a part of the threadpool API.
+#
+
+#
+# tpool::Worker --
+#
+# Creates new worker thread. This procedure must be executed
+# under the tsv lock.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+#
+# Side Effects:
+# Depends on the thread initialization script.
+#
+# Results:
+# None.
+#
+
+proc tpool::Worker {tpid} {
+
+ #
+ # Create new worker thread
+ #
+
+ set tid [thread::create]
+
+ thread::send $tid [tsv::set $tpid -initcmd]
+ thread::preserve $tid
+
+ tsv::incr $tpid numworkers
+ tsv::lpush $tpid thrworkers $tid
+
+ #
+ # Signalize waiter threads if any
+ #
+
+ set waiter [tsv::lpop $tpid thrwaiters]
+ if {$waiter != ""} {
+ thread::send -async $waiter [subst {
+ set [namespace current]::waiter 1
+ }]
+ }
+}
+
+#
+# tpool::Timer --
+#
+# This procedure should be executed within the worker thread only.
+# It registers the callback for terminating the idle thread.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+#
+# Side Effects:
+# Thread may eventually exit.
+#
+# Results:
+# None.
+#
+
+proc tpool::Timer {tpid} {
+
+ tsv::lock $tpid {
+ if {[tsv::set $tpid numworkers] > [tsv::set $tpid -minworkers]} {
+
+ #
+ # We have more workers than needed, so kill this one.
+ # We first splice ourselves from the list of active
+ # workers, adjust the number of workers and release
+ # this thread, which may exit eventually.
+ #
+
+ set x [tsv::lsearch $tpid thrworkers [thread::id]]
+ if {$x >= 0} {
+ tsv::lreplace $tpid thrworkers $x $x
+ tsv::incr $tpid numworkers -1
+ set exitcmd [tsv::set $tpid -exitcmd]
+ if {$exitcmd != ""} {
+ catch {eval $exitcmd}
+ }
+ thread::release
+ }
+ }
+ }
+}
+
+#
+# tpool::Run --
+#
+# This procedure should be executed within the worker thread only.
+# It performs the actual command execution in the worker thread.
+#
+# Arguments:
+# tpid Name of the pool shared array.
+# jid The job id
+# cmd The command to execute
+#
+# Side Effects:
+# Many, depending of the passed command
+#
+# Results:
+# List for passing the evaluation result and status back.
+#
+
+proc tpool::Run {tpid jid cmd} {
+
+ #
+ # Cancel the idle timer callback, if any.
+ #
+
+ variable afterevent
+ if {$afterevent != ""} {
+ after cancel $afterevent
+ }
+
+ #
+ # Evaluate passed command and build the result list.
+ #
+
+ set code [catch {uplevel \#0 $cmd} ret]
+ if {$code == 0} {
+ set res [list $jid 0 $ret]
+ } else {
+ set res [list $jid $code $ret $::errorInfo $::errorCode]
+ }
+
+ #
+ # Check to see if any caller is waiting to be serviced.
+ # If yes, kick it out of the waiting state.
+ #
+
+ set ns [namespace current]
+
+ tsv::lock $tpid {
+ tsv::lpush $tpid thrworkers [thread::id]
+ set waiter [tsv::lpop $tpid thrwaiters]
+ if {$waiter != ""} {
+ thread::send -async $waiter [subst {
+ set ${ns}::waiter 1
+ }]
+ }
+ }
+
+ #
+ # Release the thread. If this turns out to be
+ # the last refcount held, don't bother to do
+ # any more work, since thread will soon exit.
+ #
+
+ if {[thread::release] <= 0} {
+ return $res
+ }
+
+ #
+ # Register the idle timer again.
+ #
+
+ if {[set idle [tsv::set $tpid -idletime]]} {
+ set afterevent [after $idle [subst {
+ ${ns}::Timer $tpid
+ }]]
+ }
+
+ return $res
+}
+
+# EOF $RCSfile: tpool.tcl,v $
+
+# Emacs Setup Variables
+# Local Variables:
+# mode: Tcl
+# indent-tabs-mode: nil
+# tcl-basic-offset: 4
+# End:
+