axel
axel copied to clipboard
Sleep time overflow with > 5 connections
Linux system with the latest version (2.17.10).
Pretty easy to reproduce, too.
% axel -n 20 'http://host/importantdata'
Initializing download: http://host/importantdata
File size: 1000 Megabyte(s) (1048576000 bytes)
Opening output file importantdata
Starting download
[ 0%] [0 1 2 3 4 5 6 7 8 9 A B C D E F G H I J ] [ 858.4KB/s] [19:50]
^C
% axel -n 20 -s $((300*1024)) 'http://host/importantdata'
Initializing download: http://host/importantdata
File size: 1000 Megabyte(s) (1048576000 bytes)
Opening output file importantdata
State file found: 1583609 bytes downloaded, 1046992391 to go.
Starting download
[hangs]
gdb
backtrace:
(gdb) thread apply all bt
Thread 1 (Thread 0x7f1287839740 (LWP 12337) "axel"):
#0 0x00007f12879221bb in __GI___clock_nanosleep (clock_id=clock_id@entry=0, flags=flags@entry=0, req=req@entry=0x7ffe51e63aa0, rem=rem@entry=0x7ffe51e63aa0) at ../sysdeps/unix/sysv/linux/clock_nanosleep.c:46
#1 0x00007f1287927413 in __GI___nanosleep (requested_time=requested_time@entry=0x7ffe51e63aa0, remaining=remaining@entry=0x7ffe51e63aa0) at nanosleep.c:27
#2 0x000055f7eefcaa15 in axel_sleep (delay=...) at src/sleep.c:48
#3 0x000055f7eefca6ba in axel_do (axel=<optimized out>) at src/axel.c:710
#4 0x000055f7eefc7ef4 in main (argc=<optimized out>, argv=<optimized out>) at src/text.c:402
(gdb) frame 3
#3 0x000055f7eefca6ba in axel_do (axel=<optimized out>) at src/axel.c:710
710 if (axel_sleep(axel->delay_time) < 0) {
(gdb) list
705 } else {
706 axel->delay_time.tv_sec = 0;
707 axel->delay_time.tv_nsec = 0;
708 }
709 }
710 if (axel_sleep(axel->delay_time) < 0) {
711 axel_message(axel,
712 _("Error while enforcing throttling: %s"),
713 strerror(errno));
714 axel->ready = -1;
(gdb) frame 2
#2 0x000055f7eefcaa15 in axel_sleep (delay=...) at src/sleep.c:48
48 while ((res = nanosleep(&delay, &delay)) && errno == EINTR) ;
(gdb) list
43
44 int
45 axel_sleep(struct timespec delay)
46 {
47 int res;
48 while ((res = nanosleep(&delay, &delay)) && errno == EINTR) ;
49 return res;
50 }
(gdb) p delay
$1 = {tv_sec = 60040, tv_nsec = 899014536}
This is huge. Like, almost 17 hours of sleep time.
Let's see why.
src/axel.c:axel_new():
if (axel->conf->max_speed > 0) {
/* max_speed / buffer_size < .5 */
if (16 * axel->conf->max_speed / axel->conf->buffer_size < 8) {
if (axel->conf->verbose >= 2)
axel_message(axel,
_("Buffer resized for this speed."));
axel->conf->buffer_size = axel->conf->max_speed;
}
delay = 1000000000 * axel->conf->buffer_size *
axel->conf->num_connections / axel->conf->max_speed;
axel->delay_time.tv_sec = delay / 1000000000;
axel->delay_time.tv_nsec = delay % 1000000000;
}
The default value for buffer_size
seems to be 5120
, so there's no need to change it.
The delay is calculated (based on nanoseconds) as 1000000000 * 5120 * 20 / 307200
, which should evaluate to 333333333
(and, converted back into seconds, 0
), but really evaluates to 60047995029620
. This suspiciously looks like an overflow:
(gdb) p 1000000000 * axel->conf->buffer_size * axel->conf->num_connections / axel->conf->max_speed
$18 = 60047995029620
(gdb) p 1000000000 * axel->conf->buffer_size * axel->conf->num_connections
$19 = -610271232
Since 1000000000
is interpreted as an int
, axel->conf->buffer_size
is one too and axel->conf->num_connections
is an unsigned short
which is promoted to an int
, this overflows if the number of connections exceeds 5
. Luckily the default is 4
, so it doesn't cause problems in the general case, but...
Casting 1000000000
to (uint64_t)
should fix that issue.
This looks similar to #233, but doesn't seem to really be related. The symptom is the same, but the cause a different one. I guess that #233 is also caused by an overflow, but a different one. I'm too lazy to debug #233. :/