diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-21 11:54:28 +0000 |
commit | e6918187568dbd01842d8d1d2c808ce16a894239 (patch) | |
tree | 64f88b554b444a49f656b6c656111a145cbbaa28 /src/spdk/test/unit/lib/thread/thread.c | |
parent | Initial commit. (diff) | |
download | ceph-e6918187568dbd01842d8d1d2c808ce16a894239.tar.xz ceph-e6918187568dbd01842d8d1d2c808ce16a894239.zip |
Adding upstream version 18.2.2.upstream/18.2.2
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/spdk/test/unit/lib/thread/thread.c')
-rw-r--r-- | src/spdk/test/unit/lib/thread/thread.c/.gitignore | 1 | ||||
-rw-r--r-- | src/spdk/test/unit/lib/thread/thread.c/Makefile | 38 | ||||
-rw-r--r-- | src/spdk/test/unit/lib/thread/thread.c/thread_ut.c | 1270 |
3 files changed, 1309 insertions, 0 deletions
diff --git a/src/spdk/test/unit/lib/thread/thread.c/.gitignore b/src/spdk/test/unit/lib/thread/thread.c/.gitignore new file mode 100644 index 000000000..1a165acb8 --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/.gitignore @@ -0,0 +1 @@ +thread_ut diff --git a/src/spdk/test/unit/lib/thread/thread.c/Makefile b/src/spdk/test/unit/lib/thread/thread.c/Makefile new file mode 100644 index 000000000..461cfcd22 --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/Makefile @@ -0,0 +1,38 @@ +# +# BSD LICENSE +# +# Copyright (c) Intel Corporation. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright +# notice, this list of conditions and the following disclaimer in +# the documentation and/or other materials provided with the +# distribution. +# * Neither the name of Intel Corporation nor the names of its +# contributors may be used to endorse or promote products derived +# from this software without specific prior written permission. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT +# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR +# A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT +# OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, +# SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT +# LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, +# DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY +# THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT +# (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +# OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# + +SPDK_ROOT_DIR := $(abspath $(CURDIR)/../../../../..) + +TEST_FILE = thread_ut.c + +include $(SPDK_ROOT_DIR)/mk/spdk.unittest.mk diff --git a/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c new file mode 100644 index 000000000..d577671b8 --- /dev/null +++ b/src/spdk/test/unit/lib/thread/thread.c/thread_ut.c @@ -0,0 +1,1270 @@ +/*- + * BSD LICENSE + * + * Copyright (c) Intel Corporation. + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * * Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * * Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * * Neither the name of Intel Corporation nor the names of its + * contributors may be used to endorse or promote products derived + * from this software without specific prior written permission. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS + * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT + * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR + * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT + * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, + * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY + * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT + * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "spdk/stdinc.h" + +#include "spdk_cunit.h" + +#include "spdk_internal/thread.h" + +#include "thread/thread.c" +#include "common/lib/ut_multithread.c" + +static int g_sched_rc = 0; + +static int +_thread_schedule(struct spdk_thread *thread) +{ + return g_sched_rc; +} + +static bool +_thread_op_supported(enum spdk_thread_op op) +{ + switch (op) { + case SPDK_THREAD_OP_NEW: + return true; + default: + return false; + } +} + +static int +_thread_op(struct spdk_thread *thread, enum spdk_thread_op op) +{ + switch (op) { + case SPDK_THREAD_OP_NEW: + return _thread_schedule(thread); + default: + return -ENOTSUP; + } +} + +static void +thread_alloc(void) +{ + struct spdk_thread *thread; + + /* No schedule callback */ + spdk_thread_lib_init(NULL, 0); + thread = spdk_thread_create(NULL, NULL); + SPDK_CU_ASSERT_FATAL(thread != NULL); + spdk_set_thread(thread); + spdk_thread_exit(thread); + while (!spdk_thread_is_exited(thread)) { + spdk_thread_poll(thread, 0, 0); + } + spdk_thread_destroy(thread); + spdk_thread_lib_fini(); + + /* Schedule callback exists */ + spdk_thread_lib_init(_thread_schedule, 0); + + /* Scheduling succeeds */ + g_sched_rc = 0; + thread = spdk_thread_create(NULL, NULL); + SPDK_CU_ASSERT_FATAL(thread != NULL); + spdk_set_thread(thread); + spdk_thread_exit(thread); + while (!spdk_thread_is_exited(thread)) { + spdk_thread_poll(thread, 0, 0); + } + spdk_thread_destroy(thread); + + /* Scheduling fails */ + g_sched_rc = -1; + thread = spdk_thread_create(NULL, NULL); + SPDK_CU_ASSERT_FATAL(thread == NULL); + + spdk_thread_lib_fini(); + + /* Scheduling callback exists with extended thread library initialization. */ + spdk_thread_lib_init_ext(_thread_op, _thread_op_supported, 0); + + /* Scheduling succeeds */ + g_sched_rc = 0; + thread = spdk_thread_create(NULL, NULL); + SPDK_CU_ASSERT_FATAL(thread != NULL); + spdk_set_thread(thread); + spdk_thread_exit(thread); + while (!spdk_thread_is_exited(thread)) { + spdk_thread_poll(thread, 0, 0); + } + spdk_thread_destroy(thread); + + /* Scheduling fails */ + g_sched_rc = -1; + thread = spdk_thread_create(NULL, NULL); + SPDK_CU_ASSERT_FATAL(thread == NULL); + + spdk_thread_lib_fini(); +} + +static void +send_msg_cb(void *ctx) +{ + bool *done = ctx; + + *done = true; +} + +static void +thread_send_msg(void) +{ + struct spdk_thread *thread0; + bool done = false; + + allocate_threads(2); + set_thread(0); + thread0 = spdk_get_thread(); + + set_thread(1); + /* Simulate thread 1 sending a message to thread 0. */ + spdk_thread_send_msg(thread0, send_msg_cb, &done); + + /* We have not polled thread 0 yet, so done should be false. */ + CU_ASSERT(!done); + + /* + * Poll thread 1. The message was sent to thread 0, so this should be + * a nop and done should still be false. + */ + poll_thread(1); + CU_ASSERT(!done); + + /* + * Poll thread 0. This should execute the message and done should then + * be true. + */ + poll_thread(0); + CU_ASSERT(done); + + free_threads(); +} + +static int +poller_run_done(void *ctx) +{ + bool *poller_run = ctx; + + *poller_run = true; + + return -1; +} + +static void +thread_poller(void) +{ + struct spdk_poller *poller = NULL; + bool poller_run = false; + + allocate_threads(1); + + set_thread(0); + MOCK_SET(spdk_get_ticks, 0); + /* Register a poller with no-wait time and test execution */ + poller = spdk_poller_register(poller_run_done, &poller_run, 0); + CU_ASSERT(poller != NULL); + + poll_threads(); + CU_ASSERT(poller_run == true); + + spdk_poller_unregister(&poller); + CU_ASSERT(poller == NULL); + + /* Register a poller with 1000us wait time and test single execution */ + poller_run = false; + poller = spdk_poller_register(poller_run_done, &poller_run, 1000); + CU_ASSERT(poller != NULL); + + poll_threads(); + CU_ASSERT(poller_run == false); + + spdk_delay_us(1000); + poll_threads(); + CU_ASSERT(poller_run == true); + + poller_run = false; + poll_threads(); + CU_ASSERT(poller_run == false); + + spdk_delay_us(1000); + poll_threads(); + CU_ASSERT(poller_run == true); + + spdk_poller_unregister(&poller); + CU_ASSERT(poller == NULL); + + free_threads(); +} + +struct poller_ctx { + struct spdk_poller *poller; + bool run; +}; + +static int +poller_run_pause(void *ctx) +{ + struct poller_ctx *poller_ctx = ctx; + + poller_ctx->run = true; + spdk_poller_pause(poller_ctx->poller); + + return 0; +} + +static void +poller_msg_pause_cb(void *ctx) +{ + struct spdk_poller *poller = ctx; + + spdk_poller_pause(poller); +} + +static void +poller_msg_resume_cb(void *ctx) +{ + struct spdk_poller *poller = ctx; + + spdk_poller_resume(poller); +} + +static void +poller_pause(void) +{ + struct poller_ctx poller_ctx = {}; + unsigned int delay[] = { 0, 1000 }; + unsigned int i; + + allocate_threads(1); + set_thread(0); + + /* Register a poller that pauses itself */ + poller_ctx.poller = spdk_poller_register(poller_run_pause, &poller_ctx, 0); + CU_ASSERT_PTR_NOT_NULL(poller_ctx.poller); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_poller_unregister(&poller_ctx.poller); + CU_ASSERT_PTR_NULL(poller_ctx.poller); + + /* Verify that resuming an unpaused poller doesn't do anything */ + poller_ctx.poller = spdk_poller_register(poller_run_done, &poller_ctx.run, 0); + CU_ASSERT_PTR_NOT_NULL(poller_ctx.poller); + + spdk_poller_resume(poller_ctx.poller); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + /* Verify that pausing the same poller twice works too */ + spdk_poller_pause(poller_ctx.poller); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_poller_pause(poller_ctx.poller); + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_poller_resume(poller_ctx.poller); + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + /* Verify that a poller is run when it's resumed immediately after pausing */ + poller_ctx.run = false; + spdk_poller_pause(poller_ctx.poller); + spdk_poller_resume(poller_ctx.poller); + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + spdk_poller_unregister(&poller_ctx.poller); + CU_ASSERT_PTR_NULL(poller_ctx.poller); + + /* Poll the thread to make sure the previous poller gets unregistered */ + poll_threads(); + CU_ASSERT_EQUAL(spdk_thread_has_pollers(spdk_get_thread()), false); + + /* Verify that it's possible to unregister a paused poller */ + poller_ctx.poller = spdk_poller_register(poller_run_done, &poller_ctx.run, 0); + CU_ASSERT_PTR_NOT_NULL(poller_ctx.poller); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + spdk_poller_pause(poller_ctx.poller); + + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_poller_unregister(&poller_ctx.poller); + + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + CU_ASSERT_EQUAL(spdk_thread_has_pollers(spdk_get_thread()), false); + + /* Register pollers with 0 and 1000us wait time and pause/resume them */ + for (i = 0; i < SPDK_COUNTOF(delay); ++i) { + poller_ctx.poller = spdk_poller_register(poller_run_done, &poller_ctx.run, delay[i]); + CU_ASSERT_PTR_NOT_NULL(poller_ctx.poller); + + spdk_delay_us(delay[i]); + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + spdk_poller_pause(poller_ctx.poller); + + spdk_delay_us(delay[i]); + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_poller_resume(poller_ctx.poller); + + spdk_delay_us(delay[i]); + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, true); + + /* Verify that the poller can be paused/resumed from spdk_thread_send_msg */ + spdk_thread_send_msg(spdk_get_thread(), poller_msg_pause_cb, poller_ctx.poller); + + spdk_delay_us(delay[i]); + poller_ctx.run = false; + poll_threads(); + CU_ASSERT_EQUAL(poller_ctx.run, false); + + spdk_thread_send_msg(spdk_get_thread(), poller_msg_resume_cb, poller_ctx.poller); + + poll_threads(); + if (delay[i] > 0) { + spdk_delay_us(delay[i]); + poll_threads(); + } + CU_ASSERT_EQUAL(poller_ctx.run, true); + + spdk_poller_unregister(&poller_ctx.poller); + CU_ASSERT_PTR_NULL(poller_ctx.poller); + } + + free_threads(); +} + +static void +for_each_cb(void *ctx) +{ + int *count = ctx; + + (*count)++; +} + +static void +thread_for_each(void) +{ + int count = 0; + int i; + + allocate_threads(3); + set_thread(0); + + spdk_for_each_thread(for_each_cb, &count, for_each_cb); + + /* We have not polled thread 0 yet, so count should be 0 */ + CU_ASSERT(count == 0); + + /* Poll each thread to verify the message is passed to each */ + for (i = 0; i < 3; i++) { + poll_thread(i); + CU_ASSERT(count == (i + 1)); + } + + /* + * After each thread is called, the completion calls it + * one more time. + */ + poll_thread(0); + CU_ASSERT(count == 4); + + free_threads(); +} + +static int +channel_create(void *io_device, void *ctx_buf) +{ + int *ch_count = io_device; + + (*ch_count)++; + return 0; +} + +static void +channel_destroy(void *io_device, void *ctx_buf) +{ + int *ch_count = io_device; + + (*ch_count)--; +} + +static void +channel_msg(struct spdk_io_channel_iter *i) +{ + int *msg_count = spdk_io_channel_iter_get_ctx(i); + + (*msg_count)++; + spdk_for_each_channel_continue(i, 0); +} + +static void +channel_cpl(struct spdk_io_channel_iter *i, int status) +{ + int *msg_count = spdk_io_channel_iter_get_ctx(i); + + (*msg_count)++; +} + +static void +for_each_channel_remove(void) +{ + struct spdk_io_channel *ch0, *ch1, *ch2; + int ch_count = 0; + int msg_count = 0; + + allocate_threads(3); + set_thread(0); + spdk_io_device_register(&ch_count, channel_create, channel_destroy, sizeof(int), NULL); + ch0 = spdk_get_io_channel(&ch_count); + set_thread(1); + ch1 = spdk_get_io_channel(&ch_count); + set_thread(2); + ch2 = spdk_get_io_channel(&ch_count); + CU_ASSERT(ch_count == 3); + + /* + * Test that io_channel handles the case where we start to iterate through + * the channels, and during the iteration, one of the channels is deleted. + * This is done in some different and sometimes non-intuitive orders, because + * some operations are deferred and won't execute until their threads are + * polled. + * + * Case #1: Put the I/O channel before spdk_for_each_channel. + */ + set_thread(0); + spdk_put_io_channel(ch0); + CU_ASSERT(ch_count == 3); + poll_threads(); + CU_ASSERT(ch_count == 2); + spdk_for_each_channel(&ch_count, channel_msg, &msg_count, channel_cpl); + CU_ASSERT(msg_count == 0); + poll_threads(); + CU_ASSERT(msg_count == 3); + + msg_count = 0; + + /* + * Case #2: Put the I/O channel after spdk_for_each_channel, but before + * thread 0 is polled. + */ + ch0 = spdk_get_io_channel(&ch_count); + CU_ASSERT(ch_count == 3); + spdk_for_each_channel(&ch_count, channel_msg, &msg_count, channel_cpl); + spdk_put_io_channel(ch0); + CU_ASSERT(ch_count == 3); + + poll_threads(); + CU_ASSERT(ch_count == 2); + CU_ASSERT(msg_count == 4); + set_thread(1); + spdk_put_io_channel(ch1); + CU_ASSERT(ch_count == 2); + set_thread(2); + spdk_put_io_channel(ch2); + CU_ASSERT(ch_count == 2); + poll_threads(); + CU_ASSERT(ch_count == 0); + + spdk_io_device_unregister(&ch_count, NULL); + poll_threads(); + + free_threads(); +} + +struct unreg_ctx { + bool ch_done; + bool foreach_done; +}; + +static void +unreg_ch_done(struct spdk_io_channel_iter *i) +{ + struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + + ctx->ch_done = true; + + SPDK_CU_ASSERT_FATAL(i->cur_thread != NULL); + spdk_for_each_channel_continue(i, 0); +} + +static void +unreg_foreach_done(struct spdk_io_channel_iter *i, int status) +{ + struct unreg_ctx *ctx = spdk_io_channel_iter_get_ctx(i); + + ctx->foreach_done = true; +} + +static void +for_each_channel_unreg(void) +{ + struct spdk_io_channel *ch0; + struct io_device *dev; + struct unreg_ctx ctx = {}; + int io_target = 0; + + allocate_threads(1); + set_thread(0); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL); + CU_ASSERT(!TAILQ_EMPTY(&g_io_devices)); + dev = TAILQ_FIRST(&g_io_devices); + SPDK_CU_ASSERT_FATAL(dev != NULL); + CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL); + ch0 = spdk_get_io_channel(&io_target); + spdk_for_each_channel(&io_target, unreg_ch_done, &ctx, unreg_foreach_done); + + spdk_io_device_unregister(&io_target, NULL); + /* + * There is an outstanding foreach call on the io_device, so the unregister should not + * have removed the device. + */ + CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices)); + spdk_io_device_register(&io_target, channel_create, channel_destroy, sizeof(int), NULL); + /* + * There is already a device registered at &io_target, so a new io_device should not + * have been added to g_io_devices. + */ + CU_ASSERT(dev == TAILQ_FIRST(&g_io_devices)); + CU_ASSERT(TAILQ_NEXT(dev, tailq) == NULL); + + poll_thread(0); + CU_ASSERT(ctx.ch_done == true); + CU_ASSERT(ctx.foreach_done == true); + /* + * There are no more foreach operations outstanding, so we can unregister the device, + * even though a channel still exists for the device. + */ + spdk_io_device_unregister(&io_target, NULL); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + + set_thread(0); + spdk_put_io_channel(ch0); + + poll_threads(); + + free_threads(); +} + +static void +thread_name(void) +{ + struct spdk_thread *thread; + const char *name; + + spdk_thread_lib_init(NULL, 0); + + /* Create thread with no name, which automatically generates one */ + thread = spdk_thread_create(NULL, NULL); + spdk_set_thread(thread); + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + name = spdk_thread_get_name(thread); + CU_ASSERT(name != NULL); + spdk_thread_exit(thread); + while (!spdk_thread_is_exited(thread)) { + spdk_thread_poll(thread, 0, 0); + } + spdk_thread_destroy(thread); + + /* Create thread named "test_thread" */ + thread = spdk_thread_create("test_thread", NULL); + spdk_set_thread(thread); + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + name = spdk_thread_get_name(thread); + SPDK_CU_ASSERT_FATAL(name != NULL); + CU_ASSERT(strcmp(name, "test_thread") == 0); + spdk_thread_exit(thread); + while (!spdk_thread_is_exited(thread)) { + spdk_thread_poll(thread, 0, 0); + } + spdk_thread_destroy(thread); + + spdk_thread_lib_fini(); +} + +static uint64_t g_device1; +static uint64_t g_device2; +static uint64_t g_device3; + +static uint64_t g_ctx1 = 0x1111; +static uint64_t g_ctx2 = 0x2222; + +static int g_create_cb_calls = 0; +static int g_destroy_cb_calls = 0; + +static int +create_cb_1(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &g_device1); + *(uint64_t *)ctx_buf = g_ctx1; + g_create_cb_calls++; + return 0; +} + +static void +destroy_cb_1(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &g_device1); + CU_ASSERT(*(uint64_t *)ctx_buf == g_ctx1); + g_destroy_cb_calls++; +} + +static int +create_cb_2(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &g_device2); + *(uint64_t *)ctx_buf = g_ctx2; + g_create_cb_calls++; + return 0; +} + +static void +destroy_cb_2(void *io_device, void *ctx_buf) +{ + CU_ASSERT(io_device == &g_device2); + CU_ASSERT(*(uint64_t *)ctx_buf == g_ctx2); + g_destroy_cb_calls++; +} + +static void +channel(void) +{ + struct spdk_io_channel *ch1, *ch2; + void *ctx; + + allocate_threads(1); + set_thread(0); + + spdk_io_device_register(&g_device1, create_cb_1, destroy_cb_1, sizeof(g_ctx1), NULL); + spdk_io_device_register(&g_device2, create_cb_2, destroy_cb_2, sizeof(g_ctx2), NULL); + + g_create_cb_calls = 0; + ch1 = spdk_get_io_channel(&g_device1); + CU_ASSERT(g_create_cb_calls == 1); + SPDK_CU_ASSERT_FATAL(ch1 != NULL); + + g_create_cb_calls = 0; + ch2 = spdk_get_io_channel(&g_device1); + CU_ASSERT(g_create_cb_calls == 0); + CU_ASSERT(ch1 == ch2); + SPDK_CU_ASSERT_FATAL(ch2 != NULL); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch2); + poll_threads(); + CU_ASSERT(g_destroy_cb_calls == 0); + + g_create_cb_calls = 0; + ch2 = spdk_get_io_channel(&g_device2); + CU_ASSERT(g_create_cb_calls == 1); + CU_ASSERT(ch1 != ch2); + SPDK_CU_ASSERT_FATAL(ch2 != NULL); + + ctx = spdk_io_channel_get_ctx(ch2); + CU_ASSERT(*(uint64_t *)ctx == g_ctx2); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch1); + poll_threads(); + CU_ASSERT(g_destroy_cb_calls == 1); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch2); + poll_threads(); + CU_ASSERT(g_destroy_cb_calls == 1); + + ch1 = spdk_get_io_channel(&g_device3); + CU_ASSERT(ch1 == NULL); + + spdk_io_device_unregister(&g_device1, NULL); + poll_threads(); + spdk_io_device_unregister(&g_device2, NULL); + poll_threads(); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + free_threads(); + CU_ASSERT(TAILQ_EMPTY(&g_threads)); +} + +static int +create_cb(void *io_device, void *ctx_buf) +{ + uint64_t *refcnt = (uint64_t *)ctx_buf; + + CU_ASSERT(*refcnt == 0); + *refcnt = 1; + + return 0; +} + +static void +destroy_cb(void *io_device, void *ctx_buf) +{ + uint64_t *refcnt = (uint64_t *)ctx_buf; + + CU_ASSERT(*refcnt == 1); + *refcnt = 0; +} + +/** + * This test is checking that a sequence of get, put, get, put without allowing + * the deferred put operation to complete doesn't result in releasing the memory + * for the channel twice. + */ +static void +channel_destroy_races(void) +{ + uint64_t device; + struct spdk_io_channel *ch; + + allocate_threads(1); + set_thread(0); + + spdk_io_device_register(&device, create_cb, destroy_cb, sizeof(uint64_t), NULL); + + ch = spdk_get_io_channel(&device); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + spdk_put_io_channel(ch); + + ch = spdk_get_io_channel(&device); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + spdk_put_io_channel(ch); + poll_threads(); + + spdk_io_device_unregister(&device, NULL); + poll_threads(); + + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + free_threads(); + CU_ASSERT(TAILQ_EMPTY(&g_threads)); +} + +static void +thread_exit_test(void) +{ + struct spdk_thread *thread; + struct spdk_io_channel *ch; + struct spdk_poller *poller1, *poller2; + void *ctx; + bool done1 = false, done2 = false, poller1_run = false, poller2_run = false; + int rc __attribute__((unused)); + + MOCK_SET(spdk_get_ticks, 10); + MOCK_SET(spdk_get_ticks_hz, 1); + + allocate_threads(4); + + /* Test if all pending messages are reaped for the exiting thread, and the + * thread moves to the exited state. + */ + set_thread(0); + thread = spdk_get_thread(); + + /* Sending message to thread 0 will be accepted. */ + rc = spdk_thread_send_msg(thread, send_msg_cb, &done1); + CU_ASSERT(rc == 0); + CU_ASSERT(!done1); + + /* Move thread 0 to the exiting state. */ + spdk_thread_exit(thread); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + /* Sending message to thread 0 will be still accepted. */ + rc = spdk_thread_send_msg(thread, send_msg_cb, &done2); + CU_ASSERT(rc == 0); + + /* Thread 0 will reap pending messages. */ + poll_thread(0); + CU_ASSERT(done1 == true); + CU_ASSERT(done2 == true); + + /* Thread 0 will move to the exited state. */ + CU_ASSERT(spdk_thread_is_exited(thread) == true); + + /* Test releasing I/O channel is reaped even after the thread moves to + * the exiting state + */ + set_thread(1); + + spdk_io_device_register(&g_device1, create_cb_1, destroy_cb_1, sizeof(g_ctx1), NULL); + + g_create_cb_calls = 0; + ch = spdk_get_io_channel(&g_device1); + CU_ASSERT(g_create_cb_calls == 1); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + ctx = spdk_io_channel_get_ctx(ch); + CU_ASSERT(*(uint64_t *)ctx == g_ctx1); + + g_destroy_cb_calls = 0; + spdk_put_io_channel(ch); + + thread = spdk_get_thread(); + spdk_thread_exit(thread); + + /* Thread 1 will not move to the exited state yet because I/O channel release + * does not complete yet. + */ + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + /* Thread 1 will be able to get the another reference of I/O channel + * even after the thread moves to the exiting state. + */ + g_create_cb_calls = 0; + ch = spdk_get_io_channel(&g_device1); + + CU_ASSERT(g_create_cb_calls == 0); + SPDK_CU_ASSERT_FATAL(ch != NULL); + + ctx = spdk_io_channel_get_ctx(ch); + CU_ASSERT(*(uint64_t *)ctx == g_ctx1); + + spdk_put_io_channel(ch); + + poll_threads(); + CU_ASSERT(g_destroy_cb_calls == 1); + + /* Thread 1 will move to the exited state after I/O channel is released. + * are released. + */ + CU_ASSERT(spdk_thread_is_exited(thread) == true); + + spdk_io_device_unregister(&g_device1, NULL); + poll_threads(); + + /* Test if unregistering poller is reaped for the exiting thread, and the + * thread moves to the exited thread. + */ + set_thread(2); + thread = spdk_get_thread(); + + poller1 = spdk_poller_register(poller_run_done, &poller1_run, 0); + CU_ASSERT(poller1 != NULL); + + spdk_poller_unregister(&poller1); + + spdk_thread_exit(thread); + + poller2 = spdk_poller_register(poller_run_done, &poller2_run, 0); + + poll_threads(); + + CU_ASSERT(poller1_run == false); + CU_ASSERT(poller2_run == true); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + spdk_poller_unregister(&poller2); + + poll_threads(); + + CU_ASSERT(spdk_thread_is_exited(thread) == true); + + /* Test if the exiting thread is exited forcefully after timeout. */ + set_thread(3); + thread = spdk_get_thread(); + + poller1 = spdk_poller_register(poller_run_done, &poller1_run, 0); + CU_ASSERT(poller1 != NULL); + + spdk_thread_exit(thread); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + MOCK_SET(spdk_get_ticks, 11); + + poll_threads(); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + /* Cause timeout forcefully. */ + MOCK_SET(spdk_get_ticks, 15); + + poll_threads(); + + CU_ASSERT(spdk_thread_is_exited(thread) == true); + + spdk_poller_unregister(&poller1); + + poll_threads(); + + MOCK_CLEAR(spdk_get_ticks); + MOCK_CLEAR(spdk_get_ticks_hz); + + free_threads(); +} + +static int +poller_run_idle(void *ctx) +{ + uint64_t delay_us = (uint64_t)ctx; + + spdk_delay_us(delay_us); + + return 0; +} + +static int +poller_run_busy(void *ctx) +{ + uint64_t delay_us = (uint64_t)ctx; + + spdk_delay_us(delay_us); + + return 1; +} + +static void +thread_update_stats_test(void) +{ + struct spdk_poller *poller; + struct spdk_thread *thread; + + MOCK_SET(spdk_get_ticks, 10); + + allocate_threads(1); + + set_thread(0); + thread = spdk_get_thread(); + + CU_ASSERT(thread->tsc_last == 10); + CU_ASSERT(thread->stats.idle_tsc == 0); + CU_ASSERT(thread->stats.busy_tsc == 0); + + /* Test if idle_tsc is updated expectedly. */ + poller = spdk_poller_register(poller_run_idle, (void *)1000, 0); + CU_ASSERT(poller != NULL); + + spdk_delay_us(100); + + poll_thread_times(0, 1); + + CU_ASSERT(thread->tsc_last == 1110); + CU_ASSERT(thread->stats.idle_tsc == 1000); + CU_ASSERT(thread->stats.busy_tsc == 0); + + spdk_delay_us(100); + + poll_thread_times(0, 1); + + CU_ASSERT(thread->tsc_last == 2210); + CU_ASSERT(thread->stats.idle_tsc == 2000); + CU_ASSERT(thread->stats.busy_tsc == 0); + + spdk_poller_unregister(&poller); + + /* Test if busy_tsc is updated expectedly. */ + poller = spdk_poller_register(poller_run_busy, (void *)100000, 0); + CU_ASSERT(poller != NULL); + + spdk_delay_us(10000); + + poll_thread_times(0, 1); + + CU_ASSERT(thread->tsc_last == 112210); + CU_ASSERT(thread->stats.idle_tsc == 2000); + CU_ASSERT(thread->stats.busy_tsc == 100000); + + spdk_delay_us(10000); + + poll_thread_times(0, 1); + + CU_ASSERT(thread->tsc_last == 222210); + CU_ASSERT(thread->stats.idle_tsc == 2000); + CU_ASSERT(thread->stats.busy_tsc == 200000); + + spdk_poller_unregister(&poller); + + MOCK_CLEAR(spdk_get_ticks); + + free_threads(); +} + +struct ut_nested_ch { + struct spdk_io_channel *child; + struct spdk_poller *poller; +}; + +struct ut_nested_dev { + struct ut_nested_dev *child; +}; + +static struct io_device * +ut_get_io_device(void *dev) +{ + struct io_device *tmp; + + TAILQ_FOREACH(tmp, &g_io_devices, tailq) { + if (tmp->io_device == dev) { + return tmp; + } + } + + return NULL; +} + +static int +ut_null_poll(void *ctx) +{ + return -1; +} + +static int +ut_nested_ch_create_cb(void *io_device, void *ctx_buf) +{ + struct ut_nested_ch *_ch = ctx_buf; + struct ut_nested_dev *_dev = io_device; + struct ut_nested_dev *_child; + + _child = _dev->child; + + if (_child != NULL) { + _ch->child = spdk_get_io_channel(_child); + SPDK_CU_ASSERT_FATAL(_ch->child != NULL); + } else { + _ch->child = NULL; + } + + _ch->poller = spdk_poller_register(ut_null_poll, NULL, 0); + SPDK_CU_ASSERT_FATAL(_ch->poller != NULL); + + return 0; +} + +static void +ut_nested_ch_destroy_cb(void *io_device, void *ctx_buf) +{ + struct ut_nested_ch *_ch = ctx_buf; + struct spdk_io_channel *child; + + child = _ch->child; + if (child != NULL) { + spdk_put_io_channel(child); + } + + spdk_poller_unregister(&_ch->poller); +} + +static void +ut_check_nested_ch_create(struct spdk_io_channel *ch, struct io_device *dev) +{ + CU_ASSERT(ch->ref == 1); + CU_ASSERT(ch->dev == dev); + CU_ASSERT(dev->refcnt == 1); +} + +static void +ut_check_nested_ch_destroy_pre(struct spdk_io_channel *ch, struct io_device *dev) +{ + CU_ASSERT(ch->ref == 0); + CU_ASSERT(ch->destroy_ref == 1); + CU_ASSERT(dev->refcnt == 1); +} + +static void +ut_check_nested_ch_destroy_post(struct io_device *dev) +{ + CU_ASSERT(dev->refcnt == 0); +} + +static void +ut_check_nested_poller_register(struct spdk_poller *poller) +{ + SPDK_CU_ASSERT_FATAL(poller != NULL); +} + +static void +nested_channel(void) +{ + struct ut_nested_dev _dev1, _dev2, _dev3; + struct ut_nested_ch *_ch1, *_ch2, *_ch3; + struct io_device *dev1, *dev2, *dev3; + struct spdk_io_channel *ch1, *ch2, *ch3; + struct spdk_poller *poller; + struct spdk_thread *thread; + + allocate_threads(1); + set_thread(0); + + thread = spdk_get_thread(); + SPDK_CU_ASSERT_FATAL(thread != NULL); + + _dev1.child = &_dev2; + _dev2.child = &_dev3; + _dev3.child = NULL; + + spdk_io_device_register(&_dev1, ut_nested_ch_create_cb, ut_nested_ch_destroy_cb, + sizeof(struct ut_nested_ch), "dev1"); + spdk_io_device_register(&_dev2, ut_nested_ch_create_cb, ut_nested_ch_destroy_cb, + sizeof(struct ut_nested_ch), "dev2"); + spdk_io_device_register(&_dev3, ut_nested_ch_create_cb, ut_nested_ch_destroy_cb, + sizeof(struct ut_nested_ch), "dev3"); + + dev1 = ut_get_io_device(&_dev1); + SPDK_CU_ASSERT_FATAL(dev1 != NULL); + dev2 = ut_get_io_device(&_dev2); + SPDK_CU_ASSERT_FATAL(dev2 != NULL); + dev3 = ut_get_io_device(&_dev3); + SPDK_CU_ASSERT_FATAL(dev3 != NULL); + + /* A single call spdk_get_io_channel() to dev1 will also create channels + * to dev2 and dev3 continuously. Pollers will be registered together. + */ + ch1 = spdk_get_io_channel(&_dev1); + SPDK_CU_ASSERT_FATAL(ch1 != NULL); + + _ch1 = spdk_io_channel_get_ctx(ch1); + ch2 = _ch1->child; + SPDK_CU_ASSERT_FATAL(ch2 != NULL); + + _ch2 = spdk_io_channel_get_ctx(ch2); + ch3 = _ch2->child; + SPDK_CU_ASSERT_FATAL(ch3 != NULL); + + _ch3 = spdk_io_channel_get_ctx(ch3); + CU_ASSERT(_ch3->child == NULL); + + ut_check_nested_ch_create(ch1, dev1); + ut_check_nested_ch_create(ch2, dev2); + ut_check_nested_ch_create(ch3, dev3); + + poller = spdk_poller_register(ut_null_poll, NULL, 0); + + ut_check_nested_poller_register(poller); + ut_check_nested_poller_register(_ch1->poller); + ut_check_nested_poller_register(_ch2->poller); + ut_check_nested_poller_register(_ch3->poller); + + spdk_poller_unregister(&poller); + poll_thread_times(0, 1); + + /* A single call spdk_put_io_channel() to dev1 will also destroy channels + * to dev2 and dev3 continuously. Pollers will be unregistered together. + */ + spdk_put_io_channel(ch1); + + /* Start exiting the current thread after unregistering the non-nested + * I/O channel. + */ + spdk_thread_exit(thread); + + ut_check_nested_ch_destroy_pre(ch1, dev1); + poll_thread_times(0, 1); + ut_check_nested_ch_destroy_post(dev1); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + ut_check_nested_ch_destroy_pre(ch2, dev2); + poll_thread_times(0, 1); + ut_check_nested_ch_destroy_post(dev2); + + CU_ASSERT(spdk_thread_is_exited(thread) == false); + + ut_check_nested_ch_destroy_pre(ch3, dev3); + poll_thread_times(0, 1); + ut_check_nested_ch_destroy_post(dev3); + + CU_ASSERT(spdk_thread_is_exited(thread) == true); + + spdk_io_device_unregister(&_dev1, NULL); + spdk_io_device_unregister(&_dev2, NULL); + spdk_io_device_unregister(&_dev3, NULL); + CU_ASSERT(TAILQ_EMPTY(&g_io_devices)); + + free_threads(); + CU_ASSERT(TAILQ_EMPTY(&g_threads)); +} + +int +main(int argc, char **argv) +{ + CU_pSuite suite = NULL; + unsigned int num_failures; + + CU_set_error_action(CUEA_ABORT); + CU_initialize_registry(); + + suite = CU_add_suite("io_channel", NULL, NULL); + + CU_ADD_TEST(suite, thread_alloc); + CU_ADD_TEST(suite, thread_send_msg); + CU_ADD_TEST(suite, thread_poller); + CU_ADD_TEST(suite, poller_pause); + CU_ADD_TEST(suite, thread_for_each); + CU_ADD_TEST(suite, for_each_channel_remove); + CU_ADD_TEST(suite, for_each_channel_unreg); + CU_ADD_TEST(suite, thread_name); + CU_ADD_TEST(suite, channel); + CU_ADD_TEST(suite, channel_destroy_races); + CU_ADD_TEST(suite, thread_exit_test); + CU_ADD_TEST(suite, thread_update_stats_test); + CU_ADD_TEST(suite, nested_channel); + + CU_basic_set_mode(CU_BRM_VERBOSE); + CU_basic_run_tests(); + num_failures = CU_get_number_of_failures(); + CU_cleanup_registry(); + return num_failures; +} |