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