tests: use SPDX identifiers
[urcu.git] / tests / benchmark / test_urcu_hash.c
1 // SPDX-FileCopyrightText: 2009-2012 Mathieu Desnoyers <mathieu.desnoyers@efficios.com>
2 //
3 // SPDX-License-Identifier: GPL-2.0-or-later
4
5 /*
6 * Userspace RCU library - test program
7 */
8
9 #include "test_urcu_hash.h"
10
11 enum test_hash {
12 TEST_HASH_RW,
13 TEST_HASH_UNIQUE,
14 };
15
16 struct test_hash_cb {
17 void (*sigusr1)(int signo);
18 void (*sigusr2)(int signo);
19 void *(*thr_reader)(void *_count);
20 void *(*thr_writer)(void *_count);
21 int (*populate_hash)(void);
22 };
23
24 static
25 struct test_hash_cb test_hash_cb[] = {
26 [TEST_HASH_RW] = {
27 test_hash_rw_sigusr1_handler,
28 test_hash_rw_sigusr2_handler,
29 test_hash_rw_thr_reader,
30 test_hash_rw_thr_writer,
31 test_hash_rw_populate_hash,
32 },
33 [TEST_HASH_UNIQUE] = {
34 test_hash_unique_sigusr1_handler,
35 test_hash_unique_sigusr2_handler,
36 test_hash_unique_thr_reader,
37 test_hash_unique_thr_writer,
38 test_hash_unique_populate_hash,
39 },
40
41 };
42
43 static enum test_hash test_choice = TEST_HASH_RW;
44
45 static
46 void (*get_sigusr1_cb(void))(int)
47 {
48 return test_hash_cb[test_choice].sigusr1;
49 }
50
51 static
52 void (*get_sigusr2_cb(void))(int)
53 {
54 return test_hash_cb[test_choice].sigusr2;
55 }
56
57 static
58 void *(*get_thr_reader_cb(void))(void *)
59 {
60 return test_hash_cb[test_choice].thr_reader;
61 }
62
63 static
64 void *(*get_thr_writer_cb(void))(void *)
65 {
66 return test_hash_cb[test_choice].thr_writer;
67 }
68
69 static
70 int (*get_populate_hash_cb(void))(void)
71 {
72 return test_hash_cb[test_choice].populate_hash;
73 }
74
75 DEFINE_URCU_TLS(unsigned int, rand_lookup);
76 DEFINE_URCU_TLS(unsigned long, nr_add);
77 DEFINE_URCU_TLS(unsigned long, nr_addexist);
78 DEFINE_URCU_TLS(unsigned long, nr_del);
79 DEFINE_URCU_TLS(unsigned long, nr_delnoent);
80 DEFINE_URCU_TLS(unsigned long, lookup_fail);
81 DEFINE_URCU_TLS(unsigned long, lookup_ok);
82
83 struct cds_lfht *test_ht;
84
85 volatile int test_go, test_stop;
86
87 unsigned long wdelay;
88
89 unsigned long duration;
90
91 /* read-side C.S. duration, in loops */
92 unsigned long rduration;
93
94 unsigned long init_hash_size = DEFAULT_HASH_SIZE;
95 unsigned long min_hash_alloc_size = DEFAULT_MIN_ALLOC_SIZE;
96 unsigned long max_hash_buckets_size = (1UL << 20);
97 unsigned long init_populate;
98 int opt_auto_resize;
99 int add_only, add_unique, add_replace;
100 const struct cds_lfht_mm_type *memory_backend;
101
102 unsigned long init_pool_offset, lookup_pool_offset, write_pool_offset;
103 unsigned long init_pool_size = DEFAULT_RAND_POOL,
104 lookup_pool_size = DEFAULT_RAND_POOL,
105 write_pool_size = DEFAULT_RAND_POOL;
106 int validate_lookup;
107 unsigned long nr_hash_chains; /* 0: normal table, other: number of hash chains */
108
109 int count_pipe[2];
110
111 int verbose_mode;
112
113 unsigned int cpu_affinities[NR_CPUS];
114 unsigned int next_aff = 0;
115 int use_affinity = 0;
116
117 pthread_mutex_t affinity_mutex = PTHREAD_MUTEX_INITIALIZER;
118
119 DEFINE_URCU_TLS(unsigned long long, nr_writes);
120 DEFINE_URCU_TLS(unsigned long long, nr_reads);
121
122 unsigned int nr_readers;
123 unsigned int nr_writers;
124
125 static pthread_mutex_t rcu_copy_mutex = PTHREAD_MUTEX_INITIALIZER;
126
127 void set_affinity(void)
128 {
129 #ifdef HAVE_SCHED_SETAFFINITY
130 cpu_set_t mask;
131 int cpu, ret;
132 #endif /* HAVE_SCHED_SETAFFINITY */
133
134 if (!use_affinity)
135 return;
136
137 #ifdef HAVE_SCHED_SETAFFINITY
138 ret = pthread_mutex_lock(&affinity_mutex);
139 if (ret) {
140 perror("Error in pthread mutex lock");
141 exit(-1);
142 }
143 cpu = cpu_affinities[next_aff++];
144 ret = pthread_mutex_unlock(&affinity_mutex);
145 if (ret) {
146 perror("Error in pthread mutex unlock");
147 exit(-1);
148 }
149 CPU_ZERO(&mask);
150 CPU_SET(cpu, &mask);
151 sched_setaffinity(0, sizeof(mask), &mask);
152 #endif /* HAVE_SCHED_SETAFFINITY */
153 }
154
155 void rcu_copy_mutex_lock(void)
156 {
157 int ret;
158 ret = pthread_mutex_lock(&rcu_copy_mutex);
159 if (ret) {
160 perror("Error in pthread mutex lock");
161 exit(-1);
162 }
163 }
164
165 void rcu_copy_mutex_unlock(void)
166 {
167 int ret;
168
169 ret = pthread_mutex_unlock(&rcu_copy_mutex);
170 if (ret) {
171 perror("Error in pthread mutex unlock");
172 exit(-1);
173 }
174 }
175
176 unsigned long test_compare(const void *key1, size_t key1_len,
177 const void *key2, size_t key2_len)
178 {
179 if (caa_unlikely(key1_len != key2_len))
180 return -1;
181 urcu_posix_assert(key1_len == sizeof(unsigned long));
182 if (key1 == key2)
183 return 0;
184 else
185 return 1;
186 }
187
188 static
189 void *thr_count(void *arg __attribute__((unused)))
190 {
191 printf_verbose("thread_begin %s, tid %lu\n",
192 "counter", urcu_get_thread_id());
193
194 rcu_register_thread();
195
196 for (;;) {
197 unsigned long count;
198 long approx_before, approx_after;
199 ssize_t len;
200 char buf[1];
201
202 rcu_thread_offline();
203 len = read(count_pipe[0], buf, 1);
204 rcu_thread_online();
205 if (caa_unlikely(!test_duration_read()))
206 break;
207 if (len != 1)
208 continue;
209 /* Accounting */
210 printf("Counting nodes... ");
211 fflush(stdout);
212 rcu_read_lock();
213 cds_lfht_count_nodes(test_ht, &approx_before, &count,
214 &approx_after);
215 rcu_read_unlock();
216 printf("done.\n");
217 printf("Approximation before node accounting: %ld nodes.\n",
218 approx_before);
219 printf("Accounting of nodes in the hash table: "
220 "%lu nodes.\n",
221 count);
222 printf("Approximation after node accounting: %ld nodes.\n",
223 approx_after);
224 }
225 rcu_unregister_thread();
226 return NULL;
227 }
228
229 void free_node_cb(struct rcu_head *head)
230 {
231 struct lfht_test_node *node =
232 caa_container_of(head, struct lfht_test_node, head);
233 free(node);
234 }
235
236 static
237 void test_delete_all_nodes(struct cds_lfht *ht)
238 {
239 struct cds_lfht_iter iter;
240 struct lfht_test_node *node;
241 unsigned long count = 0;
242
243 cds_lfht_for_each_entry(ht, &iter, node, node) {
244 int ret;
245
246 ret = cds_lfht_del(test_ht, cds_lfht_iter_get_node(&iter));
247 urcu_posix_assert(!ret);
248 call_rcu(&node->head, free_node_cb);
249 count++;
250 }
251 printf("deleted %lu nodes.\n", count);
252 }
253
254 static
255 void show_usage(char **argv)
256 {
257 printf("Usage : %s nr_readers nr_writers duration (s) <OPTIONS>\n",
258 argv[0]);
259 printf("OPTIONS:\n");
260 printf(" [-r] [-w] (yield reader and/or writer)\n");
261 printf(" [-d delay] (writer period (us))\n");
262 printf(" [-c duration] (reader C.S. duration (in loops))\n");
263 printf(" [-v] (verbose output)\n");
264 printf(" [-a cpu#] [-a cpu#]... (affinity)\n");
265 printf(" [-h size] (initial number of buckets)\n");
266 printf(" [-m size] (minimum number of allocated buckets)\n");
267 printf(" [-n size] (maximum number of buckets)\n");
268 printf(" [not -u nor -s] Add entries (supports redundant keys).\n");
269 printf(" [-u] Uniquify add (no redundant keys).\n");
270 printf(" [-s] Replace (swap) entries.\n");
271 printf(" [-i] Add only (no removal).\n");
272 printf(" [-k nr_nodes] Number of nodes to insert initially.\n");
273 printf(" [-A] Automatically resize hash table.\n");
274 printf(" [-B order|chunk|mmap] Specify the memory backend.\n");
275 printf(" [-R offset] Lookup pool offset.\n");
276 printf(" [-S offset] Write pool offset.\n");
277 printf(" [-T offset] Init pool offset.\n");
278 printf(" [-M size] Lookup pool size.\n");
279 printf(" [-N size] Write pool size.\n");
280 printf(" [-O size] Init pool size.\n");
281 printf(" [-V] Validate lookups of init values.\n");
282 printf(" (use with filled init pool, same lookup range,\n");
283 printf(" with different write range)\n");
284 printf(" [-U] Uniqueness test.\n");
285 printf(" [-C] Number of hash chains.\n");
286 printf("\n");
287 }
288
289 int main(int argc, char **argv)
290 {
291 pthread_t *tid_reader, *tid_writer;
292 pthread_t tid_count;
293 void *tret;
294 unsigned long long *count_reader;
295 struct wr_count *count_writer;
296 unsigned long long tot_reads = 0, tot_writes = 0,
297 tot_add = 0, tot_add_exist = 0, tot_remove = 0;
298 unsigned long count;
299 long approx_before, approx_after;
300 int i, a, ret, err, mainret = 0;
301 unsigned int i_thr;
302 struct sigaction act;
303 unsigned int remain;
304 unsigned int nr_readers_created = 0, nr_writers_created = 0;
305 long long nr_leaked;
306
307 if (argc < 4) {
308 show_usage(argv);
309 mainret = 1;
310 goto end;
311 }
312
313 err = sscanf(argv[1], "%u", &nr_readers);
314 if (err != 1) {
315 show_usage(argv);
316 mainret = 1;
317 goto end;
318 }
319
320 err = sscanf(argv[2], "%u", &nr_writers);
321 if (err != 1) {
322 show_usage(argv);
323 mainret = 1;
324 goto end;
325 }
326
327 err = sscanf(argv[3], "%lu", &duration);
328 if (err != 1) {
329 show_usage(argv);
330 mainret = 1;
331 goto end;
332 }
333
334 for (i = 4; i < argc; i++) {
335 if (argv[i][0] != '-')
336 continue;
337 switch (argv[i][1]) {
338 case 'r':
339 rcu_debug_yield_enable(RCU_YIELD_READ);
340 break;
341 case 'w':
342 rcu_debug_yield_enable(RCU_YIELD_WRITE);
343 break;
344 case 'a':
345 if (argc < i + 2) {
346 show_usage(argv);
347 mainret = 1;
348 goto end;
349 }
350 a = atoi(argv[++i]);
351 cpu_affinities[next_aff++] = a;
352 use_affinity = 1;
353 printf_verbose("Adding CPU %d affinity\n", a);
354 break;
355 case 'c':
356 if (argc < i + 2) {
357 show_usage(argv);
358 mainret = 1;
359 goto end;
360 }
361 rduration = atol(argv[++i]);
362 break;
363 case 'd':
364 if (argc < i + 2) {
365 show_usage(argv);
366 mainret = 1;
367 goto end;
368 }
369 wdelay = atol(argv[++i]);
370 break;
371 case 'v':
372 verbose_mode = 1;
373 break;
374 case 'h':
375 if (argc < i + 2) {
376 show_usage(argv);
377 mainret = 1;
378 goto end;
379 }
380 init_hash_size = atol(argv[++i]);
381 break;
382 case 'm':
383 if (argc < i + 2) {
384 show_usage(argv);
385 mainret = 1;
386 goto end;
387 }
388 min_hash_alloc_size = atol(argv[++i]);
389 break;
390 case 'n':
391 if (argc < i + 2) {
392 show_usage(argv);
393 mainret = 1;
394 goto end;
395 }
396 max_hash_buckets_size = atol(argv[++i]);
397 break;
398 case 'u':
399 if (add_replace) {
400 printf("Please specify at most one of -s or -u.\n");
401 exit(-1);
402 }
403 add_unique = 1;
404 break;
405 case 's':
406 if (add_unique) {
407 printf("Please specify at most one of -s or -u.\n");
408 exit(-1);
409 }
410 add_replace = 1;
411 break;
412 case 'i':
413 add_only = 1;
414 break;
415 case 'k':
416 init_populate = atol(argv[++i]);
417 break;
418 case 'A':
419 opt_auto_resize = 1;
420 break;
421 case 'B':
422 if (argc < i + 2) {
423 show_usage(argv);
424 mainret = 1;
425 goto end;
426 }
427 i++;
428 if (!strcmp("order", argv[i]))
429 memory_backend = &cds_lfht_mm_order;
430 else if (!strcmp("chunk", argv[i]))
431 memory_backend = &cds_lfht_mm_chunk;
432 else if (!strcmp("mmap", argv[i]))
433 memory_backend = &cds_lfht_mm_mmap;
434 else {
435 printf("Please specify memory backend with order|chunk|mmap.\n");
436 mainret = 1;
437 goto end;
438 }
439 break;
440 case 'R':
441 lookup_pool_offset = atol(argv[++i]);
442 break;
443 case 'S':
444 write_pool_offset = atol(argv[++i]);
445 break;
446 case 'T':
447 init_pool_offset = atol(argv[++i]);
448 break;
449 case 'M':
450 lookup_pool_size = atol(argv[++i]);
451 break;
452 case 'N':
453 write_pool_size = atol(argv[++i]);
454 break;
455 case 'O':
456 init_pool_size = atol(argv[++i]);
457 break;
458 case 'V':
459 validate_lookup = 1;
460 break;
461 case 'U':
462 test_choice = TEST_HASH_UNIQUE;
463 break;
464 case 'C':
465 nr_hash_chains = atol(argv[++i]);
466 break;
467 }
468 }
469
470 /* Check if hash size is power of 2 */
471 if (init_hash_size && init_hash_size & (init_hash_size - 1)) {
472 printf("Error: Initial number of buckets (%lu) is not a power of 2.\n",
473 init_hash_size);
474 mainret = 1;
475 goto end;
476 }
477
478 if (min_hash_alloc_size && min_hash_alloc_size & (min_hash_alloc_size - 1)) {
479 printf("Error: Minimum number of allocated buckets (%lu) is not a power of 2.\n",
480 min_hash_alloc_size);
481 mainret = 1;
482 goto end;
483 }
484
485 if (max_hash_buckets_size && max_hash_buckets_size & (max_hash_buckets_size - 1)) {
486 printf("Error: Maximum number of buckets (%lu) is not a power of 2.\n",
487 max_hash_buckets_size);
488 mainret = 1;
489 goto end;
490 }
491
492 memset(&act, 0, sizeof(act));
493 ret = sigemptyset(&act.sa_mask);
494 if (ret == -1) {
495 perror("sigemptyset");
496 mainret = 1;
497 goto end;
498 }
499 act.sa_handler = get_sigusr1_cb();
500 act.sa_flags = SA_RESTART;
501 ret = sigaction(SIGUSR1, &act, NULL);
502 if (ret == -1) {
503 perror("sigaction");
504 mainret = 1;
505 goto end;
506 }
507
508 act.sa_handler = get_sigusr2_cb();
509 act.sa_flags = SA_RESTART;
510 ret = sigaction(SIGUSR2, &act, NULL);
511 if (ret == -1) {
512 perror("sigaction");
513 mainret = 1;
514 goto end;
515 }
516
517 printf_verbose("running test for %lu seconds, %u readers, %u writers.\n",
518 duration, nr_readers, nr_writers);
519 printf_verbose("Writer delay : %lu loops.\n", wdelay);
520 printf_verbose("Reader duration : %lu loops.\n", rduration);
521 printf_verbose("Mode:%s%s.\n",
522 add_only ? " add only" : " add/remove",
523 add_unique ? " uniquify" : ( add_replace ? " replace" : " insert"));
524 printf_verbose("Initial number of buckets: %lu buckets.\n", init_hash_size);
525 printf_verbose("Minimum number of allocated buckets: %lu buckets.\n", min_hash_alloc_size);
526 printf_verbose("Maximum number of buckets: %lu buckets.\n", max_hash_buckets_size);
527 printf_verbose("Init pool size offset %lu size %lu.\n",
528 init_pool_offset, init_pool_size);
529 printf_verbose("Lookup pool size offset %lu size %lu.\n",
530 lookup_pool_offset, lookup_pool_size);
531 printf_verbose("Update pool size offset %lu size %lu.\n",
532 write_pool_offset, write_pool_size);
533 printf_verbose("Number of hash chains: %lu.\n",
534 nr_hash_chains);
535 printf_verbose("thread %-6s, tid %lu\n",
536 "main", urcu_get_thread_id());
537
538 tid_reader = calloc(nr_readers, sizeof(*tid_reader));
539 if (!tid_reader) {
540 mainret = 1;
541 goto end;
542 }
543 tid_writer = calloc(nr_writers, sizeof(*tid_writer));
544 if (!tid_writer) {
545 mainret = 1;
546 goto end_free_tid_reader;
547 }
548 count_reader = calloc(nr_readers, sizeof(*count_reader));
549 if (!count_reader) {
550 mainret = 1;
551 goto end_free_tid_writer;
552 }
553 count_writer = calloc(nr_writers, sizeof(*count_writer));
554 if (!count_writer) {
555 mainret = 1;
556 goto end_free_count_reader;
557 }
558
559 err = create_all_cpu_call_rcu_data(0);
560 if (err) {
561 printf("Per-CPU call_rcu() worker threads unavailable. Using default global worker thread.\n");
562 }
563
564 if (memory_backend) {
565 test_ht = _cds_lfht_new(init_hash_size, min_hash_alloc_size,
566 max_hash_buckets_size,
567 (opt_auto_resize ? CDS_LFHT_AUTO_RESIZE : 0) |
568 CDS_LFHT_ACCOUNTING, memory_backend,
569 &rcu_flavor, NULL);
570 } else {
571 test_ht = cds_lfht_new(init_hash_size, min_hash_alloc_size,
572 max_hash_buckets_size,
573 (opt_auto_resize ? CDS_LFHT_AUTO_RESIZE : 0) |
574 CDS_LFHT_ACCOUNTING, NULL);
575 }
576 if (!test_ht) {
577 printf("Error allocating hash table.\n");
578 mainret = 1;
579 goto end_free_call_rcu_data;
580 }
581
582 /*
583 * Hash Population needs to be seen as a RCU reader
584 * thread from the point of view of resize.
585 */
586 rcu_register_thread();
587 ret = (get_populate_hash_cb())();
588 urcu_posix_assert(!ret);
589
590 rcu_thread_offline();
591
592 next_aff = 0;
593
594 ret = pipe(count_pipe);
595 if (ret == -1) {
596 perror("pipe");
597 mainret = 1;
598 goto end_online;
599 }
600
601 /* spawn counter thread */
602 err = pthread_create(&tid_count, NULL, thr_count,
603 NULL);
604 if (err != 0) {
605 errno = err;
606 mainret = 1;
607 perror("pthread_create");
608 goto end_close_pipe;
609 }
610
611 for (i_thr = 0; i_thr < nr_readers; i_thr++) {
612 err = pthread_create(&tid_reader[i_thr],
613 NULL, get_thr_reader_cb(),
614 &count_reader[i_thr]);
615 if (err != 0) {
616 errno = err;
617 mainret = 1;
618 perror("pthread_create");
619 goto end_pthread_join;
620 }
621 nr_readers_created++;
622 }
623 for (i_thr = 0; i_thr < nr_writers; i_thr++) {
624 err = pthread_create(&tid_writer[i_thr],
625 NULL, get_thr_writer_cb(),
626 &count_writer[i_thr]);
627 if (err != 0) {
628 errno = err;
629 mainret = 1;
630 perror("pthread_create");
631 goto end_pthread_join;
632 }
633 nr_writers_created++;
634 }
635
636 cmm_smp_mb();
637
638 test_go = 1;
639
640 remain = duration;
641 do {
642 remain = sleep(remain);
643 } while (remain > 0);
644
645 test_stop = 1;
646
647 end_pthread_join:
648 for (i_thr = 0; i_thr < nr_readers_created; i_thr++) {
649 err = pthread_join(tid_reader[i_thr], &tret);
650 if (err != 0) {
651 errno = err;
652 mainret = 1;
653 perror("pthread_join");
654 }
655 tot_reads += count_reader[i_thr];
656 }
657 for (i_thr = 0; i_thr < nr_writers_created; i_thr++) {
658 err = pthread_join(tid_writer[i_thr], &tret);
659 if (err != 0) {
660 errno = err;
661 mainret = 1;
662 perror("pthread_join");
663 }
664 tot_writes += count_writer[i_thr].update_ops;
665 tot_add += count_writer[i_thr].add;
666 tot_add_exist += count_writer[i_thr].add_exist;
667 tot_remove += count_writer[i_thr].remove;
668 }
669
670 /* teardown counter thread */
671 act.sa_handler = SIG_IGN;
672 act.sa_flags = SA_RESTART;
673 ret = sigaction(SIGUSR2, &act, NULL);
674 if (ret == -1) {
675 mainret = 1;
676 perror("sigaction");
677 }
678 {
679 char msg[1] = { 0x42 };
680 ssize_t sret;
681
682 do {
683 sret = write(count_pipe[1], msg, 1); /* wakeup thread */
684 } while (sret == -1L && errno == EINTR);
685 }
686 err = pthread_join(tid_count, &tret);
687 if (err != 0) {
688 errno = err;
689 mainret = 1;
690 perror("pthread_join");
691 }
692
693 end_close_pipe:
694 for (i = 0; i < 2; i++) {
695 err = close(count_pipe[i]);
696 if (err) {
697 mainret = 1;
698 perror("close pipe");
699 }
700 }
701 fflush(stdout);
702 end_online:
703 rcu_thread_online();
704 rcu_read_lock();
705 printf("Counting nodes... ");
706 cds_lfht_count_nodes(test_ht, &approx_before, &count, &approx_after);
707 printf("done.\n");
708 test_delete_all_nodes(test_ht);
709 rcu_read_unlock();
710 rcu_thread_offline();
711 if (count) {
712 printf("Approximation before node accounting: %ld nodes.\n",
713 approx_before);
714 printf("Nodes deleted from hash table before destroy: "
715 "%lu nodes.\n",
716 count);
717 printf("Approximation after node accounting: %ld nodes.\n",
718 approx_after);
719 }
720
721 ret = cds_lfht_destroy(test_ht, NULL);
722 if (ret) {
723 printf_verbose("final delete aborted\n");
724 mainret = 1;
725 } else {
726 printf_verbose("final delete success\n");
727 }
728 printf_verbose("total number of reads : %llu, writes %llu\n", tot_reads,
729 tot_writes);
730 nr_leaked = (long long) tot_add + init_populate - tot_remove - count;
731 printf("SUMMARY %-25s testdur %4lu nr_readers %3u rdur %6lu "
732 "nr_writers %3u "
733 "wdelay %6lu nr_reads %12llu nr_writes %12llu nr_ops %12llu "
734 "nr_add %12llu nr_add_fail %12llu nr_remove %12llu nr_leaked %12lld\n",
735 argv[0], duration, nr_readers, rduration,
736 nr_writers, wdelay, tot_reads, tot_writes,
737 tot_reads + tot_writes, tot_add, tot_add_exist, tot_remove,
738 nr_leaked);
739 if (nr_leaked != 0) {
740 mainret = 1;
741 printf("WARNING: %lld nodes were leaked!\n", nr_leaked);
742 }
743
744 rcu_unregister_thread();
745 end_free_call_rcu_data:
746 free_all_cpu_call_rcu_data();
747 free(count_writer);
748 end_free_count_reader:
749 free(count_reader);
750 end_free_tid_writer:
751 free(tid_writer);
752 end_free_tid_reader:
753 free(tid_reader);
754 end:
755 if (!mainret)
756 exit(EXIT_SUCCESS);
757 else
758 exit(EXIT_FAILURE);
759 }
This page took 0.050889 seconds and 5 git commands to generate.