Search code examples
linuxbashsignalsnamed-pipessigpipe

Trap SIGPIPE when trying to write without reader


I am trying to implement a named-pipe communication solution between two processes in Bash.

The first process writes something to the named pipe:

send(){
    echo 'something' > $NAMEDPIPE
}

And the second script is supposed to read the named pipe like this:

while true;do
  if read line < $NAMEDPIPE;do
      someCommands
  fi
done

Note that the named pipe has been previously created using the traditional command

mkfifo $NAMEDPIPE

My problem is that the reader script is not always running so that if the writer script tries to write to the named-pipe it will stay blocked until a reader connects to the pipe.

I want to avoid this behavior, and a solution would be to trap a SIGPIPE signal. Indeed, according to man 7 signal is supposed to be sent when trying to write in a pipe with no reader. So I changed my red function by:

read(){
    trap 'echo "SIGPIPE received"' SIGPIPE
    echo 'something' > $NAMEDPIPE
}

But when I run the reader script, the script stays blocked, and "SIGPIPE received" does not get printed.

Am I mistaking on the signal mechanism or is there any better solution to my problem?


Solution

  • Here's a fun code I just made. Perhaps you can refer to this:

    #!/bin/bash
    
    shopt -s extglob
    
    NAMEDPIPE=/var/run/sr-pipe
    RECEIVER_PID_FILE=/var/run/sr-receiver-pid
    
    function sender_start {
        # Create named pipe.
    
        if [[ -e $NAMEDPIPE ]]; then
            echo "[Sender] Named pipe \"$NAMEDPIPE\" already exists."
        else
            echo "[Sender] Creating named pipe \"$NAMEDPIPE\"."
            mkfifo "$NAMEDPIPE" || {
                echo "Failed to create named pipe \"$NAMEDPIPE\"."
                exit 1
            }
        fi
    
        # Wait for receiver.
    
        echo "[Sender] Waiting for receiver."
        local PID
        until [[ -e $RECEIVER_PID_FILE ]] \
        && read PID < "$RECEIVER_PID_FILE" \
        && [[ $PID == +([[:digit:]]) ]] \
        && kill -s 0 "$PID" &>/dev/null; do
            sleep 1s
        done
        echo "[Sender] Receiver is [now] active."
    
        # Send signal.
    
        kill -s SIGPIPE "$PID"
    
        # Send messages.
    
        local SEND=''
    
        echo "[Sender] Now sending messages."
        while sleep 1s; do
            SEND=$RANDOM
            echo "[Sender] Sending $SEND."
            echo "$SEND" >&4
        done 4>"$NAMEDPIPE"
    }
    
    function receiver_start {
        echo "$BASHPID" > "$RECEIVER_PID_FILE"
    
        echo "[Receiver] Receiver is now active."
    
        local QUIT=false RECEIVE=false
    
        trap 'RECEIVE=true' SIGPIPE
        trap 'QUIT=true' SIGINT SIGTERM SIGHUP
    
        while [[ $QUIT == false ]]; do
            if [[ $RECEIVE == true ]]; then
                RECEIVE=false
                echo "[Receiver] Now receiving messages."
                while [[ $QUIT == false ]] && IFS= read -r -u 4 LINE; do
                    echo "[Receiver] Received $LINE."
                done 4<"$NAMEDPIPE"
            fi
            sleep 1s
        done
    }
    
    if [[ $1 == send ]]; then
        sender_start
    elif [[ $1 == receive ]]; then
        receiver_start
    fi
    

    On one terminal I ran this:

    # bash sender-receiver.sh send
    [Sender] Named pipe "/var/run/sr-pipe" already exists.
    [Sender] Waiting for receiver.
    [Sender] Receiver is [now] active.
    [Sender] Now sending messages.
    [Sender] Sending 21524.
    [Sender] Sending 1460.
    [Sender] Sending 8352.
    [Sender] Sending 4424.
    ...
    

    And on another I got this (corrected):

    # bash sender-receiver.sh receive
    [Receiver] Receiver is now active.
    [Receiver] Now receiving messages.
    [Receiver] Received 21524.
    [Receiver] Received 1460.
    [Receiver] Received 8352.
    [Receiver] Received 4424.
    ...
    

    You probably can run sender on background and receiver on the foreground on the same terminal.