RECVFROM addresses with FORK option hung after processing the first packet

This commit is contained in:
Gerhard Rieger 2008-05-22 13:54:10 +02:00
parent fe1337fe5f
commit d086001911
4 changed files with 101 additions and 27 deletions

View file

@ -4,6 +4,9 @@ corrections:
with the first received packet an error occurred: with the first received packet an error occurred:
socket_init(): unknown address family 0 socket_init(): unknown address family 0
RECVFROM addresses with FORK option hung after processing the first
packet.
####################### V 1.6.0.1: ####################### V 1.6.0.1:
new features: new features:

View file

@ -1 +1 @@
"1.6.0.1+ip4bind" "1.6.0.1+ip4bind+recvfromfork"

48
test.sh
View file

@ -8195,6 +8195,54 @@ PORT=$((PORT+1))
N=$((N+1)) N=$((N+1))
# there was a bug in *-recvfrom with fork: due to an error in the appropriate
# signal handler the master process would hang after forking off the first
# child process.
NAME=UDP4RECVFROM_FORK
case "$TESTS" in
*%functions%*|*%ip4%*|*%udp%*|*%dgram%*|*%$NAME%*)
TEST="$NAME: test if UDP4-RECVFROM handles more than one packet"
# idea: run a UDP4-RECVFROM process with fork and -T. Send it one packet;
# send it a second packet and check if this is processed properly. If yes, the
# test succeeded.
tf="$td/test$N.stdout"
te="$td/test$N.stderr"
tdiff="$td/test$N.diff"
tsp=$PORT
ts="$LOCALHOST:$tsp"
da=$(date)
CMD1="$SOCAT $opts -T 2 UDP4-RECVFROM:$tsp,reuseaddr,fork PIPE"
CMD2="$SOCAT $opts -T 1 - UDP4-SENDTO:$ts"
printf "test $F_n $TEST... " $N
$CMD1 >/dev/null 2>"${te}1" &
pid1=$!
waitudp4port $tsp 1
echo "$da" |$CMD2 >/dev/null 2>>"${te}2" # this should always work
rc2a=$?
sleep 1
echo "$da" |$CMD2 >"$tf" 2>>"${te}3" # this would fail when bug
rc2b=$?
kill $pid1 2>/dev/null; wait
if [ $rc2b -ne 0 ]; then
$PRINTF "$NO_RESULT\n"
numCANT=$((numCANT+1))
elif ! echo "$da" |diff - "$tf" >"$tdiff"; then
$PRINTF "$FAILED: $SOCAT:\n"
echo "$CMD1 &"
echo "$CMD2"
cat "${te}1" "${te}2" "${te}3"
cat "$tdiff"
numFAIL=$((numFAIL+1))
else
$PRINTF "$OK\n"
if [ -n "$debug" ]; then cat "${te}1" "${te}2" "${te}3"; fi
numOK=$((numOK+1))
fi ;;
esac
PORT=$((PORT+1))
N=$((N+1))
echo "summary: $((N-1)) tests; $numOK ok, $numFAIL failed, $numCANT could not be performed" echo "summary: $((N-1)) tests; $numOK ok, $numFAIL failed, $numCANT could not be performed"
if [ "$numFAIL" -gt 0 ]; then if [ "$numFAIL" -gt 0 ]; then

View file

@ -509,8 +509,26 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */
} }
static pid_t xio_waitingfor; /* when the recvfrom address (with option fork) receives a packet it keeps this
static bool xio_hashappened; packet in the IP stacks input queue and forks a sub process. The sub process
then reads this packet for processing its data.
There is a problem because the parent process would find the same packet
again if it calls select()/poll() before the client process reads the
packet.
To solve this problem we implement the following mechanism:
The sub process sends a SIGUSR1 when it has read the packet (or a SIGCHLD if
it dies before). The parent process waits until it receives that signal and
only then continues to listen.
To prevent a signal from another process to trigger our loop, we pass the
pid of the sub process to the signal handler in xio_waitingfor. The signal
handler sets xio_hashappened if the pid matched.
*/
static pid_t xio_waitingfor; /* info from recv loop to signal handler:
indicates the pid that of the child process
that should send us the USR1 signal */
static bool xio_hashappened; /* info from signal handler to loop: child
process has read ("consumed") the packet */
/* this is the signal handler for USR1 and CHLD */
void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) { void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) {
pid_t pid; pid_t pid;
int _errno; int _errno;
@ -519,6 +537,7 @@ void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) {
Debug5("xiosigaction_hasread(%d, {%d,%d,%d,"F_pid"}, )", Debug5("xiosigaction_hasread(%d, {%d,%d,%d,"F_pid"}, )",
signum, siginfo->si_signo, siginfo->si_errno, siginfo->si_code, signum, siginfo->si_signo, siginfo->si_errno, siginfo->si_code,
siginfo->si_pid); siginfo->si_pid);
if (signum == SIGCHLD) {
_errno = errno; _errno = errno;
do { do {
pid = Waitpid(-1, &status, WNOHANG); pid = Waitpid(-1, &status, WNOHANG);
@ -527,12 +546,14 @@ void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) {
"waitpid(-1, {}, WNOHANG): no child has exited"); "waitpid(-1, {}, WNOHANG): no child has exited");
Info("childdied() finished"); Info("childdied() finished");
errno = _errno; errno = _errno;
Debug("xiosigaction_hasread() ->");
return; return;
} else if (pid < 0 && errno == ECHILD) { } else if (pid < 0 && errno == ECHILD) {
Msg1(wassig?E_INFO:E_WARN, Msg1(wassig?E_INFO:E_WARN,
"waitpid(-1, {}, WNOHANG): %s", strerror(errno)); "waitpid(-1, {}, WNOHANG): %s", strerror(errno));
Info("childdied() finished"); Info("childdied() finished");
errno = _errno; errno = _errno;
Debug("xiosigaction_hasread() ->");
return; return;
} }
wassig = true; wassig = true;
@ -540,9 +561,11 @@ void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) {
Warn2("waitpid(-1, {%d}, WNOHANG): %s", status, strerror(errno)); Warn2("waitpid(-1, {%d}, WNOHANG): %s", status, strerror(errno));
Info("childdied() finished"); Info("childdied() finished");
errno = _errno; errno = _errno;
Debug("xiosigaction_hasread() ->");
return; return;
} }
} while (1); } while (1);
}
if (xio_waitingfor == siginfo->si_pid) { if (xio_waitingfor == siginfo->si_pid) {
xio_hashappened = true; xio_hashappened = true;
} }