Skip to content

Conversation

@igchor
Copy link

@igchor igchor commented Mar 10, 2023

The assumption for moving items was that once item is unmarked no one can add new waiters for that item. However, since incrementing item ref count was not done under the MoveMap lock, there was a race: item could have been unmarked right after incRef returned incFailedMoving.


This change is Reviewable

@igchor
Copy link
Author

igchor commented Mar 10, 2023

It would be good to run a few long-running benchmarks to make sure this fixes the issue

} else {
// item is being moved - wait for completion
return handleWithWaitContextForMovingItem(*it);
while (true) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Inside this spin loop, it is a good practice to use pause instructions. Actually, folly already has platform-agnostic function asm_volatile_pause(). Furthermore, there is a folly::detail::Sleeper abstraction, but the problem is that it is not a public interface.

Copy link
Author

@igchor igchor Mar 13, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this loop is actually guaranteed to have at most 2 iterations. I'll try to verify this experimentally but here's my thinking:

acquire() is always called under Access Container lock so there is only one scenario (1.a.) when the race can happen on the evicting 'item':

  1. tryEvictToNextMemoryTier failed, or we are evicting from the last tier
    a. markForEvictionWhenMoving is called just after we got incFailedMoving: if under the MoveLock we realize that item is no longer moving, it has has to be marked for eviction so we just return NULL (we loop once and fail with incFailedEviction). There's no use-after free on the item because acquire holds AC lock, so findEviction will block on unlinkItemForEviction.
    b. in all other cases we synchronize on AC lock or the item is already marked for eviction (and we just return NULL).
  2. the item is successfully moved between tiers
    a. unmarMoving is called just after we got incFailedMoving? This cannot happen because unmarkMoving is called AFTER AC->replaceIf (which takes AC lock) which means we wouldn't enter acquire with pointer to the item that is being evicted.

We also need to consider newItemHdl. If find or inserOrReplace gets newItem that is still marked moving (replaceIf in moveRegularItemWithSync was executed but unmarkMoving was not) and the newItem is no longer moving under MoveLock then we just increment the item (it will work since it's no longer moving).

If someone will try to evict/move this new item before acquire gets a chance to incRef successfully, acquire will surely succeed in creating a wait context (because acquire() is under AC lock so findEviction cannot replaceIf or remove from access container which is always done before unmarking).

return handleWithWaitContextForMovingItem(*it);
while (true) {
// TODO: do not block incRef for child items to avoid deadlock
auto failIfMoving = getNumTiers() > 1 && !it->isChainedItem();

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can it be calculated outside the loop?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure, done.

The assumption for moving items was that once item is unmarked no one
can add new waiters for that item. However, since incrementing item ref
count was not done under the MoveMap lock, there was a race: item could
have been unmarked right after incRef returned incFailedMoving.
@igchor igchor marked this pull request as ready for review March 15, 2023 16:42
@byrnedj
Copy link

byrnedj commented Mar 15, 2023

I can confirm no impact on performance for leader and follower workloads.

@vinser52
Copy link

@guptask could you please check this patch with your DSA branch and confirm that it fixes the hang that you reported recently?

@byrnedj
Copy link

byrnedj commented Mar 16, 2023

@vinser52 I confirmed with @guptask that the patch works on his DSA branch - however we are now hitting a runtime error now with DSA that is unrelated to the race condition. We confirmed that his branch works by just using memcpy in place of DSA calls with this patch.

@vinser52
Copy link

So, in that case, I believe we can merge this PR?

@byrnedj byrnedj merged commit f4e30a7 into intel:develop Mar 16, 2023
@igchor igchor deleted the fix_moving_race branch March 20, 2023 17:23
@igchor igchor restored the fix_moving_race branch March 20, 2023 17:23
vinser52 pushed a commit that referenced this pull request Jul 17, 2023
The assumption for moving items was that once item is unmarked no one
can add new waiters for that item. However, since incrementing item ref
count was not done under the MoveMap lock, there was a race: item could
have been unmarked right after incRef returned incFailedMoving.
byrnedj pushed a commit that referenced this pull request Jul 23, 2023
The assumption for moving items was that once item is unmarked no one
can add new waiters for that item. However, since incrementing item ref
count was not done under the MoveMap lock, there was a race: item could
have been unmarked right after incRef returned incFailedMoving.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants