xref: /openbmc/linux/tools/testing/radix-tree/iteration_check.c (revision 75bf465f0bc33e9b776a46d6a1b9b990f5fb7c37)
1  // SPDX-License-Identifier: GPL-2.0-only
2  /*
3   * iteration_check.c: test races having to do with xarray iteration
4   * Copyright (c) 2016 Intel Corporation
5   * Author: Ross Zwisler <ross.zwisler@linux.intel.com>
6   */
7  #include <pthread.h>
8  #include "test.h"
9  
10  #define NUM_THREADS	5
11  #define MAX_IDX		100
12  #define TAG		XA_MARK_0
13  #define NEW_TAG		XA_MARK_1
14  
15  static pthread_t threads[NUM_THREADS];
16  static unsigned int seeds[3];
17  static DEFINE_XARRAY(array);
18  static bool test_complete;
19  static int max_order;
20  
my_item_insert(struct xarray * xa,unsigned long index)21  void my_item_insert(struct xarray *xa, unsigned long index)
22  {
23  	XA_STATE(xas, xa, index);
24  	struct item *item = item_create(index, 0);
25  	int order;
26  
27  retry:
28  	xas_lock(&xas);
29  	for (order = max_order; order >= 0; order--) {
30  		xas_set_order(&xas, index, order);
31  		item->order = order;
32  		if (xas_find_conflict(&xas))
33  			continue;
34  		xas_store(&xas, item);
35  		xas_set_mark(&xas, TAG);
36  		break;
37  	}
38  	xas_unlock(&xas);
39  	if (xas_nomem(&xas, GFP_KERNEL))
40  		goto retry;
41  	if (order < 0)
42  		free(item);
43  }
44  
45  /* relentlessly fill the array with tagged entries */
add_entries_fn(void * arg)46  static void *add_entries_fn(void *arg)
47  {
48  	rcu_register_thread();
49  
50  	while (!test_complete) {
51  		unsigned long pgoff;
52  
53  		for (pgoff = 0; pgoff < MAX_IDX; pgoff++) {
54  			my_item_insert(&array, pgoff);
55  		}
56  	}
57  
58  	rcu_unregister_thread();
59  
60  	return NULL;
61  }
62  
63  /*
64   * Iterate over tagged entries, retrying when we find ourselves in a deleted
65   * node and randomly pausing the iteration.
66   */
tagged_iteration_fn(void * arg)67  static void *tagged_iteration_fn(void *arg)
68  {
69  	XA_STATE(xas, &array, 0);
70  	void *entry;
71  
72  	rcu_register_thread();
73  
74  	while (!test_complete) {
75  		xas_set(&xas, 0);
76  		rcu_read_lock();
77  		xas_for_each_marked(&xas, entry, ULONG_MAX, TAG) {
78  			if (xas_retry(&xas, entry))
79  				continue;
80  
81  			if (rand_r(&seeds[0]) % 50 == 0) {
82  				xas_pause(&xas);
83  				rcu_read_unlock();
84  				rcu_barrier();
85  				rcu_read_lock();
86  			}
87  		}
88  		rcu_read_unlock();
89  	}
90  
91  	rcu_unregister_thread();
92  
93  	return NULL;
94  }
95  
96  /*
97   * Iterate over the entries, retrying when we find ourselves in a deleted
98   * node and randomly pausing the iteration.
99   */
untagged_iteration_fn(void * arg)100  static void *untagged_iteration_fn(void *arg)
101  {
102  	XA_STATE(xas, &array, 0);
103  	void *entry;
104  
105  	rcu_register_thread();
106  
107  	while (!test_complete) {
108  		xas_set(&xas, 0);
109  		rcu_read_lock();
110  		xas_for_each(&xas, entry, ULONG_MAX) {
111  			if (xas_retry(&xas, entry))
112  				continue;
113  
114  			if (rand_r(&seeds[1]) % 50 == 0) {
115  				xas_pause(&xas);
116  				rcu_read_unlock();
117  				rcu_barrier();
118  				rcu_read_lock();
119  			}
120  		}
121  		rcu_read_unlock();
122  	}
123  
124  	rcu_unregister_thread();
125  
126  	return NULL;
127  }
128  
129  /*
130   * Randomly remove entries to help induce retries in the
131   * two iteration functions.
132   */
remove_entries_fn(void * arg)133  static void *remove_entries_fn(void *arg)
134  {
135  	rcu_register_thread();
136  
137  	while (!test_complete) {
138  		int pgoff;
139  		struct item *item;
140  
141  		pgoff = rand_r(&seeds[2]) % MAX_IDX;
142  
143  		item = xa_erase(&array, pgoff);
144  		if (item)
145  			item_free(item, pgoff);
146  	}
147  
148  	rcu_unregister_thread();
149  
150  	return NULL;
151  }
152  
tag_entries_fn(void * arg)153  static void *tag_entries_fn(void *arg)
154  {
155  	rcu_register_thread();
156  
157  	while (!test_complete) {
158  		tag_tagged_items(&array, 0, MAX_IDX, 10, TAG, NEW_TAG);
159  	}
160  	rcu_unregister_thread();
161  	return NULL;
162  }
163  
164  /* This is a unit test for a bug found by the syzkaller tester */
iteration_test(unsigned order,unsigned test_duration)165  void iteration_test(unsigned order, unsigned test_duration)
166  {
167  	int i;
168  
169  	printv(1, "Running %siteration tests for %d seconds\n",
170  			order > 0 ? "multiorder " : "", test_duration);
171  
172  	max_order = order;
173  	test_complete = false;
174  
175  	for (i = 0; i < 3; i++)
176  		seeds[i] = rand();
177  
178  	if (pthread_create(&threads[0], NULL, tagged_iteration_fn, NULL)) {
179  		perror("create tagged iteration thread");
180  		exit(1);
181  	}
182  	if (pthread_create(&threads[1], NULL, untagged_iteration_fn, NULL)) {
183  		perror("create untagged iteration thread");
184  		exit(1);
185  	}
186  	if (pthread_create(&threads[2], NULL, add_entries_fn, NULL)) {
187  		perror("create add entry thread");
188  		exit(1);
189  	}
190  	if (pthread_create(&threads[3], NULL, remove_entries_fn, NULL)) {
191  		perror("create remove entry thread");
192  		exit(1);
193  	}
194  	if (pthread_create(&threads[4], NULL, tag_entries_fn, NULL)) {
195  		perror("create tag entry thread");
196  		exit(1);
197  	}
198  
199  	sleep(test_duration);
200  	test_complete = true;
201  
202  	for (i = 0; i < NUM_THREADS; i++) {
203  		if (pthread_join(threads[i], NULL)) {
204  			perror("pthread_join");
205  			exit(1);
206  		}
207  	}
208  
209  	item_kill_tree(&array);
210  }
211