diff --git a/CHANGES b/CHANGES index 406c48a..213c651 100644 --- a/CHANGES +++ b/CHANGES @@ -4,6 +4,9 @@ corrections: with the first received packet an error occurred: socket_init(): unknown address family 0 + RECVFROM addresses with FORK option hung after processing the first + packet. + ####################### V 1.6.0.1: new features: diff --git a/VERSION b/VERSION index ec9f0dd..5f22ae7 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -"1.6.0.1+ip4bind" +"1.6.0.1+ip4bind+recvfromfork" diff --git a/test.sh b/test.sh index 05e9957..9d2fff7 100755 --- a/test.sh +++ b/test.sh @@ -8195,6 +8195,54 @@ PORT=$((PORT+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" if [ "$numFAIL" -gt 0 ]; then diff --git a/xio-socket.c b/xio-socket.c index 01fda15..36fdb59 100644 --- a/xio-socket.c +++ b/xio-socket.c @@ -509,8 +509,26 @@ int _xioopen_dgram_sendto(/* them is already in xfd->peersa */ } -static pid_t xio_waitingfor; -static bool xio_hashappened; +/* when the recvfrom address (with option fork) receives a packet it keeps this + 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) { pid_t pid; int _errno; @@ -519,30 +537,35 @@ void xiosigaction_hasread(int signum, siginfo_t *siginfo, void *ucontext) { Debug5("xiosigaction_hasread(%d, {%d,%d,%d,"F_pid"}, )", signum, siginfo->si_signo, siginfo->si_errno, siginfo->si_code, siginfo->si_pid); - _errno = errno; - do { - pid = Waitpid(-1, &status, WNOHANG); - if (pid == 0) { - Msg(wassig?E_INFO:E_WARN, - "waitpid(-1, {}, WNOHANG): no child has exited"); - Info("childdied() finished"); - errno = _errno; - return; - } else if (pid < 0 && errno == ECHILD) { - Msg1(wassig?E_INFO:E_WARN, - "waitpid(-1, {}, WNOHANG): %s", strerror(errno)); - Info("childdied() finished"); - errno = _errno; - return; - } - wassig = true; - if (pid < 0) { - Warn2("waitpid(-1, {%d}, WNOHANG): %s", status, strerror(errno)); - Info("childdied() finished"); - errno = _errno; - return; - } - } while (1); + if (signum == SIGCHLD) { + _errno = errno; + do { + pid = Waitpid(-1, &status, WNOHANG); + if (pid == 0) { + Msg(wassig?E_INFO:E_WARN, + "waitpid(-1, {}, WNOHANG): no child has exited"); + Info("childdied() finished"); + errno = _errno; + Debug("xiosigaction_hasread() ->"); + return; + } else if (pid < 0 && errno == ECHILD) { + Msg1(wassig?E_INFO:E_WARN, + "waitpid(-1, {}, WNOHANG): %s", strerror(errno)); + Info("childdied() finished"); + errno = _errno; + Debug("xiosigaction_hasread() ->"); + return; + } + wassig = true; + if (pid < 0) { + Warn2("waitpid(-1, {%d}, WNOHANG): %s", status, strerror(errno)); + Info("childdied() finished"); + errno = _errno; + Debug("xiosigaction_hasread() ->"); + return; + } + } while (1); + } if (xio_waitingfor == siginfo->si_pid) { xio_hashappened = true; }