![Matthew Wilcox](/assets/img/avatar_default.png)
The tag_tagged_items() function is supposed to test the page-writeback tagging code. Since that has been converted to the XArray, there's not much point in testing the radix tree's tagging code. This requires using the pthread mutex embedded in the xarray instead of an external lock, so remove the pthread mutexes which protect xarrays/radix trees. Also remove radix_tree_iter_tag_set() as this was the last user. Signed-off-by: Matthew Wilcox <willy@infradead.org>
352 lines
8.6 KiB
C
352 lines
8.6 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
#include <stdlib.h>
|
|
#include <assert.h>
|
|
#include <stdio.h>
|
|
#include <string.h>
|
|
|
|
#include <linux/slab.h>
|
|
#include <linux/radix-tree.h>
|
|
|
|
#include "test.h"
|
|
|
|
|
|
static void
|
|
__simple_checks(struct radix_tree_root *tree, unsigned long index, int tag)
|
|
{
|
|
unsigned long first = 0;
|
|
int ret;
|
|
|
|
item_check_absent(tree, index);
|
|
assert(item_tag_get(tree, index, tag) == 0);
|
|
|
|
item_insert(tree, index);
|
|
assert(item_tag_get(tree, index, tag) == 0);
|
|
item_tag_set(tree, index, tag);
|
|
ret = item_tag_get(tree, index, tag);
|
|
assert(ret != 0);
|
|
ret = tag_tagged_items(tree, first, ~0UL, 10, tag, !tag);
|
|
assert(ret == 1);
|
|
ret = item_tag_get(tree, index, !tag);
|
|
assert(ret != 0);
|
|
ret = item_delete(tree, index);
|
|
assert(ret != 0);
|
|
item_insert(tree, index);
|
|
ret = item_tag_get(tree, index, tag);
|
|
assert(ret == 0);
|
|
ret = item_delete(tree, index);
|
|
assert(ret != 0);
|
|
ret = item_delete(tree, index);
|
|
assert(ret == 0);
|
|
}
|
|
|
|
void simple_checks(void)
|
|
{
|
|
unsigned long index;
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
|
|
for (index = 0; index < 10000; index++) {
|
|
__simple_checks(&tree, index, 0);
|
|
__simple_checks(&tree, index, 1);
|
|
}
|
|
verify_tag_consistency(&tree, 0);
|
|
verify_tag_consistency(&tree, 1);
|
|
printv(2, "before item_kill_tree: %d allocated\n", nr_allocated);
|
|
item_kill_tree(&tree);
|
|
rcu_barrier();
|
|
printv(2, "after item_kill_tree: %d allocated\n", nr_allocated);
|
|
}
|
|
|
|
/*
|
|
* Check that tags propagate correctly when extending a tree.
|
|
*/
|
|
static void extend_checks(void)
|
|
{
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
|
|
item_insert(&tree, 43);
|
|
assert(item_tag_get(&tree, 43, 0) == 0);
|
|
item_tag_set(&tree, 43, 0);
|
|
assert(item_tag_get(&tree, 43, 0) == 1);
|
|
item_insert(&tree, 1000000);
|
|
assert(item_tag_get(&tree, 43, 0) == 1);
|
|
|
|
item_insert(&tree, 0);
|
|
item_tag_set(&tree, 0, 0);
|
|
item_delete(&tree, 1000000);
|
|
assert(item_tag_get(&tree, 43, 0) != 0);
|
|
item_delete(&tree, 43);
|
|
assert(item_tag_get(&tree, 43, 0) == 0); /* crash */
|
|
assert(item_tag_get(&tree, 0, 0) == 1);
|
|
|
|
verify_tag_consistency(&tree, 0);
|
|
|
|
item_kill_tree(&tree);
|
|
}
|
|
|
|
/*
|
|
* Check that tags propagate correctly when contracting a tree.
|
|
*/
|
|
static void contract_checks(void)
|
|
{
|
|
struct item *item;
|
|
int tmp;
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
|
|
tmp = 1<<RADIX_TREE_MAP_SHIFT;
|
|
item_insert(&tree, tmp);
|
|
item_insert(&tree, tmp+1);
|
|
item_tag_set(&tree, tmp, 0);
|
|
item_tag_set(&tree, tmp, 1);
|
|
item_tag_set(&tree, tmp+1, 0);
|
|
item_delete(&tree, tmp+1);
|
|
item_tag_clear(&tree, tmp, 1);
|
|
|
|
assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 0) == 1);
|
|
assert(radix_tree_gang_lookup_tag(&tree, (void **)&item, 0, 1, 1) == 0);
|
|
|
|
assert(item_tag_get(&tree, tmp, 0) == 1);
|
|
assert(item_tag_get(&tree, tmp, 1) == 0);
|
|
|
|
verify_tag_consistency(&tree, 0);
|
|
item_kill_tree(&tree);
|
|
}
|
|
|
|
/*
|
|
* Stupid tag thrasher
|
|
*
|
|
* Create a large linear array corresponding to the tree. Each element in
|
|
* the array is coherent with each node in the tree
|
|
*/
|
|
|
|
enum {
|
|
NODE_ABSENT = 0,
|
|
NODE_PRESENT = 1,
|
|
NODE_TAGGED = 2,
|
|
};
|
|
|
|
#define THRASH_SIZE (1000 * 1000)
|
|
#define N 127
|
|
#define BATCH 33
|
|
|
|
static void gang_check(struct radix_tree_root *tree,
|
|
char *thrash_state, int tag)
|
|
{
|
|
struct item *items[BATCH];
|
|
int nr_found;
|
|
unsigned long index = 0;
|
|
unsigned long last_index = 0;
|
|
|
|
while ((nr_found = radix_tree_gang_lookup_tag(tree, (void **)items,
|
|
index, BATCH, tag))) {
|
|
int i;
|
|
|
|
for (i = 0; i < nr_found; i++) {
|
|
struct item *item = items[i];
|
|
|
|
while (last_index < item->index) {
|
|
assert(thrash_state[last_index] != NODE_TAGGED);
|
|
last_index++;
|
|
}
|
|
assert(thrash_state[last_index] == NODE_TAGGED);
|
|
last_index++;
|
|
}
|
|
index = items[nr_found - 1]->index + 1;
|
|
}
|
|
}
|
|
|
|
static void do_thrash(struct radix_tree_root *tree, char *thrash_state, int tag)
|
|
{
|
|
int insert_chunk;
|
|
int delete_chunk;
|
|
int tag_chunk;
|
|
int untag_chunk;
|
|
int total_tagged = 0;
|
|
int total_present = 0;
|
|
|
|
for (insert_chunk = 1; insert_chunk < THRASH_SIZE; insert_chunk *= N)
|
|
for (delete_chunk = 1; delete_chunk < THRASH_SIZE; delete_chunk *= N)
|
|
for (tag_chunk = 1; tag_chunk < THRASH_SIZE; tag_chunk *= N)
|
|
for (untag_chunk = 1; untag_chunk < THRASH_SIZE; untag_chunk *= N) {
|
|
int i;
|
|
unsigned long index;
|
|
int nr_inserted = 0;
|
|
int nr_deleted = 0;
|
|
int nr_tagged = 0;
|
|
int nr_untagged = 0;
|
|
int actual_total_tagged;
|
|
int actual_total_present;
|
|
|
|
for (i = 0; i < insert_chunk; i++) {
|
|
index = rand() % THRASH_SIZE;
|
|
if (thrash_state[index] != NODE_ABSENT)
|
|
continue;
|
|
item_check_absent(tree, index);
|
|
item_insert(tree, index);
|
|
assert(thrash_state[index] != NODE_PRESENT);
|
|
thrash_state[index] = NODE_PRESENT;
|
|
nr_inserted++;
|
|
total_present++;
|
|
}
|
|
|
|
for (i = 0; i < delete_chunk; i++) {
|
|
index = rand() % THRASH_SIZE;
|
|
if (thrash_state[index] == NODE_ABSENT)
|
|
continue;
|
|
item_check_present(tree, index);
|
|
if (item_tag_get(tree, index, tag)) {
|
|
assert(thrash_state[index] == NODE_TAGGED);
|
|
total_tagged--;
|
|
} else {
|
|
assert(thrash_state[index] == NODE_PRESENT);
|
|
}
|
|
item_delete(tree, index);
|
|
assert(thrash_state[index] != NODE_ABSENT);
|
|
thrash_state[index] = NODE_ABSENT;
|
|
nr_deleted++;
|
|
total_present--;
|
|
}
|
|
|
|
for (i = 0; i < tag_chunk; i++) {
|
|
index = rand() % THRASH_SIZE;
|
|
if (thrash_state[index] != NODE_PRESENT) {
|
|
if (item_lookup(tree, index))
|
|
assert(item_tag_get(tree, index, tag));
|
|
continue;
|
|
}
|
|
item_tag_set(tree, index, tag);
|
|
item_tag_set(tree, index, tag);
|
|
assert(thrash_state[index] != NODE_TAGGED);
|
|
thrash_state[index] = NODE_TAGGED;
|
|
nr_tagged++;
|
|
total_tagged++;
|
|
}
|
|
|
|
for (i = 0; i < untag_chunk; i++) {
|
|
index = rand() % THRASH_SIZE;
|
|
if (thrash_state[index] != NODE_TAGGED)
|
|
continue;
|
|
item_check_present(tree, index);
|
|
assert(item_tag_get(tree, index, tag));
|
|
item_tag_clear(tree, index, tag);
|
|
item_tag_clear(tree, index, tag);
|
|
assert(thrash_state[index] != NODE_PRESENT);
|
|
thrash_state[index] = NODE_PRESENT;
|
|
nr_untagged++;
|
|
total_tagged--;
|
|
}
|
|
|
|
actual_total_tagged = 0;
|
|
actual_total_present = 0;
|
|
for (index = 0; index < THRASH_SIZE; index++) {
|
|
switch (thrash_state[index]) {
|
|
case NODE_ABSENT:
|
|
item_check_absent(tree, index);
|
|
break;
|
|
case NODE_PRESENT:
|
|
item_check_present(tree, index);
|
|
assert(!item_tag_get(tree, index, tag));
|
|
actual_total_present++;
|
|
break;
|
|
case NODE_TAGGED:
|
|
item_check_present(tree, index);
|
|
assert(item_tag_get(tree, index, tag));
|
|
actual_total_present++;
|
|
actual_total_tagged++;
|
|
break;
|
|
}
|
|
}
|
|
|
|
gang_check(tree, thrash_state, tag);
|
|
|
|
printv(2, "%d(%d) %d(%d) %d(%d) %d(%d) / "
|
|
"%d(%d) present, %d(%d) tagged\n",
|
|
insert_chunk, nr_inserted,
|
|
delete_chunk, nr_deleted,
|
|
tag_chunk, nr_tagged,
|
|
untag_chunk, nr_untagged,
|
|
total_present, actual_total_present,
|
|
total_tagged, actual_total_tagged);
|
|
}
|
|
}
|
|
|
|
static void thrash_tags(void)
|
|
{
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
char *thrash_state;
|
|
|
|
thrash_state = malloc(THRASH_SIZE);
|
|
memset(thrash_state, 0, THRASH_SIZE);
|
|
|
|
do_thrash(&tree, thrash_state, 0);
|
|
|
|
verify_tag_consistency(&tree, 0);
|
|
item_kill_tree(&tree);
|
|
free(thrash_state);
|
|
}
|
|
|
|
static void leak_check(void)
|
|
{
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
|
|
item_insert(&tree, 1000000);
|
|
item_delete(&tree, 1000000);
|
|
item_kill_tree(&tree);
|
|
}
|
|
|
|
static void __leak_check(void)
|
|
{
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
|
|
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
|
|
item_insert(&tree, 1000000);
|
|
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
|
|
item_delete(&tree, 1000000);
|
|
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
|
|
item_kill_tree(&tree);
|
|
printv(2, "%d: nr_allocated=%d\n", __LINE__, nr_allocated);
|
|
}
|
|
|
|
static void single_check(void)
|
|
{
|
|
struct item *items[BATCH];
|
|
RADIX_TREE(tree, GFP_KERNEL);
|
|
int ret;
|
|
unsigned long first = 0;
|
|
|
|
item_insert(&tree, 0);
|
|
item_tag_set(&tree, 0, 0);
|
|
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
|
|
assert(ret == 1);
|
|
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 1, BATCH, 0);
|
|
assert(ret == 0);
|
|
verify_tag_consistency(&tree, 0);
|
|
verify_tag_consistency(&tree, 1);
|
|
ret = tag_tagged_items(&tree, first, 10, 10, XA_MARK_0, XA_MARK_1);
|
|
assert(ret == 1);
|
|
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 1);
|
|
assert(ret == 1);
|
|
item_tag_clear(&tree, 0, 0);
|
|
ret = radix_tree_gang_lookup_tag(&tree, (void **)items, 0, BATCH, 0);
|
|
assert(ret == 0);
|
|
item_kill_tree(&tree);
|
|
}
|
|
|
|
void tag_check(void)
|
|
{
|
|
single_check();
|
|
extend_checks();
|
|
contract_checks();
|
|
rcu_barrier();
|
|
printv(2, "after extend_checks: %d allocated\n", nr_allocated);
|
|
__leak_check();
|
|
leak_check();
|
|
rcu_barrier();
|
|
printv(2, "after leak_check: %d allocated\n", nr_allocated);
|
|
simple_checks();
|
|
rcu_barrier();
|
|
printv(2, "after simple_checks: %d allocated\n", nr_allocated);
|
|
thrash_tags();
|
|
rcu_barrier();
|
|
printv(2, "after thrash_tags: %d allocated\n", nr_allocated);
|
|
}
|