/* * \brief Test for signalling framework * \author Norman Feske * \date 2008-09-06 */ /* * Copyright (C) 2008-2013 Genode Labs GmbH * * This file is part of the Genode OS framework, which is distributed * under the terms of the GNU General Public License version 2. */ #include #include #include #include #include #include #include using namespace Genode; /** * Transmit signals in a periodic fashion */ class Sender : Thread<4096> { private: Signal_transmitter _transmitter; Timer::Connection _timer; /* timer connection for local use */ unsigned _interval_ms; /* interval between signals in milliseconds */ bool _stop; /* state for destruction protocol */ unsigned _submit_cnt; /* statistics */ bool _idle; /* suppress the submission of signals */ bool _verbose; /* print activities */ /** * Sender thread submits signals every '_interval_ms' milliseconds */ void entry() { while (!_stop) { if (!_idle) { _submit_cnt++; if (_verbose) printf("submit signal %d\n", _submit_cnt); _transmitter.submit(); if (_interval_ms) _timer.msleep(_interval_ms); } else _timer.msleep(100); } } public: /** * Constructor * * \param context signal destination * \param interval_ms interval between signals * \param verbose print status information */ Sender(Signal_context_capability context, unsigned interval_ms, bool verbose = true) : _transmitter(context), _interval_ms(interval_ms), _stop(false), _submit_cnt(0), _idle(0), _verbose(verbose) { /* start thread at 'entry' function */ start(); } /** * Destructor */ ~Sender() { /* tell thread to stop iterating */ _stop = true; /* wait for current 'msleep' call of the thread to finish */ _timer.msleep(0); } /** * Suppress the transmission of further signals */ void idle(bool idle = true) { _idle = idle; } /** * Return total number of submitted notifications */ unsigned submit_cnt() { return _submit_cnt; } }; /** * Signal handler receives signals and takes some time to handle each */ class Handler : Thread<4096> { private: unsigned _dispatch_ms; /* time needed for dispatching a signal */ unsigned _id; /* unique ID of signal handler for debug output */ static unsigned _id_cnt; /* counter for producing unique IDs */ Signal_receiver *_receiver; /* signal endpoint */ Timer::Connection _timer; /* timer connection for local use */ bool _stop; /* state for destruction protocol */ unsigned _receive_cnt; /* number of received notifications */ unsigned _activation_cnt; /* number of invocations of the signal handler */ bool _idle; /* suppress the further handling of signals */ bool _verbose; /* print status information */ /** * Signal handler needs '_dispatch_ms' milliseconds for each signal */ void entry() { while (!_stop) { if (!_idle) { Signal signal = _receiver->wait_for_signal(); if (_verbose) printf("handler %d got %u signal%s with context %p\n", _id, signal.num(), signal.num() == 1 ? "" : "s", signal.context()); _receive_cnt += signal.num(); _activation_cnt++; } if (_dispatch_ms) _timer.msleep(_dispatch_ms); } } public: /** * Constructor * * \param receiver receiver to request signals from * \param dispatch_ms duration of signal-handler activity * \param verbose print status information */ Handler(Signal_receiver *receiver, unsigned dispatch_ms, bool verbose = true) : _dispatch_ms(dispatch_ms), _id(++_id_cnt), _receiver(receiver), _stop(false), _receive_cnt(0), _activation_cnt(0), _idle(0), _verbose(verbose) { start(); } /** * Destructor */ ~Handler() { /* tell thread to stop iterating */ _stop = true; /* wait for current 'msleep' call of the thread to finish */ _timer.msleep(0); } /** * Suppress the handling of further signals */ void idle(bool idle = true) { _idle = idle; } /** * Return total number of received notifications */ unsigned receive_cnt() const { return _receive_cnt; } /** * Return total number of signal-handler activations */ unsigned activation_cnt() const { return _activation_cnt; } }; /** * Counter for generating unique signal-handler IDs */ unsigned Handler::_id_cnt = 0; /** * Counter for enumerating the tests */ static unsigned test_cnt = 0; /** * Timer connection to be used by the main program */ static Timer::Connection timer; /** * Connection to CAP service used for allocating signal-context capabilities */ static Genode::Cap_connection cap; /** * Symbolic error codes */ class Test_failed { }; class Test_failed_with_unequal_sent_and_received_signals : public Test_failed { }; class Test_failed_with_unequal_activation_of_handlers : public Test_failed { }; class Id_signal_context : public Signal_context { private: int _id; public: Id_signal_context(int id) : _id(id) { } int id() const { return _id; } }; /** * Test for reliable notification delivery * * For this test, the produce more notification than that can be handled. * Still, the total number of notifications gets transmitted because of the * batching of notifications in one signal. This test fails if the number of * submitted notifications on the sender side does not match the number of * notifications received at the signal handler. */ static void fast_sender_test() { enum { SPEED = 10 }; enum { TEST_DURATION = 50*SPEED }; enum { HANDLER_INTERVAL = 10*SPEED }; enum { SENDER_INTERVAL = 2*SPEED }; enum { FINISH_IDLE_TIME = 2*HANDLER_INTERVAL }; printf("\n"); printf("TEST %d: one sender, one handler, sender is faster than handler\n", ++test_cnt); printf("\n"); Signal_receiver receiver; Id_signal_context context_123(123); Handler *handler = new (env()->heap()) Handler(&receiver, HANDLER_INTERVAL, false); Sender *sender = new (env()->heap()) Sender(receiver.manage(&context_123), SENDER_INTERVAL, false); timer.msleep(TEST_DURATION); /* stop emitting signals */ printf("deactivate sender\n"); sender->idle(); timer.msleep(FINISH_IDLE_TIME); printf("\n"); printf("sender submitted a total of %d signals\n", sender->submit_cnt()); printf("handler received a total of %d signals\n", handler->receive_cnt()); printf("\n"); if (sender->submit_cnt() != handler->receive_cnt()) throw Test_failed(); destroy(env()->heap(), sender); destroy(env()->heap(), handler); printf("TEST %d FINISHED\n", test_cnt); } /** * Fairness test if multiple signal-handler threads are present at one receiver * * We expect that all handler threads get activated in a fair manner. The test * fails if the number of activations per handler differs by more than one. * Furthermore, if operating in non-descrete mode, the total number of sent and * handled notifications is checked. */ static void multiple_handlers_test() { enum { SPEED = 10 }; enum { TEST_DURATION = 50*SPEED }; enum { HANDLER_INTERVAL = 8*SPEED }; enum { SENDER_INTERVAL = 1*SPEED }; enum { FINISH_IDLE_TIME = 2*HANDLER_INTERVAL }; enum { NUM_HANDLERS = 4 }; printf("\n"); printf("TEST %d: one busy sender, %d handlers\n", ++test_cnt, NUM_HANDLERS); printf("\n"); Signal_receiver receiver; Handler *handler[NUM_HANDLERS]; for (int i = 0; i < NUM_HANDLERS; i++) handler[i] = new (env()->heap()) Handler(&receiver, HANDLER_INTERVAL); Id_signal_context context_123(123); Sender *sender = new (env()->heap()) Sender(receiver.manage(&context_123), SENDER_INTERVAL); timer.msleep(TEST_DURATION); /* stop emitting signals */ printf("stop generating new notifications\n"); sender->idle(); timer.msleep(FINISH_IDLE_TIME); /* let handlers settle down */ for (int i = 0; i < NUM_HANDLERS; i++) handler[i]->idle(); timer.msleep(FINISH_IDLE_TIME); /* print signal delivery statistics */ printf("\n"); printf("sender submitted a total of %d signals\n", sender->submit_cnt()); unsigned total_receive_cnt = 0; for (int i = 0; i < NUM_HANDLERS; i++) { printf("handler %d received a total of %d signals\n", i, handler[i]->receive_cnt()); total_receive_cnt += handler[i]->receive_cnt(); } printf("all handlers received a total of %d signals\n", total_receive_cnt); /* check if number of sent notifications match the received ones */ if (sender->submit_cnt() != total_receive_cnt) throw Test_failed_with_unequal_sent_and_received_signals(); /* print activation statistics */ printf("\n"); for (int i = 0; i < NUM_HANDLERS; i++) printf("handler %d was activated %d times\n", i, handler[i]->activation_cnt()); printf("\n"); /* check if handlers had been activated equally (tolerating a difference of one) */ for (int i = 0; i < NUM_HANDLERS; i++) { int diff = handler[0]->activation_cnt() - handler[(i + 1)/NUM_HANDLERS]->activation_cnt(); if (abs(diff) > 1) throw Test_failed_with_unequal_activation_of_handlers(); } /* cleanup */ destroy(env()->heap(), sender); for (int i = 0; i < NUM_HANDLERS; i++) destroy(env()->heap(), handler[i]); printf("TEST %d FINISHED\n", test_cnt); } /** * Stress test to estimate signal throughput * * For this test, we disable status output and any simulated wait times. * We produce and handle notifications as fast as possible via spinning * loops at the sender and handler side. */ static void stress_test() { enum { SPEED = 10 }; enum { DURATION_SECONDS = 5 }; enum { FINISH_IDLE_TIME = 100*SPEED }; printf("\n"); printf("TEST %d: stress test, busy signal transmission and handling\n", ++test_cnt); printf("\n"); Signal_receiver receiver; Id_signal_context context_123(123); Handler *handler = new (env()->heap()) Handler(&receiver, 0, false); Sender *sender = new (env()->heap()) Sender(receiver.manage(&context_123), 0, false); for (int i = 1; i <= DURATION_SECONDS; i++) { printf("%d/%d\n", i, DURATION_SECONDS); timer.msleep(1000); } /* stop emitting signals */ printf("deactivate sender\n"); sender->idle(); while (handler->receive_cnt() < sender->submit_cnt()) { printf("waiting for signals still in flight..."); timer.msleep(FINISH_IDLE_TIME); } printf("\n"); printf("sender submitted a total of %d signals\n", sender->submit_cnt()); printf("handler received a total of %d signals\n", handler->receive_cnt()); printf("\n"); printf("processed %d notifications per second\n", (handler->receive_cnt())/DURATION_SECONDS); printf("handler was activated %d times per second\n", (handler->activation_cnt())/DURATION_SECONDS); printf("\n"); if (sender->submit_cnt() != handler->receive_cnt()) throw Test_failed_with_unequal_sent_and_received_signals(); destroy(env()->heap(), sender); destroy(env()->heap(), handler); printf("TEST %d FINISHED\n", test_cnt); } static void lazy_receivers_test() { printf("\n"); printf("TEST %d: lazy and out-of-order signal reception test\n", ++test_cnt); printf("\n"); Signal_receiver rec_1, rec_2; Signal_context rec_context_1, rec_context_2; Signal_transmitter transmitter_1(rec_1.manage(&rec_context_1)); Signal_transmitter transmitter_2(rec_2.manage(&rec_context_2)); printf("submit and receive signals with multiple receivers in order\n"); transmitter_1.submit(); transmitter_2.submit(); { Signal signal = rec_1.wait_for_signal(); printf("returned from wait_for_signal for receiver 1\n"); signal = rec_2.wait_for_signal(); printf("returned from wait_for_signal for receiver 2\n"); } printf("submit and receive signals with multiple receivers out of order\n"); transmitter_1.submit(); transmitter_2.submit(); { Signal signal = rec_2.wait_for_signal(); printf("returned from wait_for_signal for receiver 2\n"); signal = rec_1.wait_for_signal(); printf("returned from wait_for_signal for receiver 1\n"); } printf("TEST %d FINISHED\n", test_cnt); } /** * Try correct initialization and cleanup of receiver/context */ static void check_context_management() { Id_signal_context *context; Signal_receiver *rec; Signal_context_capability cap; /* setup receiver side */ context = new (env()->heap()) Id_signal_context(321); rec = new (env()->heap()) Signal_receiver; cap = rec->manage(context); /* spawn sender */ Sender *sender = new (env()->heap()) Sender(cap, 500); /* stop sender after timeout */ timer.msleep(1000); printf("suspend sender\n"); sender->idle(); /* collect pending signals and dissolve context from receiver */ { Signal signal = rec->wait_for_signal(); printf("got %d signal(s) from %p\n", signal.num(), signal.context()); } rec->dissolve(context); /* let sender spin for some time */ printf("resume sender\n"); sender->idle(false); timer.msleep(1000); printf("suspend sender\n"); sender->idle(); printf("destroy sender\n"); destroy(env()->heap(), sender); } /** * Test if 'Signal_receiver::dissolve()' blocks as long as the signal context * is still referenced by one or more 'Signal' objects */ static Lock signal_context_destroyer_lock(Lock::LOCKED); static bool signal_context_destroyed = false; class Signal_context_destroyer : public Thread<4096> { private: Signal_receiver *_receiver; Signal_context *_context; public: Signal_context_destroyer(Signal_receiver *receiver, Signal_context *context) : _receiver(receiver), _context(context) { } void entry() { signal_context_destroyer_lock.lock(); _receiver->dissolve(_context); signal_context_destroyed = true; destroy(env()->heap(), _context); } }; static void synchronized_context_destruction_test() { Signal_receiver receiver; Signal_context *context = new (env()->heap()) Signal_context; Signal_transmitter transmitter(receiver.manage(context)); transmitter.submit(); Signal_context_destroyer signal_context_destroyer(&receiver, context); signal_context_destroyer.start(); /* The signal context destroyer thread should not be able to destroy the * signal context during the 'Signal' objects life time. */ { Signal signal = receiver.wait_for_signal(); /* let the signal context destroyer thread try to destroy the signal context */ signal_context_destroyer_lock.unlock(); timer.msleep(1000); Signal signal_copy = signal; Signal signal_copy2 = signal; signal_copy = signal_copy2; if (signal_context_destroyed) { PERR("signal context destroyed too early"); sleep_forever(); } } signal_context_destroyer.join(); } /** * Main program */ int main(int, char **) { printf("--- signalling test ---\n"); fast_sender_test(); multiple_handlers_test(); stress_test(); lazy_receivers_test(); check_context_management(); synchronized_context_destruction_test(); printf("--- signalling test finished ---\n"); return 0; }