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