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