TIP:            332
Title:          Half-Close for Bidirectional Channels
Version:        $Revision: 1.6 $
Author:         Alexandre Ferrieux <alexandre.ferrieux@gmail.com>
State:          Final
Type:           Project
Vote:           Done
Created:        25-Sep-2008
Post-History:   
Keywords:       Tcl,channel,close,socket,shutdown
Obsoletes:      301
Tcl-Version:    8.6

~ Abstract

This TIP proposes to extend the '''close'''/'''chan close''' commands to let
them perform an unidirectional "half-close" on bidirectional channels.

~ Background

Bidirectional channels (sockets and command pipelines) allow Tcl to make an
efficient use of a "filter process", by exchanging data back and forth over an
abstract "single" channel.

However, this single channel abstraction comes with a too coarse-grained
'''close''' primitive. Indeed, it closes both directions simultaneously, while
it is often desirable to close "gracefully" the half-connection ''to'' the
filter process, leaving the return path open. The effect of such a half-close
is that the filter receives a bona fide EOF alone, without a nearly
simultaneous SIGPIPE on its write end if it happens to be writing at that
time. Moreover, if the filter is itself comprised of a pipeline of processes,
some of which doing buffered I/O, then this graceful EOF may be the only way
of flushing the chain and receiving back precious data.

This technique is supported by all modern OSes: for pipes there are actually
two separate file descriptors/handles, and it suffices to close() the write
side; for sockets, a single fd is used, but a specific syscall, shutdown(),
brings back the ability to half-close. Hence it is fairly natural for a
universal "OS glove" like Tcl to expose this universal feature.

~ Proposed Change

This TIP proposes to extend the '''close''' and twin brother '''chan close'''
commands to take an optional extra "direction" argument, indicating a
half-close on the substream going in that direction:

 > '''close''' ''channel'' ?'''read'''|'''write'''?

When the extra direction argument (which may be abbreviated) is given, first
the OS-level half-close is performed: this means a shutdown() on a socket, and
a close() of one end of a pipe for a command pipeline. Then, the Tcl-level
channel data structure is either kept or freed depending on whether the other
direction is still open:

|	set f [open |command r+]
|	...
|	close $f w ;# $f still exists
|	...
|	close $f r ;# now $f is gone

Also, a single-argument '''close''' on an already half-closed bi-channel is
defined to just "finish the job", which allows to write blind cleanup
procedures easily:

|	if {[catch {
|	  set f [open |command r+]
|	  ...
|	  close $f w
|	  ...
|	} err]} {
|	  ...
|	  close $f ;# close what's left
|	}

In the case of a command pipeline, the child-reaping duty falls upon the
shoulders of the last close or half-close, so that an error condition at this
stage (like "Child exited abnormally") doesn't leak system resources.

Last, a half-close on an already closed half raises an error:

|	set [open |command r+]
|	close $f w
|	close $f w
|	==> channel "file3" wasn't opened for writing

And the same applies to wrong-sided unidirectional channels:

|	set [open filename r]
|	close $f w
|	==> channel "file3" wasn't opened for writing

~ Rationale

The concept has gone full circle. From an initial half-close proposal very
close to the current one, an ambitious '''chan split''' generalization was
born in the surrounding enthusiasm, and specified in [301]. Then, [304]'s
'''chan pipe''' was accepted, which addressed most of the "splitting" demand
(asymmetric fconfigures on both ends of a command pipeline can be done by
redirecting to a standalone pipe). Moreover, in hindsight it appears that the
implementation of [301] had very long-ranging effects on various channel
implementations, which are more numerous today than before (TLS, reflection
API).

As a consequence, [301] is being withdrawn, and the current TIP goes back to
its initial, lower profile: just provide the half-close.

~ C Interface

Luckily, the polymorphic channel API in Tcl has been fitted with a half-close function for nearly 10 years (!): the Tcl_ChannelType structure has had a ''close2proc'' member since 1998 in order to avoid deadlocks when closing a command pipeline. As a consequence, the implementation of half-close can be done with a constant channel ABI, avoiding any compatibility issue for extensions.

Still, the ''close2proc'' member does not have public status. To promote symmetry between the script-level and public C APIs, this TIP proposes to add an entry in the main stub table with the following signature:

   > int '''Tcl_CloseEx'''(Tcl_Interp *''interp'', Tcl_Channel ''chan'', int ''flags'')

The behavior being defined as mirrorring the script-level semantics described bove, where ''flags'' is either 0 (meaning bidirectional close) or one of TCL_CLOSE_READ and TCL_CLOSE_WRITE (meaning half-close).

~ Reference Implementation

See Patch 219159 [https://sourceforge.net/tracker/index.php?func=detail&aid=219159&group_id=10894&atid=310894].

After a discussion with Andreas Kupries, the plan is to half-close-enable only raw (unstacked) channels for the time being, raising an explicit error when trying a half-close on a non-bottom channel. This leaves time to carefully design the necessary API extension of the generic stacking and reflection layers, while preparing a fully compatible change (error cases becoming valid).

On the channel-type-dependent side, only sockets and pipelines will be half-close-enabled for now: these are assumed to represent the most pressing demand.

~ Copyright

This document has been placed in the public domain.

