开发者

Data in linux FIFO seems lost

I have a bash script which wants to do some work in parallel, I did this by putting each job in an subshell which is run in the backgroun开发者_如何学JAVAd. While the number of jobs running simultaneously should under some limit, I achieve this by first put some lines in a FIFO, then just before forking the subshell, the parent script is required to read a line from this FIFO. Only after it gets a line can it fork the subshell. Up to now, everything works fine. But when I tried to read a line from the FIFO in the subshell, it seems that only one subshell can get a line, even if there are apparently more lines in the FIFO. So I wonder why other subshell(s) cannot read a line even when there are more lines in the FIFO.

My testing code looks something like this:


#!/bin/sh

fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path

process_num=5
#put $process_num lines in the FIFO

for ((i=0; i<${process_num}; i++)); do
    echo "$i"
done >&6

delay_some(){
    local index="$1"
    echo "This is what u can see. $index \n"
    sleep 20;
}

#In each iteration, try to read 2 lines from FIFO, one from this shell,
#the other from the subshell
for i in 1 2
do
    date >>/tmp/fy_date
#If a line can be read from FIFO, run a subshell in bk, otherwise, block.
    read -u6
    echo " $$ Read --- $REPLY  --- from 6 \n" >> /tmp/fy_date
    {
        delay_some $i
#Try to read a line from FIFO, __ only one subshell succeeds the following line. __
        read -u6
        echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
    } &
done

And the output file /tmp/fy_date has content of:


Mon Apr 26 16:02:18 CST 2010
 32561 Read --- 0  --- from 6 \n
Mon Apr 26 16:02:18 CST 2010
 32561 Read --- 1  --- from 6 \n
 32561 This is in child # 1, read --- 2 --- from 6 \n

There, I expect a line like this:


 32561 This is in child # 2, read --- 3 --- from 6 \n

But it never appears, and the child #2 process is blocked there until I issue:

echo something > /tmp/fy_u_test2.fifo


Is it possible there is some buffering going on of your write to the fifo? If you have unbuffer available, could you try prefacing the echos with that? I don't really see how it could happen here but the symptoms fit so its worth a shot.


Keep in mind that a FIFO on POSIX systems is essentially a named pipe. In order to move data around on a pipe, one side needs a reader and the other side needs a writer, and when one is closed the other loses usefulness.

In other words, you cannot cat on a fifo after some other reader has exited, because the contents of the FIFO will be gone.

You may want to see about using a normal file (and use file locking to ensure that you are synchronizing your access to that normal file), or use a directory with multiple files in it, or even use shared memory or something similar to that (perhaps not in a shell script, though). It all depends on what your end-goal is, really, what the best way to go about it would be.


Seems something to do with 'read -u6' shell call. If I have STDIN of the shell closed, when 'read -u6' is issued, it tries to read 128 bytes from fd 6. But if STDIN is left untouched, when 'read -u6' is issued, it reads the bytes one by one until a '\n' is encountered. I discovered this weird action from 'strace', where in the first case, a 'read -u6' call caused the following syscall:

read(6, "0\n1\n2\n3\n4\n5\n6\n7\n8\n9\n10\n11\n12\n13\n"..., 128) = 50

and in the latter case, a 'read -u6' call caused the following syscall:

30371 16:27:15 read(6, "0", 1)          = 1
30371 16:27:15 read(6, "\n", 1)         = 1

Testing code follows:


#!/bin/bash

fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path

#comment or decomment the following line makes difference
exec 0>&-

process_num=20
#put $process_num lines in the FIFO
for ((i=0;i<${process_num};i++));do
    echo "$i"
done >&6

delay_some(){
    local index="$1"
    echo "This is what u can see. $index \n"
    sleep 10;
}

#In each iteration, try to read 2 lines from FIFO, one from this shell,
#the other from the subshell
for i in 1 2 3
do
    date >>/tmp/fy_date
#If a line can be read from FIFO, run a subshell in bk, otherwise, block.
    read -u6
    echo " $$ Read --- $REPLY  --- from 6 \n" >> /tmp/fy_date
    {
        delay_some $i
#Try to read a line from FIFO
#   read -u6
        echo " $$ This is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
        echo " $$ Again this is in child # $i, read --- $REPLY --- from 6 \n" >> /tmp/fy_date
        echo "$i xx" >&6
#       echo xx >&6
    } &
done

#sleep 13
#wait
#read -u6
echo "$$ After fork, in parent, read --- $REPLY --- from 6 \n" >> /tmp/fy_date


I get all four lines in the log file when I run it. What happens if you change your shebang to #!/bin/bash?


It could be a concurrency issue, with both subshells trying to read from the same fifo at the same time. Does it happen all the time?

You could try adding a flock -x 6 statement or change the delay for the two subshells and see what happens.

BTW, I can confirm that with bash 3.2 and kernel 2.6.28 your code works fine.


I found data left unread in the FIFO when the parent shell exits got lost as the parent exits.
If I have the following code:


#!/bin/sh

fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path

process_num=9
#put $process_num lines in the FIFO

for ((i=0;i<${process_num};i++));do
echo "$i"
done >&6

for i in 1 2 3;
do
 read -u6
done

After this code ends, a command 'cat /tmp/fy_u_test2.fifo' gives nothing.
BUT if I have the following code.


#!/bin/sh

fifo_path="/tmp/fy_u_test2.fifo"
mkfifo $fifo_path
#open fifo for r/w at fd 6
exec 6<> $fifo_path

process_num=9
#put $process_num lines in the FIFO

for ((i=0;i<${process_num};i++));do
echo "$i"
done >&6

for i in 1 2 3;
do
 read -u6
done
#__ notice this line __
sleep 60

After issuing this code to run, during its sleeping 60sec, a command 'cat /tmp/fy_u_test2.fifo' gives the following output:

$ cat /tmp/fy_u_test2.fifo 
3
4
5
6
7
8


For reasons explained in other answers here you do not want a pipe unless you can read and write from the pipe at the same time.

It is therefore advisable to use another means of IPC or restructure your usage of fifos such that an asynchronous process fills up the pipe while the main process creates work processes (or the other way around).

Here's a method of getting what you want using a simple file as a sort of queue:

#!/usr/bin/env bash

stack=/tmp/stack
> "$stack"

# Create an initial 5 spots on the stack
for i in {1..5}; do
    echo >> "$stack"
done

for i in {1..10}; do
    # Wait for a spot on the stack.
    until read; do sleep 1; done

    {
        echo "Starting process #$i"
        sleep $((5 + $i)) # Do something productive
        echo "Ending process #$i"

        # We're done, free our spot on the stack.
        echo >> "$stack"
    } &
done < "$stack"

Sidenote: This method isn't ideal for unlimited work since it adds a byte to the stack file for each process it invokes meaning the stack file grows slowly.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜