IPC via named pipe: Difference between revisions

From Rosetta Code
Content added Content deleted
(→‎Tcl: Added implementation)
(→‎{{header|Ruby}}: The event loop now works, but the code is bad enough to want {{improve}}.)
Line 11: Line 11:


=={{header|Ruby}}==
=={{header|Ruby}}==
{{improve|Ruby|
{{in progress|lang=Ruby|day=30|month=September|year=2011}}
* Find a way to report errors from inside ''open_sesame'', such as Errno::ENOENT.
* Check that open file is a FIFO: <code>foopipe.stat.pipe?</code>
}}


With [[OpenBSD]], we observe that open(2) a named pipe blocks ''all threads'' in a process. (This must be bug in thread library.) So, we fork(2) other process to call open(2), and apply UNIXSocket to send IO object.
With [[OpenBSD]], we observe that open(2) a named pipe blocks ''all threads'' in a process. (This must be bug in thread library.) So, we fork(2) other process to call open(2), and apply UNIXSocket to send IO object.

{{works with|Unix}}


<lang ruby>require 'socket'
<lang ruby>require 'socket'


# Ruby has no direct access to mkfifo(2). We use a shell script.
# Ruby has no direct access to mkfifo(2). We use a shell script.
system 'sh', '-c', <<EOF or abort
system '/bin/sh', '-c', <<EOF or abort
test -p in || mkfifo in || exit
test -p in || mkfifo in || exit
test -p out || mkfifo out || exit
test -p out || mkfifo out || exit
Line 24: Line 29:


# Forks a process to open _path_. Returns a _socket_ to receive the open
# Forks a process to open _path_. Returns a _socket_ to receive the open
# IO object (by UNIXSocket#recv_io), and the _pid_ of the process.
# IO object (by UNIXSocket#recv_io).
def open_sesame(path, mode)
def open_sesame(path, mode)
reader, writer = UNIXSocket.pair
reader, writer = UNIXSocket.pair
Line 36: Line 41:
end
end
end
end
Process.detach pid
writer.close
writer.close
return reader, pid
return reader
end
end


insock, inpid = open_sesame("in", "rb")
insock = open_sesame("in", "rb")
outsock, outpid = open_sesame("out", "w")
outsock = open_sesame("out", "w")
Process.detach(inpid)
Process.detach(outpid)
inpipe, outpipe = nil
inpipe, outpipe = nil
count = 0
count = 0
Line 53: Line 57:
case reader
case reader
when insock
when insock
inpipe = reader.recv_io
inpipe = insock.recv_io
puts "-- Opened 'in' pipe."
insock.close
readers.delete insock
readers.push inpipe
when outsock
when outsock
outpipe = reader.recv_io
outpipe = outsock.recv_io
puts "-- Opened 'out' pipe."
outsock.close
readers.delete outsock
writers.push outpipe
when inpipe
when inpipe
count += (inpipe.read_nonblock.size rescue 0)
count += (inpipe.read_nonblock(4096).size rescue 0)
end
end
end
end
Line 64: Line 76:
when outpipe
when outpipe
outpipe.puts count
outpipe.puts count
puts "-- Counted #{count} bytes."
exit
exit
end
end
Line 69: Line 82:
end</lang>
end</lang>


Example run:
<pre>$ ruby count.rb

count.rb:39:in `recv_io': file descriptor was not passed (msg_controllen=0 smaller than sizeof(struct cmsghdr)=12) (SocketError)
{| class="wikitable"
from count.rb:39:in `block (2 levels) in <main>'
from count.rb:36:in `each'
| style="vertical-align: top; width: 50%;" | <pre>$ ruby count.rb
-- Opened 'in' pipe.
from count.rb:36:in `block in <main>'
-- Opened 'out' pipe.
from count.rb:34:in `loop'
-- Counted 32 bytes.
from count.rb:34:in `<main>'</pre>
$</pre>
| style="vertical-align: top; width: 50%;" | <pre>$ echo 'This is line 1.' > in
$ echo 'This is line 2.' > in
$ cat out
32
$</pre>
|}


=={{header|Tcl}}==
=={{header|Tcl}}==

Revision as of 17:59, 30 September 2011

IPC via named pipe is a draft programming task. It is not yet considered ready to be promoted as a complete task, for reasons that should be found in its talk page.

Named pipe, or FIFO, is a way of providing inter-process communications (IPC). To demonstrate how it works, create two pipes, say, "in" and "out" (choose suitable names for your system), and write a program that works the two pipes such that:

  1. Data written to the "in" FIFO will be discarded except the byte count, which will be added to a total tally kept by the program;
  2. Whenever another process reads the "out" FIFO, it should receive the total count so far.

Possible issues:

  • Chances are you don't already have "in" and "out" pipes lying around. Create them within your program or without, at your discretion. You may assume they are already created for you.
  • Your program may assume it's the sole reader on "in" and the sole writer on "out".
  • Read/write operations on pipes are generally blocking. Make your program responsive to both pipes, so that it won't block trying to read the "in" pipe while leaving another process hanging on the other end of "out" pipe indefinitely -- or vice versa. You probably need to either poll the pipes or use multi-threading.
  • You may assume other processes using the pipes behave; specificially, your program may assume the process at the other end of a pipe will not unexpectedly break away before you finish reading or writing.

Ruby

This example is in need of improvement:
  • Find a way to report errors from inside open_sesame, such as Errno::ENOENT.
  • Check that open file is a FIFO: foopipe.stat.pipe?

With OpenBSD, we observe that open(2) a named pipe blocks all threads in a process. (This must be bug in thread library.) So, we fork(2) other process to call open(2), and apply UNIXSocket to send IO object.

Works with: Unix

<lang ruby>require 'socket'

  1. Ruby has no direct access to mkfifo(2). We use a shell script.

system '/bin/sh', '-c', <<EOF or abort test -p in || mkfifo in || exit test -p out || mkfifo out || exit EOF

  1. Forks a process to open _path_. Returns a _socket_ to receive the open
  2. IO object (by UNIXSocket#recv_io).

def open_sesame(path, mode)

 reader, writer = UNIXSocket.pair
 pid = fork do
   begin
     reader.close
     file = File.open(path, mode)
     writer.send_io file
   ensure
     exit!
   end
 end
 Process.detach pid
 writer.close
 return reader

end

insock = open_sesame("in", "rb") outsock = open_sesame("out", "w") inpipe, outpipe = nil count = 0 readers = [insock, outsock] writers = [] loop do

 selection = select(readers, writers)
 selection[0].each do |reader|
   case reader
   when insock
     inpipe = insock.recv_io
     puts "-- Opened 'in' pipe."
     insock.close
     readers.delete insock
     readers.push inpipe
   when outsock
     outpipe = outsock.recv_io
     puts "-- Opened 'out' pipe."
     outsock.close
     readers.delete outsock
     writers.push outpipe
   when inpipe
     count += (inpipe.read_nonblock(4096).size rescue 0)
   end
 end
 selection[1].each do |writer|
   case writer
   when outpipe
     outpipe.puts count
     puts "-- Counted #{count} bytes."
     exit
   end
 end

end</lang>

Example run:

$ ruby count.rb  
-- Opened 'in' pipe.
-- Opened 'out' pipe.
-- Counted 32 bytes.
$
$ echo 'This is line 1.' > in
$ echo 'This is line 2.' > in
$ cat out
32
$

Tcl

<lang tcl># Make the pipes by calling a subprocess... exec sh -c {test -p in || mkfifo in || exit 1;test -p out || exec mkfifo out}

  1. How many bytes have we seen so far?

set count 0

  1. Read side; uses standard fileevent mechanism (select() under the covers)

set in [open in {RDONLY NONBLOCK}] fconfigure $in -translation binary fileevent $in readable consume proc consume {} {

   global count in
   # Reads only 4kB at a time
   set data [read $in 4096]
   incr count [string length $data]

}

  1. Writer side; relies on open() throwing ENXIO on non-blocking open of write side

proc reportEveryHalfSecond {} {

   global count
   catch {

set out [open out {WRONLY NONBLOCK}] puts $out $count close $out

   }
   # Polling nastiness!
   after 500 reportEveryHalfSecond

} reportEveryHalfSecond

  1. Run the event loop until done

vwait forever</lang>