Version 6 of chan pending

Updated 2014-03-07 15:27:46 by pooryorick

chan pending, a chan action, retrieves the about of data pending in a channel's buffer.

Synopsis

chan pending mode channelId

Documentation

TIP 287
resulted in chan pending

Description

Returns the number of bytes in Tcl's buffers for the channel channelId in the direction specified by mode (which must be input or output).

chan pending makes it safe to use chan gets with sockets, which can now be inspected to detect an excessively long line, providing an opportunity to prevent memory exhaustion.

How to Use

chan pending is designed for use with non-blocking channels, especially sockets, and it is used to deal with the case where the source is producing far more data than was expected before producing some particular delimiter, e.g., a newline character for gets.

proc accept {cb channel args} {
    global timeouts
    chan configure $channel -blocking 0 -buffersize 4096
    chan event $channel readable [list doGets $channel $cb]
    set ::timeouts($channel) ""
}

proc doGets {channel callback} {
    global timeouts
    if {[chan gets $channel line] >= 0} {
        after cancel $timeouts($channel)
        set timeouts($channel) ""
        {*}$callback $channel $line
    } elseif {[chan eof $channel]} {
        chan close $channel
        after cancel $timeouts($channel)
        unset timeouts($channel)
    } else {
        # Must be blocked; check for excessive buffering
        if {[chan pending input $channel] > 1024} {
            # must be a line longer than a kilobyte; naughty! reject
            chan close $channel
            after cancel $timeouts($channel)
            unset timeouts($channel)
        } elseif {$timeouts($channel) eq ""} {
            # no line timeout watcher; install one that waits 10 seconds
            # from when the start of the line arrives to when the end of the
            # line arrives before killing the channel
            set timeouts($channel) [after 10000 killChannel $channel]
        }
    }
}

proc killChannel channel {
    global timeouts
    unset timeout($channel)
    chan close $channel
}

# Make the (server) socket
socket -server [list accept processLineCallback] 12345
# This is where you'd add your code to handle each line
proc processLineCallback {channel line} {
    # ... whatever ...
    puts $channel-->$line
}