Correct issues #34, #86, #87 - progress output stopping when writing output data blocks - by adding a SIGALRM handler, since ignoring the signal prevents it from interrupting writes; and use setitimer() where available, instead of alarm(), to provide sufficient granularity to handle the case where the --interval is not a whole number of seconds.

This commit is contained in:
Andrew Wood
2024-04-21 13:54:01 +01:00
parent 3a41665302
commit b375041790
7 changed files with 84 additions and 9 deletions
+3
View File
@@ -100,6 +100,9 @@
/* Define to 1 if you have the `select' function. */
#undef HAVE_SELECT
/* Define to 1 if you have the `setitimer' function. */
#undef HAVE_SETITIMER
/* Define to 1 if you have the `setlocale' function. */
#undef HAVE_SETLOCALE
+1
View File
@@ -157,6 +157,7 @@ struct pvstate_s {
#ifdef SA_SIGINFO
struct sigaction old_sigusr2;
#endif
struct sigaction old_sigalrm;
} signal;
/*******************
+26
View File
@@ -244,6 +244,22 @@ bool pv_sigusr2_received(pvstate_t state, pid_t * pid)
#endif
/*
* Handle alarm signals by doing nothing.
*
* Note that we have to use a signal handler like this, instead of using
* SIG_IGN, because if we ignore the signal entirely, it does nothing,
* including not interrupting blocking write() calls - which is what we're
* using alarm signals for in the first place.
*/
static void pv_sig_alrm( /*@unused@ */ __attribute__((unused))
int s)
{
debug("%s", "SIGALRM received");
/* Do nothing. */
}
/*
* Initialise signal handling.
*/
@@ -360,6 +376,15 @@ void pv_sig_init(pvstate_t state)
* while backgrounded (see the SIGTTOU handler above).
*/
pv_sig_ensure_tty_tostop();
/*
* Handle SIGALRM by doing nothing, so we can use alarms or interval
* timers to interrupt blocking writes (returning EINTR).
*/
sa.sa_handler = pv_sig_alrm;
(void) sigemptyset(&(sa.sa_mask));
sa.sa_flags = 0;
(void) sigaction(SIGALRM, &sa, &(pv_sig_state->signal.old_sigalrm));
}
@@ -389,6 +414,7 @@ void pv_sig_fini( /*@unused@ */ __attribute__((unused)) pvstate_t state)
#ifdef SA_SIGINFO
(void) sigaction(SIGUSR2, &(pv_sig_state->signal.old_sigusr2), NULL);
#endif
(void) sigaction(SIGALRM, &(pv_sig_state->signal.old_sigalrm), NULL);
need_to_clear_tostop = pv_sig_state->signal.pv_tty_tostop_added;
+52 -8
View File
@@ -231,8 +231,8 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool
if ((EINTR == errno) || (EAGAIN == errno)) {
/*
* Interrupted by a signal - probably our
* alarm() - so just return what we've
* written so far.
* alarm or interval timer - so just return
* what we've written so far.
*/
return total_written;
} else {
@@ -267,8 +267,9 @@ static ssize_t pv__transfer_write_repeated(int fd, void *buf, size_t count, bool
/*
* Running the select() here seems to make PV eat a lot of
* CPU in some cases, so instead we just go round the loop
* again and rely on our alarm() to interrupt us if we run
* out of time - also on our elapsed time check.
* again and rely on our alarm or interval timer to
* interrupt us if we run out of time - also on our elapsed
* time check.
*/
if (count > 0) {
debug("%s %d: %s (%ld %s, %ld %s)", "fd", fd,
@@ -643,17 +644,60 @@ static int pv__transfer_write(pvstate_t state, bool *eof_in, bool *eof_out, long
if (state->control.discard_input) {
nwritten = state->transfer.to_write;
} else if (state->transfer.to_write > 0) {
if (signal(SIGALRM, SIG_IGN) == SIG_ERR) {
pv_error(state, "%s: %s", _("failed to set alarm signal handler"), strerror(errno));
} else {
(void) alarm(1);
/*
* Set an interval timer or an alarm to interrupt the write
* with a signal if the write takes too long, so we can
* continue producing progress information.
*/
#if HAVE_SETITIMER
struct itimerval new_timer;
/*@-unrecog@ */
/* splint doesn't know setitimer or ITIMER_REAL */
memset(&new_timer, 0, sizeof(new_timer));
new_timer.it_value.tv_sec = (time_t) (state->control.interval);
new_timer.it_value.tv_usec = (suseconds_t) (((long) (state->control.interval * 1000000.0)) % 1000000);
/*
* We have to set the interval so that the timer continues
* to repeat while writes are attempted, especially as it's
* possible that the initial timer run will expire
* immediately if the period is less than 1 second.
*/
new_timer.it_interval.tv_sec = new_timer.it_value.tv_sec;
new_timer.it_interval.tv_usec = new_timer.it_value.tv_usec;
debug("%s: [%lds,%ldus]", "setting interval timer", (long) (new_timer.it_value.tv_sec),
(long) (new_timer.it_value.tv_usec));
if (0 != setitimer(ITIMER_REAL, &new_timer, NULL)) {
pv_error(state, "%s: %s", _("failed to set interval timer"), strerror(errno));
}
#else /* ! HAVE_SETITIMER */
(void) alarm(1);
#endif /* HAVE_SETITIMER */
nwritten = pv__transfer_write_repeated(STDOUT_FILENO,
state->transfer.transfer_buffer +
state->transfer.write_position,
(size_t) (state->transfer.to_write),
state->control.sync_after_write);
#if HAVE_SETITIMER
memset(&new_timer, 0, sizeof(new_timer));
new_timer.it_interval.tv_sec = 0;
new_timer.it_interval.tv_usec = 0;
new_timer.it_value.tv_sec = 0;
new_timer.it_value.tv_usec = 0;
if (0 != setitimer(ITIMER_REAL, &new_timer, NULL)) {
pv_error(state, "%s: %s", _("failed to clear interval timer"), strerror(errno));
}
/*@+unrecog@ */
#else /* ! HAVE_SETITIMER */
(void) alarm(0);
#endif /* HAVE_SETITIMER */
}
if (0 == nwritten) {