timeout: fix bug in duration + duration testing

The += operator contained bugs. We now also do some tests on the Duration
type at the beginning of the timeout test.

Fixes #2581
This commit is contained in:
Martin Stein 2017-11-16 17:26:15 +01:00 committed by Christian Helmuth
parent 5d39acd3c3
commit 26bcd439f7
4 changed files with 177 additions and 7 deletions

View File

@ -360,6 +360,7 @@ _ZNK6Genode6Thread10stack_baseEv T
_ZNK6Genode6Thread4nameEv T
_ZNK6Genode6Thread9stack_topEv T
_ZNK6Genode8Duration17trunc_to_plain_usEv T
_ZNK6Genode8DurationltERS0_ T
_ZNKSt13bad_exception4whatEv T
_ZNKSt9exception4whatEv T
_ZNSt13bad_exceptionD0Ev T

View File

@ -66,6 +66,7 @@ struct Genode::Duration
unsigned long _microseconds { 0 };
unsigned long _hours { 0 };
void _add_us_less_than_an_hour(unsigned long us);
void _raise_hours(unsigned long hours);
public:

View File

@ -26,13 +26,30 @@ void Duration::_raise_hours(unsigned long hours)
}
void Duration::_add_us_less_than_an_hour(unsigned long us)
{
unsigned long const us_until_next_hr =
(unsigned long)US_PER_HOUR - _microseconds;
if (us >= us_until_next_hr) {
_raise_hours(1);
_microseconds = us - us_until_next_hr;
} else {
_microseconds += us;
}
}
void Duration::operator += (Microseconds us)
{
_microseconds += us.value;
if (_microseconds > US_PER_HOUR) {
_microseconds -= US_PER_HOUR;
_raise_hours(1);
/* filter out hours if any */
if (us.value >= (unsigned long)US_PER_HOUR) {
unsigned long const hours = us.value / US_PER_HOUR;
_raise_hours(hours);
us.value -= hours * US_PER_HOUR;
}
/* add the rest */
_add_us_less_than_an_hour(us.value);
}
@ -45,7 +62,7 @@ void Duration::operator += (Milliseconds ms)
ms.value -= hours * MS_PER_HOUR;
}
/* add the rest as microseconds value */
*this += Microseconds(ms.value * US_PER_MS);
_add_us_less_than_an_hour(ms.value * US_PER_MS);
}

View File

@ -95,6 +95,149 @@ struct Test
};
struct Duration_test : Test
{
static constexpr char const *brief = "Test operations on durations";
Duration_test(Env &env,
unsigned &error_cnt,
Signal_context_capability done,
unsigned id)
:
Test(env, error_cnt, done, id, brief)
{
log("tests with common duration values");
enum : unsigned long { US_PER_HOUR = 1000UL * 1000 * 60 * 60 };
/* create durations for corner cases */
Duration min (Microseconds(0UL));
Duration hour(Microseconds((unsigned long)US_PER_HOUR));
Duration max (Microseconds(~0UL));
{
/* create durations near the corner cases */
Duration min_plus_1 (Microseconds(1UL));
Duration hour_minus_1(Microseconds((unsigned long)US_PER_HOUR - 1));
Duration hour_plus_1 (Microseconds((unsigned long)US_PER_HOUR + 1));
Duration max_minus_1 (Microseconds(~0UL - 1));
/* all must be greater than the minimum */
if (min_plus_1 < min) { error(__func__, ":", __LINE__); error_cnt++; }
if (hour_minus_1 < min) { error(__func__, ":", __LINE__); error_cnt++; }
if (hour < min) { error(__func__, ":", __LINE__); error_cnt++; }
if (hour_plus_1 < min) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < min) { error(__func__, ":", __LINE__); error_cnt++; }
if (max_minus_1 < min) { error(__func__, ":", __LINE__); error_cnt++; }
/* all must be less than the maximum */
if (max < min ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < min_plus_1 ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < hour_minus_1) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < hour ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < hour_plus_1 ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < max_minus_1 ) { error(__func__, ":", __LINE__); error_cnt++; }
/* consistency around one hour */
if (hour < hour_minus_1) { error(__func__, ":", __LINE__); error_cnt++; }
if (hour_plus_1 < hour ) { error(__func__, ":", __LINE__); error_cnt++; }
}
/* consistency when we double the values */
Duration two_hours = hour;
Duration two_max = max;
two_hours += Microseconds((unsigned long)US_PER_HOUR);
two_max += Microseconds(~0UL);
if (two_hours < hour) { error(__func__, ":", __LINE__); error_cnt++; }
if (two_max < max) { error(__func__, ":", __LINE__); error_cnt++; }
/* create durations near corner cases by increasing after construction */
Duration hour_minus_1(Microseconds((unsigned long)US_PER_HOUR - 2));
Duration hour_plus_1 (Microseconds((unsigned long)US_PER_HOUR));
Duration max_minus_1 (Microseconds(~0UL - 2));
Duration max_plus_1 (Microseconds(~0UL));
hour_minus_1 += Microseconds(1);
hour_plus_1 += Microseconds(1);
max_minus_1 += Microseconds(1);
max_plus_1 += Microseconds(1);
/* consistency around corner cases */
if (hour < hour_minus_1) { error(__func__, ":", __LINE__); error_cnt++; }
if (hour_plus_1 < hour ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max < max_minus_1 ) { error(__func__, ":", __LINE__); error_cnt++; }
if (max_plus_1 < max ) { error(__func__, ":", __LINE__); error_cnt++; }
log("tests near maximum duration value (may take a while)");
/*
* This is a fast way to get the maximum possible duration
*/
enum { NR = 12 };
Duration duration[NR] = {
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(1UL)),
Duration(Microseconds(0UL)),
Duration(Microseconds(0UL))
};
for (unsigned volatile step = 0; NR - step > 2; step++) {
/*
* We increase all left durations equally, beginning with a big
* step width, until an overflow occurs. Then, we dismiss the
* duration that had the overflow and decrease the step width for
* the next round. With only 3 durations left, we decrease step
* width to 1 and do one more round. So, finally we have only 2
* durations left.
*/
unsigned volatile duration_id = step;
unsigned nr_left = NR - step;
try {
while (1) {
for (; duration_id < NR; duration_id++) {
if (nr_left > 4) { duration[duration_id] += Milliseconds(~0UL / (step + 1)); }
else if (nr_left == 4) { duration[duration_id] += Milliseconds(1000UL); }
else if (nr_left < 4) { duration[duration_id] += Microseconds(1UL); }
}
duration_id = step;
}
}
catch (Duration::Overflow) { }
log(" step ", step, " done: duration ", duration_id, " dismissed",
", durations ", step, "..", NR - 1, " left");
}
/*
* Now, both duration[NR - 2] and duration[NR - 1] contain one less
* than the maximum possible duration value. So, test consistency at
* this corner case.
*/
duration[NR - 2] += Microseconds(1);
if (duration[NR - 2] < duration[NR - 1]) { error(__func__, ":", __LINE__); error_cnt++; }
if (duration[NR - 1] < duration[NR - 2]) ; else { error(__func__, ":", __LINE__); error_cnt++; }
/* test if we really had the expected durations */
try {
duration[NR - 2] += Microseconds(1);
error(__func__, ":", __LINE__); error_cnt++;
}
catch (Duration::Overflow) { }
try {
duration[NR - 1] += Microseconds(2);
error(__func__, ":", __LINE__); error_cnt++;
}
catch (Duration::Overflow) { }
Test::done.submit();
}
};
struct Mixed_timeouts : Test
{
static constexpr char const *brief = "schedule multiple timeouts simultaneously";
@ -608,10 +751,12 @@ struct Main
{
Env &env;
unsigned error_cnt { 0 };
Constructible<Fast_polling> test_1;
Constructible<Mixed_timeouts> test_2;
Constructible<Duration_test> test_1;
Constructible<Fast_polling> test_2;
Constructible<Mixed_timeouts> test_3;
Signal_handler<Main> test_1_done { env.ep(), *this, &Main::handle_test_1_done };
Signal_handler<Main> test_2_done { env.ep(), *this, &Main::handle_test_2_done };
Signal_handler<Main> test_3_done { env.ep(), *this, &Main::handle_test_3_done };
Main(Env &env) : env(env)
{
@ -627,6 +772,12 @@ struct Main
void handle_test_2_done()
{
test_2.destruct();
test_3.construct(env, error_cnt, test_3_done, 2);
}
void handle_test_3_done()
{
test_3.destruct();
if (error_cnt) {
error("test failed because of ", error_cnt, " error(s)");
env.parent().exit(-1);