Here's the original patch that describes is detail the bug that caused radix_tree_gang_lookup_tag_slot() to hang up indefinitely, and the fix for the same.
The regression test 2 runs to completion immediately if successful, otherwise it hangs up indefinitely.
RADIX_TREE_MAP_SIZE denotes the number of slots in each node of a radix tree object (typically 64). First we're going to insert RADIX_TREE_MAP_SIZE number of pages in the radix tree mt_tree, with indices/keys 0 to max_slots - 1. Then we set the item/page at index max_slots - 1 to have the tag PAGECACHE_TAG_DIRTY. For this bug it does not matter which item has the tag, it can be any item.
This is to update all items with indices in range 0 to max_slots - 2 and currently have the tag PAGECACHE_TAG_DIRTY to have the new tag PAGECACHE_TAG_TOWRITE, which are none. But root is nevertheless tagged with PAGECACHE_TAG_TOWRITE, as the following diff shows.
Now we insert a new page at key max_slots. This results in creation of a new child node which succeeds the tag status of the root tag. Therefore the tag of this new node has PAGECACHE_TAG_TOWRITE but there is no slot with PAGECACHE_TAG_TOWRITE tag in this new node.
Next we update the item at index max_slots - 1, which currently has tag PAGECACHE_TAG_DIRTY, to clear this tag.
Now we delete all items in key range 0 to max_slots - 1. Only the item with index RADIX_TREE_MAP_SIZE exists in the tree. The root still has the tag PAGECACHE_TAG_TOWRITE.
This calls __lookup_tag, but since the first slot of the tree node is null and the tag corresponding to it has PAGECACHE_TAG_TOWRITE, it keeps trying to get the items but it cannot do so ever. This bug was fixed to change radix_tree_tag_if_tagged so that it doesn't tag the root tag if it doesn't set any tags within the specified range.
Here's how the call to tag_tagged_items() which replaces radix_tree_range_tag_if_tagged(), essentially looks like:
Here we specifically look for slots marked with iftag using radix_tree_for_each_tagged() and only set them with the thentag, if found.
The regression test 2 runs to completion immediately if successful, otherwise it hangs up indefinitely.
#define RADIX_TREE_MAP_SHIFT (CONFIG_BASE_SMALL ? 4 : 6) #define RADIX_TREE_MAP_SIZE (1UL << RADIX_TREE_MAP_SHIFT) ... int max_slots = RADIX_TREE_MAP_SIZE; ... for (i = 0; i <= max_slots - 1; i++) { p = page_alloc(); radix_tree_insert(&mt_tree, i, p); } radix_tree_tag_set(&mt_tree, max_slots - 1, PAGECACHE_TAG_DIRTY);
RADIX_TREE_MAP_SIZE denotes the number of slots in each node of a radix tree object (typically 64). First we're going to insert RADIX_TREE_MAP_SIZE number of pages in the radix tree mt_tree, with indices/keys 0 to max_slots - 1. Then we set the item/page at index max_slots - 1 to have the tag PAGECACHE_TAG_DIRTY. For this bug it does not matter which item has the tag, it can be any item.
start = 0; end = max_slots - 2; radix_tree_range_tag_if_tagged(&mt_tree, start, end, 1, PAGECACHE_TAG_DIRTY, PAGECACHE_TAG_TOWRITE);
This is to update all items with indices in range 0 to max_slots - 2 and currently have the tag PAGECACHE_TAG_DIRTY to have the new tag PAGECACHE_TAG_TOWRITE, which are none. But root is nevertheless tagged with PAGECACHE_TAG_TOWRITE, as the following diff shows.
p = page_alloc(); radix_tree_insert(&mt_tree, max_slots, p);
Now we insert a new page at key max_slots. This results in creation of a new child node which succeeds the tag status of the root tag. Therefore the tag of this new node has PAGECACHE_TAG_TOWRITE but there is no slot with PAGECACHE_TAG_TOWRITE tag in this new node.
radix_tree_tag_clear(&mt_tree, max_slots - 1, PAGECACHE_TAG_DIRTY);
Next we update the item at index max_slots - 1, which currently has tag PAGECACHE_TAG_DIRTY, to clear this tag.
for (i = max_slots - 1; i >= 0; i--) radix_tree_delete(&mt_tree, i);
Now we delete all items in key range 0 to max_slots - 1. Only the item with index RADIX_TREE_MAP_SIZE exists in the tree. The root still has the tag PAGECACHE_TAG_TOWRITE.
// NOTE: start should not be 0 because radix_tree_gang_lookup_tag_slot // can return. start = 1; end = max_slots - 2; radix_tree_gang_lookup_tag_slot(&mt_tree, (void ***)pages, start, end, PAGECACHE_TAG_TOWRITE);
This calls __lookup_tag, but since the first slot of the tree node is null and the tag corresponding to it has PAGECACHE_TAG_TOWRITE, it keeps trying to get the items but it cannot do so ever. This bug was fixed to change radix_tree_tag_if_tagged so that it doesn't tag the root tag if it doesn't set any tags within the specified range.
Here's how the call to tag_tagged_items() which replaces radix_tree_range_tag_if_tagged(), essentially looks like:
radix_tree_for_each_tagged(slot, root, &iter, start, iftag) { if (iter.index > end) break; radix_tree_iter_tag_set(root, &iter, thentag); tagged++; if ((tagged % batch) != 0) continue; slot = radix_tree_iter_resume(slot, &iter); if (lock) { pthread_mutex_unlock(lock); rcu_barrier(); pthread_mutex_lock(lock); } }
Here we specifically look for slots marked with iftag using radix_tree_for_each_tagged() and only set them with the thentag, if found.