1 /*- 2 * BSD LICENSE 3 * 4 * Copyright (c) Intel Corporation. 5 * All rights reserved. 6 * 7 * Redistribution and use in source and binary forms, with or without 8 * modification, are permitted provided that the following conditions 9 * are met: 10 * 11 * * Redistributions of source code must retain the above copyright 12 * notice, this list of conditions and the following disclaimer. 13 * * Redistributions in binary form must reproduce the above copyright 14 * notice, this list of conditions and the following disclaimer in 15 * the documentation and/or other materials provided with the 16 * distribution. 17 * * Neither the name of Intel Corporation nor the names of its 18 * contributors may be used to endorse or promote products derived 19 * from this software without specific prior written permission. 20 * 21 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 22 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 23 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 24 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 25 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 26 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 27 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 28 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 29 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 30 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 31 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 32 */ 33 34 #include "spdk/stdinc.h" 35 36 #include "spdk/blobfs.h" 37 #include "spdk/env.h" 38 #include "spdk/log.h" 39 #include "spdk/barrier.h" 40 #include "thread/thread_internal.h" 41 42 #include "spdk_cunit.h" 43 #include "unit/lib/blob/bs_dev_common.c" 44 #include "common/lib/test_env.c" 45 #include "blobfs/blobfs.c" 46 #include "blobfs/tree.c" 47 48 struct spdk_filesystem *g_fs; 49 struct spdk_file *g_file; 50 int g_fserrno; 51 struct spdk_thread *g_dispatch_thread = NULL; 52 53 struct ut_request { 54 fs_request_fn fn; 55 void *arg; 56 volatile int done; 57 }; 58 59 static void 60 send_request(fs_request_fn fn, void *arg) 61 { 62 spdk_thread_send_msg(g_dispatch_thread, (spdk_msg_fn)fn, arg); 63 } 64 65 static void 66 ut_call_fn(void *arg) 67 { 68 struct ut_request *req = arg; 69 70 req->fn(req->arg); 71 req->done = 1; 72 } 73 74 static void 75 ut_send_request(fs_request_fn fn, void *arg) 76 { 77 struct ut_request req; 78 79 req.fn = fn; 80 req.arg = arg; 81 req.done = 0; 82 83 spdk_thread_send_msg(g_dispatch_thread, ut_call_fn, &req); 84 85 /* Wait for this to finish */ 86 while (req.done == 0) { } 87 } 88 89 static void 90 fs_op_complete(void *ctx, int fserrno) 91 { 92 g_fserrno = fserrno; 93 } 94 95 static void 96 fs_op_with_handle_complete(void *ctx, struct spdk_filesystem *fs, int fserrno) 97 { 98 g_fs = fs; 99 g_fserrno = fserrno; 100 } 101 102 static void 103 fs_thread_poll(void) 104 { 105 struct spdk_thread *thread; 106 107 thread = spdk_get_thread(); 108 while (spdk_thread_poll(thread, 0, 0) > 0) {} 109 while (spdk_thread_poll(g_cache_pool_thread, 0, 0) > 0) {} 110 } 111 112 static void 113 _fs_init(void *arg) 114 { 115 struct spdk_bs_dev *dev; 116 117 g_fs = NULL; 118 g_fserrno = -1; 119 dev = init_dev(); 120 spdk_fs_init(dev, NULL, send_request, fs_op_with_handle_complete, NULL); 121 122 fs_thread_poll(); 123 124 SPDK_CU_ASSERT_FATAL(g_fs != NULL); 125 SPDK_CU_ASSERT_FATAL(g_fs->bdev == dev); 126 CU_ASSERT(g_fserrno == 0); 127 } 128 129 static void 130 _fs_load(void *arg) 131 { 132 struct spdk_bs_dev *dev; 133 134 g_fs = NULL; 135 g_fserrno = -1; 136 dev = init_dev(); 137 spdk_fs_load(dev, send_request, fs_op_with_handle_complete, NULL); 138 139 fs_thread_poll(); 140 141 SPDK_CU_ASSERT_FATAL(g_fs != NULL); 142 SPDK_CU_ASSERT_FATAL(g_fs->bdev == dev); 143 CU_ASSERT(g_fserrno == 0); 144 } 145 146 static void 147 _fs_unload(void *arg) 148 { 149 g_fserrno = -1; 150 spdk_fs_unload(g_fs, fs_op_complete, NULL); 151 152 fs_thread_poll(); 153 154 CU_ASSERT(g_fserrno == 0); 155 g_fs = NULL; 156 } 157 158 static void 159 _nop(void *arg) 160 { 161 } 162 163 static void 164 cache_read_after_write(void) 165 { 166 uint64_t length; 167 int rc; 168 char w_buf[100], r_buf[100]; 169 struct spdk_fs_thread_ctx *channel; 170 struct spdk_file_stat stat = {0}; 171 172 ut_send_request(_fs_init, NULL); 173 174 channel = spdk_fs_alloc_thread_ctx(g_fs); 175 176 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 177 CU_ASSERT(rc == 0); 178 SPDK_CU_ASSERT_FATAL(g_file != NULL); 179 180 length = (4 * 1024 * 1024); 181 rc = spdk_file_truncate(g_file, channel, length); 182 CU_ASSERT(rc == 0); 183 184 memset(w_buf, 0x5a, sizeof(w_buf)); 185 spdk_file_write(g_file, channel, w_buf, 0, sizeof(w_buf)); 186 187 CU_ASSERT(spdk_file_get_length(g_file) == length); 188 189 rc = spdk_file_truncate(g_file, channel, sizeof(w_buf)); 190 CU_ASSERT(rc == 0); 191 192 spdk_file_close(g_file, channel); 193 194 fs_thread_poll(); 195 196 rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat); 197 CU_ASSERT(rc == 0); 198 CU_ASSERT(sizeof(w_buf) == stat.size); 199 200 rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file); 201 CU_ASSERT(rc == 0); 202 SPDK_CU_ASSERT_FATAL(g_file != NULL); 203 204 memset(r_buf, 0, sizeof(r_buf)); 205 spdk_file_read(g_file, channel, r_buf, 0, sizeof(r_buf)); 206 CU_ASSERT(memcmp(w_buf, r_buf, sizeof(r_buf)) == 0); 207 208 spdk_file_close(g_file, channel); 209 210 fs_thread_poll(); 211 212 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 213 CU_ASSERT(rc == 0); 214 215 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 216 CU_ASSERT(rc == -ENOENT); 217 218 spdk_fs_free_thread_ctx(channel); 219 220 ut_send_request(_fs_unload, NULL); 221 } 222 223 static void 224 file_length(void) 225 { 226 int rc; 227 char *buf; 228 uint64_t buf_length; 229 volatile uint64_t *length_flushed; 230 struct spdk_fs_thread_ctx *channel; 231 struct spdk_file_stat stat = {0}; 232 233 ut_send_request(_fs_init, NULL); 234 235 channel = spdk_fs_alloc_thread_ctx(g_fs); 236 237 g_file = NULL; 238 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 239 CU_ASSERT(rc == 0); 240 SPDK_CU_ASSERT_FATAL(g_file != NULL); 241 242 /* Write one CACHE_BUFFER. Filling at least one cache buffer triggers 243 * a flush to disk. 244 */ 245 buf_length = CACHE_BUFFER_SIZE; 246 buf = calloc(1, buf_length); 247 spdk_file_write(g_file, channel, buf, 0, buf_length); 248 free(buf); 249 250 /* Spin until all of the data has been flushed to the SSD. There's been no 251 * sync operation yet, so the xattr on the file is still 0. 252 * 253 * length_flushed: This variable is modified by a different thread in this unit 254 * test. So we need to dereference it as a volatile to ensure the value is always 255 * re-read. 256 */ 257 length_flushed = &g_file->length_flushed; 258 while (*length_flushed != buf_length) {} 259 260 /* Close the file. This causes an implicit sync which should write the 261 * length_flushed value as the "length" xattr on the file. 262 */ 263 spdk_file_close(g_file, channel); 264 265 fs_thread_poll(); 266 267 rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat); 268 CU_ASSERT(rc == 0); 269 CU_ASSERT(buf_length == stat.size); 270 271 spdk_fs_free_thread_ctx(channel); 272 273 /* Unload and reload the filesystem. The file length will be 274 * read during load from the length xattr. We want to make sure 275 * it matches what was written when the file was originally 276 * written and closed. 277 */ 278 ut_send_request(_fs_unload, NULL); 279 280 ut_send_request(_fs_load, NULL); 281 282 channel = spdk_fs_alloc_thread_ctx(g_fs); 283 284 rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat); 285 CU_ASSERT(rc == 0); 286 CU_ASSERT(buf_length == stat.size); 287 288 g_file = NULL; 289 rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file); 290 CU_ASSERT(rc == 0); 291 SPDK_CU_ASSERT_FATAL(g_file != NULL); 292 293 spdk_file_close(g_file, channel); 294 295 fs_thread_poll(); 296 297 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 298 CU_ASSERT(rc == 0); 299 300 spdk_fs_free_thread_ctx(channel); 301 302 ut_send_request(_fs_unload, NULL); 303 } 304 305 static void 306 append_write_to_extend_blob(void) 307 { 308 uint64_t blob_size, buf_length; 309 char *buf, append_buf[64]; 310 int rc; 311 struct spdk_fs_thread_ctx *channel; 312 313 ut_send_request(_fs_init, NULL); 314 315 channel = spdk_fs_alloc_thread_ctx(g_fs); 316 317 /* create a file and write the file with blob_size - 1 data length */ 318 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 319 CU_ASSERT(rc == 0); 320 SPDK_CU_ASSERT_FATAL(g_file != NULL); 321 322 blob_size = __file_get_blob_size(g_file); 323 324 buf_length = blob_size - 1; 325 buf = calloc(1, buf_length); 326 rc = spdk_file_write(g_file, channel, buf, 0, buf_length); 327 CU_ASSERT(rc == 0); 328 free(buf); 329 330 spdk_file_close(g_file, channel); 331 fs_thread_poll(); 332 spdk_fs_free_thread_ctx(channel); 333 ut_send_request(_fs_unload, NULL); 334 335 /* load existing file and write extra 2 bytes to cross blob boundary */ 336 ut_send_request(_fs_load, NULL); 337 338 channel = spdk_fs_alloc_thread_ctx(g_fs); 339 g_file = NULL; 340 rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &g_file); 341 CU_ASSERT(rc == 0); 342 SPDK_CU_ASSERT_FATAL(g_file != NULL); 343 344 CU_ASSERT(g_file->length == buf_length); 345 CU_ASSERT(g_file->last == NULL); 346 CU_ASSERT(g_file->append_pos == buf_length); 347 348 rc = spdk_file_write(g_file, channel, append_buf, buf_length, 2); 349 CU_ASSERT(rc == 0); 350 CU_ASSERT(2 * blob_size == __file_get_blob_size(g_file)); 351 spdk_file_close(g_file, channel); 352 fs_thread_poll(); 353 CU_ASSERT(g_file->length == buf_length + 2); 354 355 spdk_fs_free_thread_ctx(channel); 356 ut_send_request(_fs_unload, NULL); 357 } 358 359 static void 360 partial_buffer(void) 361 { 362 int rc; 363 char *buf; 364 uint64_t buf_length; 365 struct spdk_fs_thread_ctx *channel; 366 struct spdk_file_stat stat = {0}; 367 368 ut_send_request(_fs_init, NULL); 369 370 channel = spdk_fs_alloc_thread_ctx(g_fs); 371 372 g_file = NULL; 373 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 374 CU_ASSERT(rc == 0); 375 SPDK_CU_ASSERT_FATAL(g_file != NULL); 376 377 /* Write one CACHE_BUFFER plus one byte. Filling at least one cache buffer triggers 378 * a flush to disk. We want to make sure the extra byte is not implicitly flushed. 379 * It should only get flushed once we sync or close the file. 380 */ 381 buf_length = CACHE_BUFFER_SIZE + 1; 382 buf = calloc(1, buf_length); 383 spdk_file_write(g_file, channel, buf, 0, buf_length); 384 free(buf); 385 386 /* Send some nop messages to the dispatch thread. This will ensure any of the 387 * pending write operations are completed. A well-functioning blobfs should only 388 * issue one write for the filled CACHE_BUFFER - a buggy one might try to write 389 * the extra byte. So do a bunch of _nops to make sure all of them (even the buggy 390 * ones) get a chance to run. Note that we can't just send a message to the 391 * dispatch thread to call spdk_thread_poll() because the messages are themselves 392 * run in the context of spdk_thread_poll(). 393 */ 394 ut_send_request(_nop, NULL); 395 ut_send_request(_nop, NULL); 396 ut_send_request(_nop, NULL); 397 ut_send_request(_nop, NULL); 398 ut_send_request(_nop, NULL); 399 ut_send_request(_nop, NULL); 400 401 CU_ASSERT(g_file->length_flushed == CACHE_BUFFER_SIZE); 402 403 /* Close the file. This causes an implicit sync which should write the 404 * length_flushed value as the "length" xattr on the file. 405 */ 406 spdk_file_close(g_file, channel); 407 408 fs_thread_poll(); 409 410 rc = spdk_fs_file_stat(g_fs, channel, "testfile", &stat); 411 CU_ASSERT(rc == 0); 412 CU_ASSERT(buf_length == stat.size); 413 414 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 415 CU_ASSERT(rc == 0); 416 417 spdk_fs_free_thread_ctx(channel); 418 419 ut_send_request(_fs_unload, NULL); 420 } 421 422 static void 423 cache_write_null_buffer(void) 424 { 425 uint64_t length; 426 int rc; 427 struct spdk_fs_thread_ctx *channel; 428 struct spdk_thread *thread; 429 430 ut_send_request(_fs_init, NULL); 431 432 channel = spdk_fs_alloc_thread_ctx(g_fs); 433 434 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 435 CU_ASSERT(rc == 0); 436 SPDK_CU_ASSERT_FATAL(g_file != NULL); 437 438 length = 0; 439 rc = spdk_file_truncate(g_file, channel, length); 440 CU_ASSERT(rc == 0); 441 442 rc = spdk_file_write(g_file, channel, NULL, 0, 0); 443 CU_ASSERT(rc == 0); 444 445 spdk_file_close(g_file, channel); 446 447 fs_thread_poll(); 448 449 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 450 CU_ASSERT(rc == 0); 451 452 spdk_fs_free_thread_ctx(channel); 453 454 thread = spdk_get_thread(); 455 while (spdk_thread_poll(thread, 0, 0) > 0) {} 456 457 ut_send_request(_fs_unload, NULL); 458 } 459 460 static void 461 fs_create_sync(void) 462 { 463 int rc; 464 struct spdk_fs_thread_ctx *channel; 465 466 ut_send_request(_fs_init, NULL); 467 468 channel = spdk_fs_alloc_thread_ctx(g_fs); 469 CU_ASSERT(channel != NULL); 470 471 rc = spdk_fs_create_file(g_fs, channel, "testfile"); 472 CU_ASSERT(rc == 0); 473 474 /* Create should fail, because the file already exists. */ 475 rc = spdk_fs_create_file(g_fs, channel, "testfile"); 476 CU_ASSERT(rc != 0); 477 478 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 479 CU_ASSERT(rc == 0); 480 481 spdk_fs_free_thread_ctx(channel); 482 483 fs_thread_poll(); 484 485 ut_send_request(_fs_unload, NULL); 486 } 487 488 static void 489 fs_rename_sync(void) 490 { 491 int rc; 492 struct spdk_fs_thread_ctx *channel; 493 494 ut_send_request(_fs_init, NULL); 495 496 channel = spdk_fs_alloc_thread_ctx(g_fs); 497 CU_ASSERT(channel != NULL); 498 499 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 500 CU_ASSERT(rc == 0); 501 SPDK_CU_ASSERT_FATAL(g_file != NULL); 502 503 CU_ASSERT(strcmp(spdk_file_get_name(g_file), "testfile") == 0); 504 505 rc = spdk_fs_rename_file(g_fs, channel, "testfile", "newtestfile"); 506 CU_ASSERT(rc == 0); 507 CU_ASSERT(strcmp(spdk_file_get_name(g_file), "newtestfile") == 0); 508 509 spdk_file_close(g_file, channel); 510 511 fs_thread_poll(); 512 513 spdk_fs_free_thread_ctx(channel); 514 515 ut_send_request(_fs_unload, NULL); 516 } 517 518 static void 519 cache_append_no_cache(void) 520 { 521 int rc; 522 char buf[100]; 523 struct spdk_fs_thread_ctx *channel; 524 525 ut_send_request(_fs_init, NULL); 526 527 channel = spdk_fs_alloc_thread_ctx(g_fs); 528 529 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 530 CU_ASSERT(rc == 0); 531 SPDK_CU_ASSERT_FATAL(g_file != NULL); 532 533 spdk_file_write(g_file, channel, buf, 0 * sizeof(buf), sizeof(buf)); 534 CU_ASSERT(spdk_file_get_length(g_file) == 1 * sizeof(buf)); 535 spdk_file_write(g_file, channel, buf, 1 * sizeof(buf), sizeof(buf)); 536 CU_ASSERT(spdk_file_get_length(g_file) == 2 * sizeof(buf)); 537 spdk_file_sync(g_file, channel); 538 539 fs_thread_poll(); 540 541 spdk_file_write(g_file, channel, buf, 2 * sizeof(buf), sizeof(buf)); 542 CU_ASSERT(spdk_file_get_length(g_file) == 3 * sizeof(buf)); 543 spdk_file_write(g_file, channel, buf, 3 * sizeof(buf), sizeof(buf)); 544 CU_ASSERT(spdk_file_get_length(g_file) == 4 * sizeof(buf)); 545 spdk_file_write(g_file, channel, buf, 4 * sizeof(buf), sizeof(buf)); 546 CU_ASSERT(spdk_file_get_length(g_file) == 5 * sizeof(buf)); 547 548 spdk_file_close(g_file, channel); 549 550 fs_thread_poll(); 551 552 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 553 CU_ASSERT(rc == 0); 554 555 spdk_fs_free_thread_ctx(channel); 556 557 ut_send_request(_fs_unload, NULL); 558 } 559 560 static void 561 fs_delete_file_without_close(void) 562 { 563 int rc; 564 struct spdk_fs_thread_ctx *channel; 565 struct spdk_file *file; 566 567 ut_send_request(_fs_init, NULL); 568 channel = spdk_fs_alloc_thread_ctx(g_fs); 569 CU_ASSERT(channel != NULL); 570 571 rc = spdk_fs_open_file(g_fs, channel, "testfile", SPDK_BLOBFS_OPEN_CREATE, &g_file); 572 CU_ASSERT(rc == 0); 573 SPDK_CU_ASSERT_FATAL(g_file != NULL); 574 575 rc = spdk_fs_delete_file(g_fs, channel, "testfile"); 576 CU_ASSERT(rc == 0); 577 CU_ASSERT(g_file->ref_count != 0); 578 CU_ASSERT(g_file->is_deleted == true); 579 580 rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file); 581 CU_ASSERT(rc != 0); 582 583 spdk_file_close(g_file, channel); 584 585 fs_thread_poll(); 586 587 rc = spdk_fs_open_file(g_fs, channel, "testfile", 0, &file); 588 CU_ASSERT(rc != 0); 589 590 spdk_fs_free_thread_ctx(channel); 591 592 ut_send_request(_fs_unload, NULL); 593 594 } 595 596 static bool g_thread_exit = false; 597 598 static void 599 terminate_spdk_thread(void *arg) 600 { 601 g_thread_exit = true; 602 } 603 604 static void * 605 spdk_thread(void *arg) 606 { 607 struct spdk_thread *thread = arg; 608 609 spdk_set_thread(thread); 610 611 while (!g_thread_exit) { 612 spdk_thread_poll(thread, 0, 0); 613 } 614 615 return NULL; 616 } 617 618 int main(int argc, char **argv) 619 { 620 struct spdk_thread *thread; 621 CU_pSuite suite = NULL; 622 pthread_t spdk_tid; 623 unsigned int num_failures; 624 625 CU_set_error_action(CUEA_ABORT); 626 CU_initialize_registry(); 627 628 suite = CU_add_suite("blobfs_sync_ut", NULL, NULL); 629 630 CU_ADD_TEST(suite, cache_read_after_write); 631 CU_ADD_TEST(suite, file_length); 632 CU_ADD_TEST(suite, append_write_to_extend_blob); 633 CU_ADD_TEST(suite, partial_buffer); 634 CU_ADD_TEST(suite, cache_write_null_buffer); 635 CU_ADD_TEST(suite, fs_create_sync); 636 CU_ADD_TEST(suite, fs_rename_sync); 637 CU_ADD_TEST(suite, cache_append_no_cache); 638 CU_ADD_TEST(suite, fs_delete_file_without_close); 639 640 spdk_thread_lib_init(NULL, 0); 641 642 thread = spdk_thread_create("test_thread", NULL); 643 spdk_set_thread(thread); 644 645 g_dispatch_thread = spdk_thread_create("dispatch_thread", NULL); 646 pthread_create(&spdk_tid, NULL, spdk_thread, g_dispatch_thread); 647 648 g_dev_buffer = calloc(1, DEV_BUFFER_SIZE); 649 650 CU_basic_set_mode(CU_BRM_VERBOSE); 651 CU_basic_run_tests(); 652 num_failures = CU_get_number_of_failures(); 653 CU_cleanup_registry(); 654 655 free(g_dev_buffer); 656 657 ut_send_request(terminate_spdk_thread, NULL); 658 pthread_join(spdk_tid, NULL); 659 660 while (spdk_thread_poll(g_dispatch_thread, 0, 0) > 0) {} 661 while (spdk_thread_poll(thread, 0, 0) > 0) {} 662 663 spdk_set_thread(thread); 664 spdk_thread_exit(thread); 665 while (!spdk_thread_is_exited(thread)) { 666 spdk_thread_poll(thread, 0, 0); 667 } 668 spdk_thread_destroy(thread); 669 670 spdk_set_thread(g_dispatch_thread); 671 spdk_thread_exit(g_dispatch_thread); 672 while (!spdk_thread_is_exited(g_dispatch_thread)) { 673 spdk_thread_poll(g_dispatch_thread, 0, 0); 674 } 675 spdk_thread_destroy(g_dispatch_thread); 676 677 spdk_thread_lib_fini(); 678 679 return num_failures; 680 } 681