thread: Cache the closest timed poller into thread

When we introduce RB tree, getting the closest timed poller is not
O(1) but O(log N). To mitigate such delay, cache the closest timed
poller into thread, and update the cache when its content is changed.

Add unit test cases for this change. They will also clarify the current
behavior of spdk_poller_unregister() and spdk_poller_pause() for
timed pollers.

Signed-off-by: Shuhei Matsumoto <shuhei.matsumoto.xt@hitachi.com>
Change-Id: Ibb98a54c261859a3210034038d3953e5c93ef8aa
Reviewed-on: https://review.spdk.io/gerrit/c/spdk/spdk/+/7720
Tested-by: SPDK CI Jenkins <sys_sgci@intel.com>
Reviewed-by: Jim Harris <james.r.harris@intel.com>
Reviewed-by: Konrad Sztyber <konrad.sztyber@intel.com>
Reviewed-by: Aleksey Marchuk <alexeymar@mellanox.com>
Community-CI: Mellanox Build Bot
This commit is contained in:
Shuhei Matsumoto 2021-05-11 15:16:05 +09:00 committed by Tomasz Zawadzki
parent 4f11fa5b6c
commit 4748ebef40
2 changed files with 110 additions and 2 deletions

View File

@ -123,6 +123,7 @@ struct spdk_thread {
* Contains pollers running on this thread with a periodic timer.
*/
TAILQ_HEAD(timed_pollers_head, spdk_poller) timed_pollers;
struct spdk_poller *first_timed_poller;
/*
* Contains paused pollers. Pollers on this queue are waiting until
* they are resumed (in which case they're put onto the active/timer
@ -676,12 +677,18 @@ poller_insert_timer(struct spdk_thread *thread, struct spdk_poller *poller, uint
/* No earlier pollers were found, so this poller must be the new head */
TAILQ_INSERT_HEAD(&thread->timed_pollers, poller, tailq);
thread->first_timed_poller = poller;
}
static inline void
poller_remove_timer(struct spdk_thread *thread, struct spdk_poller *poller)
{
TAILQ_REMOVE(&thread->timed_pollers, poller, tailq);
if (thread->first_timed_poller == poller) {
thread->first_timed_poller = TAILQ_FIRST(&thread->timed_pollers);
}
}
static void
@ -852,7 +859,7 @@ thread_poll(struct spdk_thread *thread, uint32_t max_msgs, uint64_t now)
}
}
poller = TAILQ_FIRST(&thread->timed_pollers);
poller = thread->first_timed_poller;
while (poller != NULL) {
int timer_rc = 0;
@ -927,7 +934,7 @@ spdk_thread_next_poller_expiration(struct spdk_thread *thread)
{
struct spdk_poller *poller;
poller = TAILQ_FIRST(&thread->timed_pollers);
poller = thread->first_timed_poller;
if (poller) {
return poller->next_run_tick;
}

View File

@ -1332,6 +1332,106 @@ device_unregister_and_thread_exit_race(void)
free_threads();
}
static int
dummy_poller(void *arg)
{
return SPDK_POLLER_IDLE;
}
static void
cache_closest_timed_poller(void)
{
struct spdk_thread *thread;
struct spdk_poller *poller1, *poller2, *poller3, *tmp;
allocate_threads(1);
set_thread(0);
thread = spdk_get_thread();
SPDK_CU_ASSERT_FATAL(thread != NULL);
poller1 = spdk_poller_register(dummy_poller, NULL, 1000);
SPDK_CU_ASSERT_FATAL(poller1 != NULL);
poller2 = spdk_poller_register(dummy_poller, NULL, 1500);
SPDK_CU_ASSERT_FATAL(poller2 != NULL);
poller3 = spdk_poller_register(dummy_poller, NULL, 1800);
SPDK_CU_ASSERT_FATAL(poller3 != NULL);
poll_threads();
/* When multiple timed pollers are inserted, the cache should
* have the closest timed poller.
*/
CU_ASSERT(thread->first_timed_poller == poller1);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == poller1);
spdk_delay_us(1000);
poll_threads();
CU_ASSERT(thread->first_timed_poller == poller2);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == poller2);
/* If we unregister a timed poller by spdk_poller_unregister()
* when it is waiting, it is marked as being unregistereed and
* is actually unregistered when it is expired.
*
* Hence if we unregister the closest timed poller when it is waiting,
* the cache is not updated to the next timed poller until it is expired.
*/
tmp = poller2;
spdk_poller_unregister(&poller2);
CU_ASSERT(poller2 == NULL);
spdk_delay_us(499);
poll_threads();
CU_ASSERT(thread->first_timed_poller == tmp);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == tmp);
spdk_delay_us(1);
poll_threads();
CU_ASSERT(thread->first_timed_poller == poller3);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == poller3);
/* If we pause a timed poller by spdk_poller_pause() when it is waiting,
* it is marked as being paused and is actually paused when it is expired.
*
* Hence if we pause the closest timed poller when it is waiting, the cache
* is not updated to the next timed poller until it is expired.
*/
spdk_poller_pause(poller3);
spdk_delay_us(299);
poll_threads();
CU_ASSERT(thread->first_timed_poller == poller3);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == poller3);
spdk_delay_us(1);
poll_threads();
CU_ASSERT(thread->first_timed_poller == poller1);
CU_ASSERT(TAILQ_FIRST(&thread->timed_pollers) == poller1);
/* After unregistering all timed pollers, the cache should
* be NULL.
*/
spdk_poller_unregister(&poller1);
spdk_poller_unregister(&poller3);
spdk_delay_us(200);
poll_threads();
CU_ASSERT(thread->first_timed_poller == NULL);
CU_ASSERT(TAILQ_EMPTY(&thread->timed_pollers));
free_threads();
}
int
main(int argc, char **argv)
{
@ -1357,6 +1457,7 @@ main(int argc, char **argv)
CU_ADD_TEST(suite, thread_update_stats_test);
CU_ADD_TEST(suite, nested_channel);
CU_ADD_TEST(suite, device_unregister_and_thread_exit_race);
CU_ADD_TEST(suite, cache_closest_timed_poller);
CU_basic_set_mode(CU_BRM_VERBOSE);
CU_basic_run_tests();