#include #include #include #include #include //#define TEST_WAIT_ON_SIGNAL static s32 s_quit = 0; static struct nn_event_loop s_loop; #ifndef TEST_WAIT_ON_SIGNAL static struct nn_timer s_timer; #endif static struct nn_signal s_signal; static struct nn_cond s_cond; static struct nn_mutex s_mutex; #ifndef TEST_WAIT_ON_SIGNAL static bool flip = false; static bool any_fail = false; static bool last_was_timeout = false; #endif static f64 last_tick; static u64 iter = 0; #define DELAY 0.002 #ifndef TEST_WAIT_ON_SIGNAL static void timer_callback(void *userdata, struct nn_timer *timer) { (void)userdata; (void)timer; if (!flip) { al_printf("*******FAIL*******\n"); any_fail = true; } else if (!any_fail && last_was_timeout) { // Calling timer_stop() here could avoid this but it should // realistically not happen at nearly any above 0 delay. al_assert(false && "Unexpected repeat timeout, signal thread deadlocked or couldn't keep up?"); } al_printf("Timeout iter #%llu (%.5f)\n", iter, nn_get_tick() - last_tick); last_was_timeout = true; } #endif static void signal_callback(void *userdata) { (void)userdata; f64 tick = nn_get_tick(); #ifndef TEST_WAIT_ON_SIGNAL if (flip) { if (last_was_timeout) { last_was_timeout = false; } else { al_printf("Stopped iter #%llu (%.5f)\n", iter, tick - last_tick); } // The point of this test is to call timer_stop() right before it was set // to fire. So we only care about the case where signal_callback() runs first. // To do that we have to keep it close though, meaning they will trade back and forth. nn_timer_stop(&s_timer); // Now wait for ~DELAY seconds and if the timer still fires after being // stopped, a fail case will trigger. } else { iter++; nn_timer_again(&s_timer); } flip = !flip; #else iter++; al_printf("Signal(#%llu) (%.5f)\n", iter, tick - last_tick); #endif nn_mutex_lock(&s_mutex); nn_cond_signal(&s_cond); last_tick = tick; if (s_quit) { #ifndef TEST_WAIT_ON_SIGNAL nn_timer_stop(&s_timer); #endif nn_signal_stop(&s_signal); } nn_mutex_unlock(&s_mutex); } static nn_thread_result NNWT_THREADCALL signal_and_wait_thread(void *userdata) { (void)userdata; for (;;) { // Locking before signal_send() is necessary. Otherwise there would be a race // between this send being processed and calling cond_signal() on the loop thread // before we get to cond_wait() in this thread. nn_mutex_lock(&s_mutex); nn_signal_send(&s_signal); if (s_quit) break; nn_cond_wait(&s_cond, &s_mutex); f64 tick = last_tick; nn_mutex_unlock(&s_mutex); #ifndef TEST_WAIT_ON_SIGNAL nn_thread_sleep(MAX((tick + DELAY) - nn_get_tick(), 0.0)); #else (void)tick; #endif } nn_mutex_unlock(&s_mutex); return 0; } static void sigint_handler(s32 signum) { (void)signum; nn_mutex_lock(&s_mutex); if (s_quit) { al_printf("If the console output was frozen (deadlock), test failed.\n"); exit(128 + SIGINT); } s_quit = 1; al_printf("...^C again to force exit.\n"); nn_mutex_unlock(&s_mutex); } s32 main(void) { if (!nn_common_init(NULL)) return EXIT_FAILURE; signal(SIGINT, sigint_handler); nn_event_loop_init(&s_loop); nn_signal_init(&s_signal, &s_loop, signal_callback, NULL); nn_signal_start(&s_signal); #ifndef TEST_WAIT_ON_SIGNAL nn_timer_init(&s_timer, &s_loop, timer_callback, NULL); nn_timer_set_repeat(&s_timer, DELAY); #endif last_tick = nn_get_tick(); nn_mutex_init(&s_mutex); nn_cond_init(&s_cond); struct nn_thread thread; nn_thread_create(&thread, signal_and_wait_thread, NULL); nn_event_loop_run(&s_loop); nn_event_loop_destroy(&s_loop); nn_mutex_destroy(&s_mutex); nn_cond_destroy(&s_cond); #ifndef TEST_WAIT_ON_SIGNAL al_printf(any_fail ? "Test failed.\n" : "Test passed.\n"); #else al_printf("Test passed.\n"); #endif nn_common_close(); return EXIT_SUCCESS; }