Version 5 of FIFO

Updated 2008-08-10 19:29:21 by lars_h

In an interesting c.l.t posting, Colin Macleod describes what it takes to make a Unix FIFO (first in, first out) or named pipe, event-based:


I happen to be working on a project just now where I want to have an event-driven Tcl process read from a fifo. (This is on Solaris 2.6, in case it makes a difference.) My first attempts suggested that this could only be done by polling, but after digging around more I found that I could get fileevent to work as follows:

 proc readpipe pipe {
   set data [read $pipe]
   ... process $data ...
 }

 ### Open the fifo, set up event-driven input from it.
 proc open_pipe pipename {
   exec /bin/sh -c "sleep 10 > $pipename" &
   set pipe [open $pipename r]
   fconfigure $pipe -blocking 0
   fileevent $pipe readable [list readpipe $pipe]
   set dummy [open $pipename w]
   after 20000 {exec /bin/true}; # to get zombie sleep reaped
 }

The tricky bits are:

  1. The process opening the fifo to read will block until another process opens it to write. I get past this by exec-ing a shell which opens the pipe, writes nothing, then exits after 10 seconds. Note - it is critical that the "> $pipename" redirection is done by the shell (after it is forked as a new process) and not by Tcl.
  2. If the fifo is closed by the writing process, fileevent will fire continuously because it sees end-of-file on the fifo. We can avoid this by making sure that at least one process always has the fifo open for writing, so I also open the fifo for writing from the same Tcl process and never write to it or close it. The 10-second delay in step 1 is to give this time to happen.
  3. Finally, I noticed that the exec-ed shell would hang around as a zombie after the sleep finished. The delayed and apparently pointless exec at the end is because running exec again has the side-effect of wait-ing for terminated background processes - documented under Tcl_DetachPids(3).

Lars H, 2008-08-10: My experience on the opening problem is slightly different:

  1. Opening a pipe for reading is no problem, provided that you use the {RDONLY NONBLOCK} access mode; open returns immediately, and you can configure the channel as you wish.
  2. Opening a pipe for writing is hazardous, because the open will block until the pipe has also been opened for reading; the access mode {WRONLY NONBLOCK} is rejected by the OS (throws error). The work-around is to first open the pipe for reading as above, second open it for writing (no NONBLOCK), and third close the read channel to the pipe.

Without suggesting that it is 100% bulletproof, this works fairly well:

 ## ********************************************************
 ##
 ## Name: fifo
 ##
 ## Description:
 ## Provide an anonymous fifo using cat.
 ##
 ## Parameters:
 ##
 ## Usage:
 ## trivial handler example:
 ##
 ## proc handle { fifo } {
 ##      puts -nonewline [ gets $fifo ]
 ## }
 ##
 ## set fifo [ fifo handle ]
 ## puts $fifo peep!
 ##
 ## Comments:
 ## The input side of the fifo should never be flushed!
 ## The fifo can be closed like an ordinary file.

 proc fifo { { handler "" } } {
      if { [ catch {
      set fifo [ open |cat a+ ]
      fconfigure $fifo -blocking off
      fconfigure $fifo -buffering none
      if { [ string length $handler ] } {
         fileevent $fifo readable "$handler $fifo"
      }
      } err ] } {
         return -code error "[ myName ]: $err"
      }
      set fifo
 }
 ## ********************************************************

(Explain how general file append operations can be detected as events only through polling.)

An example of which can be seen in tailf


[ Category Interprocess Communication | Category Channel | Category Acronym ]