Files
lix/tests/unit/libutil/backoff.cc
T
Maximilian Bosch 5dc847b47b libstore: exponential backoff for downloads
Closes #932

`connect-timeout` gets replaced by an exponential backoff for the
download timeout where the initial value is controlled by the setting
`initial-connect-timeout`.

Per iteration, the upper limit of the timeout is increased set to

    timeout := min(max_connect_timeout, initial_connect_timeout * 2^i)

I decided to move the entire timeout / tracking of attempts into its own
class to not make the filetransfer implementation more complex. Also,
that allows us to write unit-tests for it.

Setting `--download-attempts` to `0` is forbidden now and an exception
will be thrown. For `--offline` we set it to `1`, the behavior is
equivalent to what it was before: whether the max tries were exceeded is
only checked after the first download exception got thrown, i.e. there's
still one attempt being made.

The end-result - with timeouts being caused by a wrongly set proxy -
looks like this:

    $ env HTTPS_PROXY=1.1.1.1 nix store ping --store https://example.com
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 5006 milliseconds (curl error code=28); retrying in 422ms ms (attempt 1/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 10010 milliseconds (curl error code=28); retrying in 1003ms ms (attempt 2/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 20020 milliseconds (curl error code=28); retrying in 2018ms ms (attempt 3/5)
    warning: error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 40007 milliseconds (curl error code=28); retrying in 4087ms ms (attempt 4/5)
    error: unable to download 'https://example.com/nix-cache-info': Connection timed out after 80074 milliseconds (curl error code=28)

Change-Id: I9e8d08d78275bcf60080d663febc9e075243d36b
2025-08-22 16:19:46 +02:00

53 lines
1.4 KiB
C++

#include <gtest/gtest.h>
#include <lix/libutil/backoff.hh>
namespace nix {
TEST(Backoff, defaults)
{
auto initial = 5;
auto backoff = backoffTimeouts(
5, std::chrono::seconds(300), std::chrono::seconds(initial), std::chrono::milliseconds(1000)
);
BackoffTiming timings = *backoff.next();
ASSERT_EQ(10000, timings.downloadTimeout.count());
ASSERT_LE(1500, timings.waitTime.count());
ASSERT_GE(2500, timings.waitTime.count());
timings = *backoff.next();
ASSERT_EQ(20000, timings.downloadTimeout.count());
ASSERT_LE(3500, timings.waitTime.count());
ASSERT_GE(4500, timings.waitTime.count());
ASSERT_TRUE(backoff.next().has_value());
timings = *backoff.next();
ASSERT_EQ(80000, timings.downloadTimeout.count());
ASSERT_LE(15500, timings.waitTime.count());
ASSERT_GE(16500, timings.waitTime.count());
ASSERT_FALSE(backoff.next().has_value());
}
TEST(Backoff, capped)
{
auto initial = 10;
auto upper = 300;
auto backoff = backoffTimeouts(
7,
std::chrono::seconds(upper),
std::chrono::seconds(initial),
std::chrono::milliseconds(1000)
);
BackoffTiming timings = *backoff.next();
*backoff.next();
*backoff.next();
*backoff.next();
*backoff.next();
timings = *backoff.next();
ASSERT_EQ(300000, timings.downloadTimeout.count());
ASSERT_FALSE(backoff.next().has_value());
}
}