xref: /dpdk/app/test/test_rwlock.c (revision 742bde12)
1 /* SPDX-License-Identifier: BSD-3-Clause
2  * Copyright(c) 2010-2014 Intel Corporation
3  */
4 
5 #include <stdio.h>
6 #include <stdint.h>
7 #include <inttypes.h>
8 #include <unistd.h>
9 #include <sys/queue.h>
10 #include <string.h>
11 
12 #include <rte_common.h>
13 #include <rte_memory.h>
14 #include <rte_per_lcore.h>
15 #include <rte_launch.h>
16 #include <rte_atomic.h>
17 #include <rte_rwlock.h>
18 #include <rte_eal.h>
19 #include <rte_lcore.h>
20 #include <rte_cycles.h>
21 
22 #include "test.h"
23 
24 /*
25  * rwlock test
26  * ===========
27  * Provides UT for rte_rwlock API.
28  * Main concern is on functional testing, but also provides some
29  * performance measurements.
30  * Obviously for proper testing need to be executed with more than one lcore.
31  */
32 
33 #define ITER_NUM	0x80
34 
35 #define TEST_SEC	5
36 
37 static rte_rwlock_t sl;
38 static rte_rwlock_t sl_tab[RTE_MAX_LCORE];
39 
40 enum {
41 	LC_TYPE_RDLOCK,
42 	LC_TYPE_WRLOCK,
43 };
44 
45 static struct {
46 	rte_rwlock_t lock;
47 	uint64_t tick;
48 	volatile union {
49 		uint8_t u8[RTE_CACHE_LINE_SIZE];
50 		uint64_t u64[RTE_CACHE_LINE_SIZE / sizeof(uint64_t)];
51 	} data;
52 } __rte_cache_aligned try_rwlock_data;
53 
54 struct try_rwlock_lcore {
55 	int32_t rc;
56 	int32_t type;
57 	struct {
58 		uint64_t tick;
59 		uint64_t fail;
60 		uint64_t success;
61 	} stat;
62 } __rte_cache_aligned;
63 
64 static struct try_rwlock_lcore try_lcore_data[RTE_MAX_LCORE];
65 
66 static int
67 test_rwlock_per_core(__attribute__((unused)) void *arg)
68 {
69 	rte_rwlock_write_lock(&sl);
70 	printf("Global write lock taken on core %u\n", rte_lcore_id());
71 	rte_rwlock_write_unlock(&sl);
72 
73 	rte_rwlock_write_lock(&sl_tab[rte_lcore_id()]);
74 	printf("Hello from core %u !\n", rte_lcore_id());
75 	rte_rwlock_write_unlock(&sl_tab[rte_lcore_id()]);
76 
77 	rte_rwlock_read_lock(&sl);
78 	printf("Global read lock taken on core %u\n", rte_lcore_id());
79 	rte_delay_ms(100);
80 	printf("Release global read lock on core %u\n", rte_lcore_id());
81 	rte_rwlock_read_unlock(&sl);
82 
83 	return 0;
84 }
85 
86 /*
87  * - There is a global rwlock and a table of rwlocks (one per lcore).
88  *
89  * - The test function takes all of these locks and launches the
90  *   ``test_rwlock_per_core()`` function on each core (except the master).
91  *
92  *   - The function takes the global write lock, display something,
93  *     then releases the global lock.
94  *   - Then, it takes the per-lcore write lock, display something, and
95  *     releases the per-core lock.
96  *   - Finally, a read lock is taken during 100 ms, then released.
97  *
98  * - The main function unlocks the per-lcore locks sequentially and
99  *   waits between each lock. This triggers the display of a message
100  *   for each core, in the correct order.
101  *
102  *   Then, it tries to take the global write lock and display the last
103  *   message. The autotest script checks that the message order is correct.
104  */
105 static int
106 rwlock_test1(void)
107 {
108 	int i;
109 
110 	rte_rwlock_init(&sl);
111 	for (i=0; i<RTE_MAX_LCORE; i++)
112 		rte_rwlock_init(&sl_tab[i]);
113 
114 	rte_rwlock_write_lock(&sl);
115 
116 	RTE_LCORE_FOREACH_SLAVE(i) {
117 		rte_rwlock_write_lock(&sl_tab[i]);
118 		rte_eal_remote_launch(test_rwlock_per_core, NULL, i);
119 	}
120 
121 	rte_rwlock_write_unlock(&sl);
122 
123 	RTE_LCORE_FOREACH_SLAVE(i) {
124 		rte_rwlock_write_unlock(&sl_tab[i]);
125 		rte_delay_ms(100);
126 	}
127 
128 	rte_rwlock_write_lock(&sl);
129 	/* this message should be the last message of test */
130 	printf("Global write lock taken on master core %u\n", rte_lcore_id());
131 	rte_rwlock_write_unlock(&sl);
132 
133 	rte_eal_mp_wait_lcore();
134 
135 	return 0;
136 }
137 
138 static int
139 try_read(uint32_t lc)
140 {
141 	int32_t rc;
142 	uint32_t i;
143 
144 	rc = rte_rwlock_read_trylock(&try_rwlock_data.lock);
145 	if (rc != 0)
146 		return rc;
147 
148 	for (i = 0; i != RTE_DIM(try_rwlock_data.data.u64); i++) {
149 
150 		/* race condition occurred, lock doesn't work properly */
151 		if (try_rwlock_data.data.u64[i] != 0) {
152 			printf("%s(%u) error: unexpected data pattern\n",
153 				__func__, lc);
154 			rte_memdump(stdout, NULL,
155 				(void *)(uintptr_t)&try_rwlock_data.data,
156 				sizeof(try_rwlock_data.data));
157 			rc = -EFAULT;
158 			break;
159 		}
160 	}
161 
162 	rte_rwlock_read_unlock(&try_rwlock_data.lock);
163 	return rc;
164 }
165 
166 static int
167 try_write(uint32_t lc)
168 {
169 	int32_t rc;
170 	uint32_t i, v;
171 
172 	v = RTE_MAX(lc % UINT8_MAX, 1U);
173 
174 	rc = rte_rwlock_write_trylock(&try_rwlock_data.lock);
175 	if (rc != 0)
176 		return rc;
177 
178 	/* update by bytes in reverese order */
179 	for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) {
180 
181 		/* race condition occurred, lock doesn't work properly */
182 		if (try_rwlock_data.data.u8[i] != 0) {
183 			printf("%s:%d(%u) error: unexpected data pattern\n",
184 				__func__, __LINE__, lc);
185 			rte_memdump(stdout, NULL,
186 				(void *)(uintptr_t)&try_rwlock_data.data,
187 				sizeof(try_rwlock_data.data));
188 			rc = -EFAULT;
189 			break;
190 		}
191 
192 		try_rwlock_data.data.u8[i] = v;
193 	}
194 
195 	/* restore by bytes in reverese order */
196 	for (i = RTE_DIM(try_rwlock_data.data.u8); i-- != 0; ) {
197 
198 		/* race condition occurred, lock doesn't work properly */
199 		if (try_rwlock_data.data.u8[i] != v) {
200 			printf("%s:%d(%u) error: unexpected data pattern\n",
201 				__func__, __LINE__, lc);
202 			rte_memdump(stdout, NULL,
203 				(void *)(uintptr_t)&try_rwlock_data.data,
204 				sizeof(try_rwlock_data.data));
205 			rc = -EFAULT;
206 			break;
207 		}
208 
209 		try_rwlock_data.data.u8[i] = 0;
210 	}
211 
212 	rte_rwlock_write_unlock(&try_rwlock_data.lock);
213 	return rc;
214 }
215 
216 static int
217 try_read_lcore(__rte_unused void *data)
218 {
219 	int32_t rc;
220 	uint32_t i, lc;
221 	uint64_t ftm, stm, tm;
222 	struct try_rwlock_lcore *lcd;
223 
224 	lc = rte_lcore_id();
225 	lcd = try_lcore_data + lc;
226 	lcd->type = LC_TYPE_RDLOCK;
227 
228 	ftm = try_rwlock_data.tick;
229 	stm = rte_get_timer_cycles();
230 
231 	do {
232 		for (i = 0; i != ITER_NUM; i++) {
233 			rc = try_read(lc);
234 			if (rc == 0)
235 				lcd->stat.success++;
236 			else if (rc == -EBUSY)
237 				lcd->stat.fail++;
238 			else
239 				break;
240 			rc = 0;
241 		}
242 		tm = rte_get_timer_cycles() - stm;
243 	} while (tm < ftm && rc == 0);
244 
245 	lcd->rc = rc;
246 	lcd->stat.tick = tm;
247 	return rc;
248 }
249 
250 static int
251 try_write_lcore(__rte_unused void *data)
252 {
253 	int32_t rc;
254 	uint32_t i, lc;
255 	uint64_t ftm, stm, tm;
256 	struct try_rwlock_lcore *lcd;
257 
258 	lc = rte_lcore_id();
259 	lcd = try_lcore_data + lc;
260 	lcd->type = LC_TYPE_WRLOCK;
261 
262 	ftm = try_rwlock_data.tick;
263 	stm = rte_get_timer_cycles();
264 
265 	do {
266 		for (i = 0; i != ITER_NUM; i++) {
267 			rc = try_write(lc);
268 			if (rc == 0)
269 				lcd->stat.success++;
270 			else if (rc == -EBUSY)
271 				lcd->stat.fail++;
272 			else
273 				break;
274 			rc = 0;
275 		}
276 		tm = rte_get_timer_cycles() - stm;
277 	} while (tm < ftm && rc == 0);
278 
279 	lcd->rc = rc;
280 	lcd->stat.tick = tm;
281 	return rc;
282 }
283 
284 static void
285 print_try_lcore_stats(const struct try_rwlock_lcore *tlc, uint32_t lc)
286 {
287 	uint64_t f, s;
288 
289 	f = RTE_MAX(tlc->stat.fail, 1ULL);
290 	s = RTE_MAX(tlc->stat.success, 1ULL);
291 
292 	printf("try_lcore_data[%u]={\n"
293 		"\trc=%d,\n"
294 		"\ttype=%s,\n"
295 		"\tfail=%" PRIu64 ",\n"
296 		"\tsuccess=%" PRIu64 ",\n"
297 		"\tcycles=%" PRIu64 ",\n"
298 		"\tcycles/op=%#Lf,\n"
299 		"\tcycles/success=%#Lf,\n"
300 		"\tsuccess/fail=%#Lf,\n"
301 		"};\n",
302 		lc,
303 		tlc->rc,
304 		tlc->type == LC_TYPE_RDLOCK ? "RDLOCK" : "WRLOCK",
305 		tlc->stat.fail,
306 		tlc->stat.success,
307 		tlc->stat.tick,
308 		(long double)tlc->stat.tick /
309 		(tlc->stat.fail + tlc->stat.success),
310 		(long double)tlc->stat.tick / s,
311 		(long double)tlc->stat.success / f);
312 }
313 
314 static void
315 collect_try_lcore_stats(struct try_rwlock_lcore *tlc,
316 	const struct try_rwlock_lcore *lc)
317 {
318 	tlc->stat.tick += lc->stat.tick;
319 	tlc->stat.fail += lc->stat.fail;
320 	tlc->stat.success += lc->stat.success;
321 }
322 
323 /*
324  * Process collected results:
325  *  - check status
326  *  - collect and print statistics
327  */
328 static int
329 process_try_lcore_stats(void)
330 {
331 	int32_t rc;
332 	uint32_t lc, rd, wr;
333 	struct try_rwlock_lcore rlc, wlc;
334 
335 	memset(&rlc, 0, sizeof(rlc));
336 	memset(&wlc, 0, sizeof(wlc));
337 
338 	rlc.type = LC_TYPE_RDLOCK;
339 	wlc.type = LC_TYPE_WRLOCK;
340 	rd = 0;
341 	wr = 0;
342 
343 	rc = 0;
344 	RTE_LCORE_FOREACH(lc) {
345 		rc |= try_lcore_data[lc].rc;
346 		if (try_lcore_data[lc].type == LC_TYPE_RDLOCK) {
347 			collect_try_lcore_stats(&rlc, try_lcore_data + lc);
348 			rd++;
349 		} else {
350 			collect_try_lcore_stats(&wlc, try_lcore_data + lc);
351 			wr++;
352 		}
353 	}
354 
355 	if (rc == 0) {
356 		RTE_LCORE_FOREACH(lc)
357 			print_try_lcore_stats(try_lcore_data + lc, lc);
358 
359 		if (rd != 0) {
360 			printf("aggregated stats for %u RDLOCK cores:\n", rd);
361 			print_try_lcore_stats(&rlc, rd);
362 		}
363 
364 		if (wr != 0) {
365 			printf("aggregated stats for %u WRLOCK cores:\n", wr);
366 			print_try_lcore_stats(&wlc, wr);
367 		}
368 	}
369 
370 	return rc;
371 }
372 
373 static void
374 try_test_reset(void)
375 {
376 	memset(&try_lcore_data, 0, sizeof(try_lcore_data));
377 	memset(&try_rwlock_data, 0, sizeof(try_rwlock_data));
378 	try_rwlock_data.tick = TEST_SEC * rte_get_tsc_hz();
379 }
380 
381 /* all lcores grab RDLOCK */
382 static int
383 try_rwlock_test_rda(void)
384 {
385 	try_test_reset();
386 
387 	/* start read test on all avaialble lcores */
388 	rte_eal_mp_remote_launch(try_read_lcore, NULL, CALL_MASTER);
389 	rte_eal_mp_wait_lcore();
390 
391 	return process_try_lcore_stats();
392 }
393 
394 /* all slave lcores grab RDLOCK, master one grabs WRLOCK */
395 static int
396 try_rwlock_test_rds_wrm(void)
397 {
398 	try_test_reset();
399 
400 	rte_eal_mp_remote_launch(try_read_lcore, NULL, SKIP_MASTER);
401 	try_write_lcore(NULL);
402 	rte_eal_mp_wait_lcore();
403 
404 	return process_try_lcore_stats();
405 }
406 
407 /* master and even slave lcores grab RDLOCK, odd lcores grab WRLOCK */
408 static int
409 try_rwlock_test_rde_wro(void)
410 {
411 	uint32_t lc, mlc;
412 
413 	try_test_reset();
414 
415 	mlc = rte_get_master_lcore();
416 
417 	RTE_LCORE_FOREACH(lc) {
418 		if (lc != mlc) {
419 			if ((lc & 1) == 0)
420 				rte_eal_remote_launch(try_read_lcore,
421 						NULL, lc);
422 			else
423 				rte_eal_remote_launch(try_write_lcore,
424 						NULL, lc);
425 		}
426 	}
427 	try_read_lcore(NULL);
428 	rte_eal_mp_wait_lcore();
429 
430 	return process_try_lcore_stats();
431 }
432 
433 static int
434 test_rwlock(void)
435 {
436 	uint32_t i;
437 	int32_t rc, ret;
438 
439 	static const struct {
440 		const char *name;
441 		int (*ftst)(void);
442 	} test[] = {
443 		{
444 			.name = "rwlock_test1",
445 			.ftst = rwlock_test1,
446 		},
447 		{
448 			.name = "try_rwlock_test_rda",
449 			.ftst = try_rwlock_test_rda,
450 		},
451 		{
452 			.name = "try_rwlock_test_rds_wrm",
453 			.ftst = try_rwlock_test_rds_wrm,
454 		},
455 		{
456 			.name = "try_rwlock_test_rde_wro",
457 			.ftst = try_rwlock_test_rde_wro,
458 		},
459 	};
460 
461 	ret = 0;
462 	for (i = 0; i != RTE_DIM(test); i++) {
463 		printf("starting test %s;\n", test[i].name);
464 		rc = test[i].ftst();
465 		printf("test %s completed with status %d\n", test[i].name, rc);
466 		ret |= rc;
467 	}
468 
469 	return ret;
470 }
471 
472 REGISTER_TEST_COMMAND(rwlock_autotest, test_rwlock);
473