diff --git a/src/ChangeLog b/src/ChangeLog index 5af1bd4a..8bd9f243 100644 --- a/src/ChangeLog +++ b/src/ChangeLog @@ -1,3 +1,12 @@ +2003-11-08 Hrvoje Niksic + + * progress.c (update_speed_ring): Clear the speed ring when the + download stalls. + + * retr.c (get_contents): Specify 0.95s read timeout, so that the + progress gauge can be updated even when data arrives very slowly + or stalls. + 2003-11-08 Hrvoje Niksic * utils.c (wtimer_allocate): Bless the use of wtimer_read on a diff --git a/src/progress.c b/src/progress.c index 8ae09c32..0f2e84d9 100644 --- a/src/progress.c +++ b/src/progress.c @@ -51,6 +51,7 @@ so, delete this exception statement from your version. */ struct progress_implementation { char *name; + int interactive; void *(*create) PARAMS ((long, long)); void (*update) PARAMS ((void *, long, double)); void (*finish) PARAMS ((void *, double)); @@ -70,8 +71,8 @@ static void bar_finish PARAMS ((void *, double)); static void bar_set_params PARAMS ((const char *)); static struct progress_implementation implementations[] = { - { "dot", dot_create, dot_update, dot_finish, dot_set_params }, - { "bar", bar_create, bar_update, bar_finish, bar_set_params } + { "dot", 0, dot_create, dot_update, dot_finish, dot_set_params }, + { "bar", 1, bar_create, bar_update, bar_finish, bar_set_params } }; static struct progress_implementation *current_impl; static int current_impl_locked; @@ -168,6 +169,17 @@ progress_create (long initial, long total) return current_impl->create (initial, total); } +/* Return non-zero if the progress gauge is "interactive", i.e. if it + can profit from being called regularly even in absence of data. + The progress bar is interactive because it regularly updates the + ETA and current update. */ + +int +progress_interactive_p (void *progress) +{ + return current_impl->interactive; +} + /* Inform the progress gauge of newly received bytes. DLTIME is the time in milliseconds since the beginning of the download. */ @@ -425,6 +437,11 @@ static volatile sig_atomic_t received_sigwinch; past. */ #define DLSPEED_SAMPLE_MIN 150 +/* The time after which the download starts to be considered + "stalled", i.e. the current bandwidth is not printed and the recent + download speeds are scratched. */ +#define STALL_START_TIME 5000 + struct bar_progress { long initial_length; /* how many bytes have been downloaded previously. */ @@ -466,8 +483,12 @@ struct bar_progress { position. */ long recent_bytes; /* bytes downloaded so far. */ + int stalled; /* set when no data arrives for longer + than STALL_START_TIME, then reset + when new data arrives. */ + /* create_image() uses these to make sure that ETA information - doesn't flash. */ + doesn't flicker. */ double last_eta_time; /* time of the last update to download speed and ETA, measured since the beginning of download. */ @@ -615,6 +636,38 @@ update_speed_ring (struct bar_progress *bp, long howmuch, double dltime) if (recent_age < DLSPEED_SAMPLE_MIN) return; + if (howmuch == 0) + { + /* If we're not downloading anything, we might be stalling, + i.e. not downloading anything for an extended period of time. + Since 0-reads do not enter the history ring, recent_age + effectively measures the time since last read. */ + if (recent_age >= STALL_START_TIME) + { + /* If we're stalling, reset the ring contents because it's + stale and because it will make bar_update stop printing + the (bogus) current bandwidth. */ + bp->stalled = 1; + xzero (*hist); + bp->recent_bytes = 0; + } + return; + } + + /* We now have a non-zero amount of to store to the speed ring. */ + + /* If the stall status was acquired, reset it. */ + if (bp->stalled) + { + bp->stalled = 0; + /* "recent_age" includes the the entired stalled period, which + could be very long. Don't update the speed ring with that + value because the current bandwidth would start too small. + Start with an arbitrary (but more reasonable) time value and + let it level out. */ + recent_age = 1000; + } + /* Store "recent" bytes and download time to history ring at the position POS. */ @@ -637,7 +690,7 @@ update_speed_ring (struct bar_progress *bp, long howmuch, double dltime) if (++hist->pos == DLSPEED_HISTORY_SIZE) hist->pos = 0; -#if 0 +#if 1 /* Sledgehammer check to verify that the totals are accurate. */ { int i; diff --git a/src/progress.h b/src/progress.h index dad06f59..5437a1d5 100644 --- a/src/progress.h +++ b/src/progress.h @@ -35,6 +35,7 @@ void set_progress_implementation PARAMS ((const char *)); void progress_schedule_redirect PARAMS ((void)); void *progress_create PARAMS ((long, long)); +int progress_interactive_p PARAMS ((void *)); void progress_update PARAMS ((void *, long, double)); void progress_finish PARAMS ((void *, double)); diff --git a/src/retr.c b/src/retr.c index 5e4212f8..4d03fd4e 100644 --- a/src/retr.c +++ b/src/retr.c @@ -161,13 +161,25 @@ get_contents (int fd, FILE *fp, long *len, long restval, long expected, static char dlbuf[16384]; int dlbufsize = sizeof (dlbuf); - void *progress = NULL; struct wget_timer *timer = wtimer_allocate (); + double last_successful_read_tm; + + /* The progress gauge, set according to the user preferences. */ + void *progress = NULL; + + /* Non-zero if the progress gauge is interactive, i.e. if it can + continually update the display. When true, smaller timeout + values are used so that the gauge can update the display when + data arrives slowly. */ + int progress_interactive = 0; *len = restval; if (opt.verbose) - progress = progress_create (restval, expected); + { + progress = progress_create (restval, expected); + progress_interactive = progress_interactive_p (progress); + } if (rbuf && RBUF_FD (rbuf) == fd) { @@ -192,6 +204,7 @@ get_contents (int fd, FILE *fp, long *len, long restval, long expected, if (opt.limit_rate) limit_bandwidth_reset (); wtimer_reset (timer); + last_successful_read_tm = 0; /* Use a smaller buffer for low requested bandwidths. For example, with --limit-rate=2k, it doesn't make sense to slurp in 16K of @@ -209,24 +222,53 @@ get_contents (int fd, FILE *fp, long *len, long restval, long expected, { int amount_to_read = (use_expected ? MIN (expected - *len, dlbufsize) : dlbufsize); - res = xread (fd, dlbuf, amount_to_read, -1); - - if (res <= 0) - break; - - fwrite (dlbuf, 1, res, fp); - /* Always flush the contents of the network packet. This should - not hinder performance: fast downloads will be received in - 16K chunks (which stdio would write out anyway), and slow - downloads won't be limited by disk performance. */ - fflush (fp); - if (ferror (fp)) + double tmout = opt.read_timeout; + if (progress_interactive) { - res = -2; - goto out; + double waittm; + /* For interactive progress gauges, always specify a ~1s + timeout, so that the gauge can be updated regularly even + when the data arrives very slowly or stalls. */ + tmout = 0.95; + waittm = (wtimer_read (timer) - last_successful_read_tm) / 1000; + if (waittm + tmout > opt.read_timeout) + { + /* Don't allow waiting for data to exceed read timeout. */ + tmout = opt.read_timeout - waittm; + if (tmout < 0) + { + /* We've already exceeded the timeout. */ + res = -1; + errno = ETIMEDOUT; + break; + } + } } + res = xread (fd, dlbuf, amount_to_read, tmout); + + if (res == 0 || (res < 0 && errno != ETIMEDOUT)) + break; + else if (res < 0) + res = 0; /* timeout */ wtimer_update (timer); + if (res > 0) + { + fwrite (dlbuf, 1, res, fp); + /* Always flush the contents of the network packet. This + should not hinder performance: fast downloads will be + received in 16K chunks (which stdio would write out + anyway), and slow downloads won't be limited by disk + performance. */ + fflush (fp); + if (ferror (fp)) + { + res = -2; + goto out; + } + last_successful_read_tm = wtimer_read (timer); + } + if (opt.limit_rate) limit_bandwidth (res, timer);