TIP #332: HALF-CLOSE FOR BIDIRECTIONAL CHANNELS ================================================= Version: $Revision: 1.6 $ Author: Alexandre Ferrieux State: Final Type: Project Tcl-Version: 8.6 Vote: Done Created: Thursday, 25 September 2008 URL: https://tip.tcl-lang.org332.html Post-History: Obsoletes: TIP #301 ------------------------------------------------------------------------- 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 [TIP #301]. Then, [TIP #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 [TIP #301] had very long-ranging effects on various channel implementations, which are more numerous today than before (TLS, reflection API). As a consequence, [TIP #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 []. 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. ------------------------------------------------------------------------- TIP AutoGenerator - written by Donal K. Fellows