centurian
centurian

Reputation: 1194

What's wrong with named pipes used between 2 processes?

I made 2 processes run in parallel that communicate with a named pipe. I noticed a strange behaviour: every write should be followed by a read or vice versa! If we break the rule the program hangs and if we terminate it with ctrl+C the child still hangs meaning that it just can't re-read any more.

My example:

#!/bin/bash
shopt -u failglob
shopt -s extglob nullglob dotglob

function london (){
   local message answer fifo id return exitcode
   fifo=fifo_$RANDOM.$RANDOM.$RANDOM.$$
   mkfifo ${fifo}
   #trap 'rm -rf "$fifo"' EXIT
   ( berlin $fifo ) &
   id=$!
   echo "parent id: $$, child id: $id"
   message='Greetings from London!(1)'
   echo "$message" > $fifo
   echo "1. parent sent it's 1st message"
   #*****write-to-write error!*****#
   message='Greetings from London!(2)'
   #read -r answer < $fifo
   echo "$message" > $fifo
   echo "2. parent sent it's 2nd message"
   wait
   rm -rf $fifo
}

function berlin (){
   local message answer fifo
   fifo=$1
   read -r answer < $fifo
   echo 'Berlin says:> '"$answer"
   #*****read-to-read error!*****#
   #echo 'next' > $fifo
   read -r answer < $fifo
   echo 'Berlin says:> '"$answer"
}

london

Under the points where I've inserted the "write-to-write" or "read-to-read" messages there are 2 commented lines that solve the problem making me think that the above rule mysteriously holds!!! Any ideas what is going on?

Here's the output:

parent id: 4921, child id: 4923
1. parent sent it's 1st message
2. parent sent it's 2nd message
Berlin says:> Greetings from London!(1)

Thanks!

I think now everything is clear and is condensed in one phrase: "keep the pipe open from the reader's part".

Suppose now, I want to add a second input file descriptor for "some" of my commands inside the loop; how can I do this? Here is my new Berlin function:

function berlin (){
   local message answer fifo
   fifo=$1
   while true; do
      read -r answer
      echo 'Berlin says:> '"$answer"
      #this fails!
      read -r -p "reading from fd0: " <&0
      if [[ $answer = 'quit' ]]; then
         break
      fi
   done < "$fifo" 3>"$fifo"
}

As one can see, we use file descriptor 3 for pipe but when we try to read from fd 0 we actually read from fd 3! Is there a way to achieve this?

Upvotes: 2

Views: 2397

Answers (1)

Spencer Rathbun
Spencer Rathbun

Reputation: 14900

Pipes have a limited amount of data they can hold. If you write to them until they are full, all future writes will block until data is read out. Similarly, reads block if there is no data in the pipe to read.

Most importantly, you need somebody on both sides of the pipe before you can do things. So, you run into a condition where the reader looks at the pipe after you've written your second message. So, as far as it is concerned, there is no data. Then, your primary process finishes, and leaves the child hanging out.

To fix this, don't close the reader side. Use a while loop to keep that end open. Like so:

#!/bin/bash
shopt -u failglob
shopt -s extglob nullglob dotglob

function london (){
   local message answer fifo id return exitcode
   fifo=fifo_$RANDOM.$RANDOM.$RANDOM.$$
   mkfifo ${fifo}
   #trap 'rm -rf "$fifo"' EXIT
   ( berlin $fifo ) & 
   id=$!
   echo "parent id: $$, child id: $id"
   message='Greetings from London!(1)'
   echo "$message" > $fifo
   echo "1. parent sent it's 1st message"
   #*****write-to-write error!*****#
   message='Greetings from London!(2)'
   #read -r answer < $fifo
   echo "$message" > $fifo
   echo "2. parent sent it's 2nd message"
   wait
   rm -rf $fifo
}

function berlin (){
   local message answer fifo
   fifo=$1
   while read -r answer
   do  
       echo 'Berlin says:> '"$answer"
   done < $fifo
}

london

Upvotes: 2

Related Questions