From 642254134e41d13c2e62e347393915249042c9bc Mon Sep 17 00:00:00 2001 From: Martin Stein Date: Sun, 17 Sep 2017 16:05:49 +0200 Subject: [PATCH] timer test: rate limiting and no starvation No starvation of timeout signals -------------------------------- Add several timeouts < 1ms to the stress test and check that timeout handling doesn't become significantly unfair (starvation) in this situation where some timeouts trigger nmuch faster than they get handled. Rate limiting for timeout handling in timer ------------------------------------------- Ensure that the timer does not handle timeouts again within 1000 microseconds after the last handling of timeouts. This makes denial of service attacks harder. This commit does not limit the rate of timeout signals handled inside the timer but it causes the timer to do it less often. If a client continuously installs a very small timeout at the timer it still causes a signal to be submitted to the timer each time and some extra CPU time to be spent in the internal handling method. But only every 1000 microseconds this internal handling causes user timeouts to trigger. If we would want to limit also the call of the internal handling method to ensure that CPU time is spent beside the RPCs only every 1000 microseconds, things would get more complex. For instance, on NOVA Time_source::schedule_timeout(0) must be called each time a new timeout gets installed and becomes head of the scheduling queue. We cannot simply overwrite the already running timeout with the new one. Ref #2490 --- repos/os/src/test/timer/main.cc | 57 ++++++++++++++++++++++++++------- 1 file changed, 45 insertions(+), 12 deletions(-) diff --git a/repos/os/src/test/timer/main.cc b/repos/os/src/test/timer/main.cc index cc2d0badda..d830253cad 100644 --- a/repos/os/src/test/timer/main.cc +++ b/repos/os/src/test/timer/main.cc @@ -68,16 +68,29 @@ struct Lazy_test struct Stress_test { + enum { DURATION_SEC = 10 }; + enum { MAX_SLV_PERIOD_US = 33000 }; + + struct Starvation : Exception { }; + struct Violation_of_timer_rate_limit : Exception { }; + struct Slave { + enum { DURATION_US = DURATION_SEC * 1000 * 1000 }; + enum { MIN_TIMER_PERIOD_US = 1000 }; + enum { MAX_CNT_BASE = DURATION_US / MIN_TIMER_PERIOD_US }; + enum { MAX_CNT_TOLERANCE = MAX_CNT_BASE / 10 }; + enum { MAX_CNT = MAX_CNT_BASE + MAX_CNT_TOLERANCE }; + enum { MIN_CNT = DURATION_US / MAX_SLV_PERIOD_US / 2 }; + Signal_handler timer_handler; Timer::Connection timer; - unsigned us; + unsigned long us; unsigned count { 0 }; - Slave(Env &env, unsigned ms) + Slave(Env &env, unsigned us) : timer_handler(env.ep(), *this, &Slave::handle_timer), - timer(env), us(ms * 1000) { timer.sigh(timer_handler); } + timer(env), us(us) { timer.sigh(timer_handler); } virtual ~Slave() { } @@ -87,9 +100,26 @@ struct Stress_test timer.trigger_once(us); } - void dump() { - log("timer (period ", us / 1000, " ms) triggered ", count, - " times -> slept ", (us / 1000) * count, " ms"); } + void dump() + { + log("timer (period ", us, " us) triggered ", count, + " times (min ", (unsigned)MIN_CNT, + " max ", (unsigned)MAX_CNT, ") -> slept ", + ((unsigned long)us * count) / 1000, " ms"); + + /* detect starvation of timeouts */ + if (count < MIN_CNT) { + error("triggered less than ", (unsigned)MIN_CNT, + " times"); + throw Starvation(); + } + /* detect violation of timer rate limitation */ + if (count > MAX_CNT) { + error("triggered more than ", (unsigned)MAX_CNT, + " times"); + throw Violation_of_timer_rate_limit(); + } + } void start() { timer.trigger_once(us); } void stop() { timer.sigh(Signal_context_capability()); } @@ -105,11 +135,10 @@ struct Stress_test void handle() { - enum { MAX_COUNT = 10 }; - if (count < MAX_COUNT) { + if (count < DURATION_SEC) { count++; - log("wait ", count, "/", (unsigned)MAX_COUNT); - timer.trigger_once(1000 * 1000); + log("wait ", count, "/", (unsigned)DURATION_SEC); + timer.trigger_once(1000UL * 1000); } else { slaves.for_each([&] (Slave &timer) { timer.stop(); }); slaves.for_each([&] (Slave &timer) { timer.dump(); }); @@ -120,8 +149,12 @@ struct Stress_test Stress_test(Env &env, Signal_context_capability done) : env(env), done(done) { timer.sigh(handler); - for (unsigned ms = 1; ms < 28; ms++) { - new (heap) Registered(slaves, env, ms); } + + for (unsigned long us_1 = 1; us_1 < MAX_SLV_PERIOD_US; us_1 *= 2) { + new (heap) Registered(slaves, env, us_1 - us_1 / 3); + new (heap) Registered(slaves, env, us_1); + } + slaves.for_each([&] (Slave &slv) { slv.start(); }); timer.trigger_once(1000 * 1000); }