• Where to close the channel

    From Luis Mendes@luisXXXlupeXXX@gmail.com to comp.lang.tcl on Wed Apr 24 16:01:54 2024
    From Newsgroup: comp.lang.tcl

    Hi,

    I'm using an async channel to read the log from an external command.

    The code skeleton is like this:

    while {[llength $in_queue] > 0} {
    nsc::runCommand $exec_id
    vwait ::exit_flag
    }

    namespace eval nsc {
    proc runCommand {exec_id} {
    set run_${exec_id} [Parse new $exec_id]
    }

    oo::class create Parse {
    variable chan_ans
    constructor {exec_id} {
    set chan_ans [open ![list {*}$comm] r]
    chan configure $chan_ans -blocking 0 -buffering line
    chan event $chan_ans readable [list [self object] parseLine]
    }
    method parseLine {} {
    set status [catch {chan gets $chan_ans line} nchars]
    .....
    if {$status || [chan eof $chan_ans]} {
    # all received
    chan configure $chan_ans -blocking 1 # to assure all is
    written
    chan event $chan_ans readable {}
    chan close $chan_ans
    set ::exit_flag 1
    }
    }
    }

    But the script never finishes, seems to complain at the line
    'chan close $chan_ans'.

    In the book "The Tcl Programming Language", by Ashok, in page 437 (466 of
    the PDF) there's this:
    "When an end of file is seen on a channel, it is crucial to EITHER remove
    the read handler from the channel aw we have done, or to close the channel
    in the handler itself before returning.
    Otherwise, the channel will continuously raise readable events because the channel is at end of file."

    Are the chan event readable {} and chan close incompatible?
    So, what should I do to correct this issue?


    Luis
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Rich@rich@example.invalid to comp.lang.tcl on Wed Apr 24 20:40:59 2024
    From Newsgroup: comp.lang.tcl

    Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
    Hi,

    I'm using an async channel to read the log from an external command.

    The code skeleton is like this:

    And includes a syntax error (missing brace).

    namespace eval nsc {
    proc runCommand {exec_id} {
    set run_${exec_id} [Parse new $exec_id]
    }

    oo::class create Parse {
    variable chan_ans
    constructor {exec_id} {
    set chan_ans [open ![list {*}$comm] r]
    chan configure $chan_ans -blocking 0 -buffering line
    chan event $chan_ans readable [list [self object] parseLine]
    }
    method parseLine {} {
    set status [catch {chan gets $chan_ans line} nchars]
    .....
    if {$status || [chan eof $chan_ans]} {
    # all received
    chan configure $chan_ans -blocking 1 # to assure all is written

    Syntax error. Tcl is not bash, you only get to insert a comment using
    # where Tcl's expecting a command. So 'same line' comments have to
    terminate the current command first (with a ;) and then the comment can
    start. The above line needs to read:

    chan configure $chan_ans -blocking 1 ;# to assure all is written

    To avoid the syntax error. And once the syntax error is fixed, and a
    missing } inserted, your code works just fine.

    Number two, given that you have a read only channel open in this code
    example, there is no need to flip to blocking, as there is no 'write
    data' in a read only channel.

    chan event $chan_ans readable {}

    Unnecessary if you are going to also close the channel, as closing the
    channel takes care of removing the readable event.

    chan close $chan_ans
    set ::exit_flag 1
    }
    }
    }

    But the script never finishes, seems to complain at the line
    'chan close $chan_ans'.

    It never finishes because the syntax error from the incorrect inline
    comment aborts the event call, such that no more event calls happen, so there's never another entry to your object to ultimately set the vwait variable and abort the script.

    So, what should I do to correct this issue?

    Fix the comment syntax error (which was all I needed to fix to make the example you posted work properly). I'm assuming the missing close
    brace (}) was a copy/paste omission here.
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Emiliano@emil.gavilan@gmail.com to comp.lang.tcl on Wed Apr 24 22:50:25 2024
    From Newsgroup: comp.lang.tcl

    On 24 Apr 2024 16:01:54 GMT
    Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:

    Hi,

    I'm using an async channel to read the log from an external command.

    The code skeleton is like this:

    while {[llength $in_queue] > 0} {
    nsc::runCommand $exec_id
    vwait ::exit_flag
    }

    namespace eval nsc {
    proc runCommand {exec_id} {
    set run_${exec_id} [Parse new $exec_id]
    }

    oo::class create Parse {
    variable chan_ans
    constructor {exec_id} {
    set chan_ans [open ![list {*}$comm] r]
    chan configure $chan_ans -blocking 0 -buffering line
    chan event $chan_ans readable [list [self object] parseLine]
    }
    method parseLine {} {
    set status [catch {chan gets $chan_ans line} nchars]
    .....
    if {$status || [chan eof $chan_ans]} {
    # all received
    chan configure $chan_ans -blocking 1 # to assure all is written
    chan event $chan_ans readable {}
    chan close $chan_ans
    set ::exit_flag 1
    }
    }
    }

    But the script never finishes, seems to complain at the line
    'chan close $chan_ans'.

    If the command $comm writes to stderr, or returns a value != 0, then
    [chan close] will raise an error, as shown in this interactive session:

    % set fd [open |[list /bin/sh -c {echo "test"; echo "error" >&2}] r]
    file6
    % chan read $fd
    test

    % catch {chan close $fd} e o
    1
    % set e
    error
    % set o
    -code 1 -level 0 -errorstack {INNER {invokeStk1 ::tcl::chan::close file6}} -errorcode NONE -errorinfo {error
    while executing
    "chan close $fd"} -errorline 1

    The code pattern I would use in such case is

    if {[chan gets $chan_ans line] < 0} {
    if {[chan eof $chan_ans]} {
    # use [catch] to avoid errors in closing the pipeline
    catch {chan close $chan_ans}
    set ::exit_flag 1
    }
    return
    }
    # process $line

    In the book "The Tcl Programming Language", by Ashok, in page 437 (466 of the PDF) there's this:
    "When an end of file is seen on a channel, it is crucial to EITHER remove the read handler from the channel aw we have done, or to close the channel in the handler itself before returning.
    Otherwise, the channel will continuously raise readable events because the channel is at end of file."

    Are the chan event readable {} and chan close incompatible?
    So, what should I do to correct this issue?

    The book is (unsurprisingly) correct; closing the channel inside the handler
    is the right thing to do.
    In your case, while the channel gets closed and the event handler unregistered, the line

    set ::exit_flag 1

    is never executed because the previous line raises an error as explained above.

    Hope this helps.
    --
    Emiliano <emil.gavilan@gmail.com>
    --- Synchronet 3.20a-Linux NewsLink 1.114
  • From Andreas Leitgeb@avl@logic.at to comp.lang.tcl on Thu Apr 25 21:59:30 2024
    From Newsgroup: comp.lang.tcl

    Rich <rich@example.invalid> wrote:
    Luis Mendes <luisXXXlupeXXX@gmail.com> wrote:
    Hi,
    I'm using an async channel to read the log from an external command.
    The code skeleton is like this:
    And includes a syntax error (missing brace).

    There are two more... inserted below...

    namespace eval nsc {
    proc runCommand {exec_id} {
    set run_${exec_id} [Parse new $exec_id]
    }

    oo::class create Parse {
    variable chan_ans
    constructor {exec_id} {
    set chan_ans [open ![list {*}$comm] r]

    to create a pipe-channel, the first char of the "filename" needs
    to be a pipe char ("|"), not an exclamation mark!

    It is possible that this exclam is really just caused by some newsclient
    going berserk and wildly replacing characters... If it were in original
    code, then it would likely not even get as far as the "chan close" line,
    but instead already bomb here in the constructor ...

    Also, I don't see where the variable "comm" comes from. Maybe the
    part defining it has been trimmed since the OP, otherwise variable
    comm needs to be defined somewhere and eventually made known within
    the constructor... (at least, if it is a global or namespace-
    variable - not sure about class members)

    chan configure $chan_ans -blocking 0 -buffering line
    chan event $chan_ans readable [list [self object] parseLine]
    }
    method parseLine {} {
    set status [catch {chan gets $chan_ans line} nchars]
    .....
    if {$status || [chan eof $chan_ans]} {
    # all received
    chan configure $chan_ans -blocking 1 # to assure all is written

    Syntax error. Tcl is not bash, you only get to insert a comment using
    # where Tcl's expecting a command. So 'same line' comments have to terminate the current command first (with a ;) and then the comment can start. The above line needs to read:

    chan configure $chan_ans -blocking 1 ;# to assure all is written

    To avoid the syntax error. And once the syntax error is fixed, and a missing } inserted, your code works just fine.

    Number two, given that you have a read only channel open in this code example, there is no need to flip to blocking, as there is no 'write
    data' in a read only channel.

    Not quite... making the command channel blocking before the close
    is necessary to capture the exit-code of the pipeline process.

    chan event $chan_ans readable {}

    Unnecessary if you are going to also close the channel, as closing the channel takes care of removing the readable event.

    chan close $chan_ans

    But for that, the "chan close" had better be wrapped in a catch or
    try statement, in order to handle non-zero exit codes from child.
    Otherwise, a non-zero exit code from child process would just bomb
    out of the tcl script, and skip setting the following variable.

    set ::exit_flag 1
    }
    }
    }
    But the script never finishes, seems to complain at the line
    'chan close $chan_ans'.

    Most likely, the child process actually returns non-zero.
    So, tcl throws an exception from the "close".

    It never finishes because the syntax error from the incorrect inline
    comment aborts the event call, such that no more event calls happen, so there's never another entry to your object to ultimately set the vwait variable and abort the script.

    So, what should I do to correct this issue?

    Fix the comment syntax error (which was all I needed to fix to make the example you posted work properly). I'm assuming the missing close
    brace (}) was a copy/paste omission here.


    --- Synchronet 3.20a-Linux NewsLink 1.114