The signal handler for SIGINT/TERM/QUIT and, importantly, SIGPIPE tries to write an informational message to stderr. When however stderr is redirected to a closed pipe, this will cause (another) SIGPIPE, and in turn the signal handler will get called again, and again and again.
Since we intend to exit rtl_fm anyways, we can just ignore this signal from this point on. --- Here is a small test program to verify the bug.
from subprocess import Popen, PIPE p = Popen("rtl_test", stderr=PIPE)
# sighandler is set up after rtlsdr_open() call, so read past that while p.stderr.readline()[:8] != b"Sampling": pass
p.stderr.close() p.terminate() # SIGTERM -> sighandler() -> fprintf(stderr) -> SIGPIPE!
Note that the signal handler is not set up immediately, but only after rtlsdr_open() is called. Due to line buffering/flushing on stderr, we must read from the pipe or rtl_* will stall and SIGTERM/PIPE will still be handled by the SIG_DFL, not triggering the write to the broken pipe.
And in case someone is wondering: Using signal(2) instead of sigaction(2) is OK, as long as it only sets to SIG_DFL or SIG_IGN.
src/rtl_adsb.c | 1 + src/rtl_fm.c | 1 + src/rtl_power.c | 1 + src/rtl_sdr.c | 1 + src/rtl_tcp.c | 1 + src/rtl_test.c | 1 + 6 files changed, 6 insertions(+)
diff --git a/src/rtl_adsb.c b/src/rtl_adsb.c index 7aea8dd..8119ac8 100644 --- a/src/rtl_adsb.c +++ b/src/rtl_adsb.c @@ -123,6 +123,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev); diff --git a/src/rtl_fm.c b/src/rtl_fm.c index 7c84332..037793c 100644 --- a/src/rtl_fm.c +++ b/src/rtl_fm.c @@ -246,6 +246,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dongle.dev); diff --git a/src/rtl_power.c b/src/rtl_power.c index 6204de2..df3ceb7 100644 --- a/src/rtl_power.c +++ b/src/rtl_power.c @@ -195,6 +195,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); do_exit++; multi_bail(); } diff --git a/src/rtl_sdr.c b/src/rtl_sdr.c index e6537ca..2c93b57 100644 --- a/src/rtl_sdr.c +++ b/src/rtl_sdr.c @@ -74,6 +74,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev); diff --git a/src/rtl_tcp.c b/src/rtl_tcp.c index 84f5591..8781ba9 100644 --- a/src/rtl_tcp.c +++ b/src/rtl_tcp.c @@ -144,6 +144,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); rtlsdr_cancel_async(dev); do_exit = 1; diff --git a/src/rtl_test.c b/src/rtl_test.c index 9b44097..b7f46ea 100644 --- a/src/rtl_test.c +++ b/src/rtl_test.c @@ -115,6 +115,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev);
The signal handler for SIGINT/TERM/QUIT and, importantly, SIGPIPE tries to write an informational message to stderr. When however stderr is redirected to a closed pipe, this will cause (another) SIGPIPE, and in turn the signal handler will get called again, and again and again.
Since we intend to exit rtl_fm anyways, we can just ignore this signal. --- Hi all, the first version of the patch got its tabs mangled, so here it is again, fixed (I hope).
Here is a small test program to verify the bug.
from subprocess import Popen, PIPE p = Popen("rtl_test", stderr=PIPE)
# sighandler is set up after rtlsdr_open() call, so read past that while p.stderr.readline()[:8] != b"Sampling": pass
p.stderr.close() p.terminate() # SIGTERM -> sighandler() -> fprintf(stderr) -> SIGPIPE!
Note that the signal handler is not set up immediately, but only after rtlsdr_open() is called. Due to line buffering/flushing on stderr, we must read from the pipe or rtl_* will stall and SIGTERM/PIPE will still be handled by the SIG_DFL, not triggering the write to the broken pipe.
And in case someone is wondering: Using signal(2) instead of sigaction(2) is OK, as long as it only sets to SIG_DFL or SIG_IGN.
src/rtl_adsb.c | 1 + src/rtl_fm.c | 1 + src/rtl_power.c | 1 + src/rtl_sdr.c | 1 + src/rtl_tcp.c | 1 + src/rtl_test.c | 1 + 6 files changed, 6 insertions(+)
diff --git a/src/rtl_adsb.c b/src/rtl_adsb.c index 7aea8dd..8119ac8 100644 --- a/src/rtl_adsb.c +++ b/src/rtl_adsb.c @@ -123,6 +123,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev); diff --git a/src/rtl_fm.c b/src/rtl_fm.c index 7c84332..037793c 100644 --- a/src/rtl_fm.c +++ b/src/rtl_fm.c @@ -246,6 +246,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dongle.dev); diff --git a/src/rtl_power.c b/src/rtl_power.c index 6204de2..df3ceb7 100644 --- a/src/rtl_power.c +++ b/src/rtl_power.c @@ -195,6 +195,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); do_exit++; multi_bail(); } diff --git a/src/rtl_sdr.c b/src/rtl_sdr.c index e6537ca..2c93b57 100644 --- a/src/rtl_sdr.c +++ b/src/rtl_sdr.c @@ -74,6 +74,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev); diff --git a/src/rtl_tcp.c b/src/rtl_tcp.c index 84f5591..8781ba9 100644 --- a/src/rtl_tcp.c +++ b/src/rtl_tcp.c @@ -144,6 +144,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); rtlsdr_cancel_async(dev); do_exit = 1; diff --git a/src/rtl_test.c b/src/rtl_test.c index 9b44097..b7f46ea 100644 --- a/src/rtl_test.c +++ b/src/rtl_test.c @@ -115,6 +115,7 @@ sighandler(int signum) #else static void sighandler(int signum) { + signal(SIGPIPE, SIG_IGN); fprintf(stderr, "Signal caught, exiting!\n"); do_exit = 1; rtlsdr_cancel_async(dev);
Hi,
On 18.11.22 16:20, Tobias Girstmair wrote:
The signal handler for SIGINT/TERM/QUIT and, importantly, SIGPIPE tries to write an informational message to stderr. When however stderr is redirected to a closed pipe, this will cause (another) SIGPIPE, and in turn the signal handler will get called again, and again and again.
Since we intend to exit rtl_fm anyways, we can just ignore this signal.
Thanks for the patch, I've merged it. I actually just exeperienced the same issue.
Regards, Steve
On Sat, Dec 17, 2022 at 09:23:38PM +0100, Steve Markgraf wrote:
Thanks for the patch, I've merged it. I actually just exeperienced the same issue.
Hi Steve,
Thanks for accepting it, and for your work on rtl-sdr in general! Would you mind cutting a new release, please? In the four years since 0.6.0 quite a number of bug fixes (and a few features, too) have accumulated.
LG tobi