Skip to content

select() busy loop with zero timeout after test completion consumes CPU on high-latency links #2018

Description

@illarionov

Problem

During the post-test result exchange, the client enters a tight polling loop that repeatedly calls select() with a zero timeout.
This occurs after the main test traffic has finished, in the TEST_END state while waiting for the server’s EXCHANGE_RESULTS response.
On high-latency connections, this busy loop wastes CPU cycles without doing useful work. Similar polling may also occur in the DISPLAY_RESULTS state later.

In this code path, timers are no longer updated, so once the last timer expires, select() is called with timeout = 0 and returns immediately. As a result, the loop spins as fast as possible until data arrives from the server.

Expected behavior: select() should block with a non-zero timeout instead of busy polling.

Version

iperf 3.20+ (cJSON 1.7.15)
Linux alexey 6.8.0-106-generic #106-Ubuntu SMP PREEMPT_DYNAMIC Fri Mar  6 07:58:08 UTC 2026 x86_64
Optional features available: CPU affinity setting, IPv6 flow label, TCP congestion algorithm setting, sendfile / zerocopy, socket pacing, authentication, bind to device, support IPv4 don't fragment, POSIX threads, GSO/GRO support

Steps to reproduce

1. Use a server with high network latency. On Linux, this can be emulated by adding artificial delay (e.g. via tc netem) before starting the iperf server. Something like this:

$ sudo tc qdisc add dev lo root handle 1: prio
$ sudo tc qdisc add dev lo parent 1:3 handle 30: netem delay 1s

$ sudo tc filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip dport 5201 0xffff flowid 1:3
$ sudo tc filter add dev lo protocol ip parent 1:0 prio 3 u32 match ip sport 5201 0xffff flowid 1:3

$ ./iperf3 -s -p 5201

2. Run the iperf with low report interval under strace:

strace -f -tt -T -o trace_log.strace -x ./iperf3 -c 127.0.0.1 -p 5201 -t 1 -i 0.2

3. Review the generated trace (trace_log.strace):

1416665 19:48:51.186114 getsockopt(5, SOL_TCP, TCP_INFO, "\x01\x00\x00\x00\x00\x07\x77\x01\x68\x91\x5b\x00\x00\x00\x00\x00\x00\x80\x00\x00\x18\x02\x00\x00\x02\x00\x00\x00\x00\x00\x00\x00"..., [248]) = 0 <0.000005>
1416665 19:48:51.186131 write(4, "\x04", 1) = 1 <0.000009>
1416665 19:48:51.186149 pselect6(5, [4], [], NULL, {tv_sec=0, tv_nsec=199267000}, NULL) = 0 (Timeout) <0.199481>
1416665 19:48:51.385662 pselect6(5, [4], [], NULL, {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) <0.000005>
1416665 19:48:51.385701 pselect6(5, [4], [], NULL, {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) <0.000004>
1416665 19:48:51.385717 pselect6(5, [4], [], NULL, {tv_sec=0, tv_nsec=0}, NULL) = 0 (Timeout) <0.000004>

Notice that after sending the \x04 state to the server, the client enters a tight select() loop with zero timeout, spinning repeatedly until the server responds.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions