Skip to content

[draft] initial sync for chained items based on head handle #75

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 6 commits into from

Conversation

byrnedj
Copy link

@byrnedj byrnedj commented Apr 3, 2023

This change is Reviewable

Item* candidate_;
if (!lastTier && toRecycle_->isChainedItem()) {
const auto parentKey = toRecycleParent_->getKey();
auto lock = chainedItemLocks_.tryLockExclusive(parentKey);
Copy link
Member

Choose a reason for hiding this comment

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

Could you put a comment explaining why we need to take this lock here? Do I remember correctly that this is to always lock in the same order and avoid deadlock?

@@ -1612,23 +1616,31 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
if (lastTier && shouldWriteToNvmCache(*candidate_) && !token.isValid()) {
stats_.evictFailConcurrentFill.inc();
} else if ( (lastTier && candidate_->markForEviction()) ||
(!lastTier && candidate_->markMoving(true)) ) {
(!lastTier && candidate_->markMoving()) ) {
Copy link
Member

Choose a reason for hiding this comment

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

Do we need to mark the candidate as moving if we have parent marked as moving?

Copy link
Member

Choose a reason for hiding this comment

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

I think that if we mark the parent as moving and then, verify that the parent hasn't changed we don't need to synchronize on the child item.

return;
} else if (!lastTier && toRecycle_->isChainedItem()) {
XDCHECK(toRecycleParent_->isMoving());
toRecycleParent_->unmarkMoving();
Copy link
Member

Choose a reason for hiding this comment

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

Should we check whether return value is 0 and do cleanup here?

@@ -1697,6 +1720,14 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {

(*stats_.numWritebacks)[tid][pid][cid].inc();
wakeUpWaiters(*candidate, std::move(evictedToNext));
if (toRecycleParent) {
XDCHECK(toRecycleParent->isMoving());
toRecycleParent->unmarkMoving();
Copy link
Member

Choose a reason for hiding this comment

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

Cleanup?

@@ -862,7 +941,7 @@ CacheAllocator<CacheTrait>::releaseBackToAllocator(Item& it,
// memory for a chained item but has decided not to insert the chained item
// to a parent item and instead drop the chained item handle. In this case,
// we free the chained item directly without calling remove callback.
if (it.isChainedItem()) {
if (it.isChainedItem() && &it != toRecycle) {
Copy link
Member

Choose a reason for hiding this comment

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

Why is this change needed?

if (item->markMoving(failIfRefNotZero)) {
markedMoving = true;
if (item->isChainedItem()) {
markedMoving = item->asChainedItem().getParentItem(compressor_)
Copy link
Member

Choose a reason for hiding this comment

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

Can we actually skip marking the actual item (only mark the parent)? The marking here is done to avoid freeing the item in releaseBackToAllocator. Is it handled differently now?


evicted = &expectedParent;
token = createPutToken(*evicted);
if (evicted->markForEviction()) {
// unmark the child so it will be freed
item.unmarkMoving();
if (item.isMoving()) {
Copy link
Member

Choose a reason for hiding this comment

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

Why do we need this check now?

//XDCHECK_EQ( &candidate->asChainedItem().getParentItem(compressor_),
// toRecycleParent );
XDCHECK(toRecycleParent->isMoving());
toRecycle = candidate; //we really want to recycle the child
Copy link
Member

Choose a reason for hiding this comment

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

Isn't toRecycle already equal to candidate?

auto parentHandle = acquire(toRecycleParent);
if (parentHandle) {
wakeUpWaiters(*toRecycleParent,std::move(parentHandle));
} else if (UNLIKELY(ref == 0)) {
Copy link
Member

Choose a reason for hiding this comment

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

Is it possible to have ``parentHandle == NULLandref != 0`? I mean, is there any `else` we need to handle? If no, perhaps let's add an assert?


auto& expectedParent = oldItem.getParentItem(compressor_);
const auto parentKey = expectedParent.getKey();
auto l = chainedItemLocks_.tryLockExclusive(parentKey);
Copy link
Member

Choose a reason for hiding this comment

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

Do we still need chaineItemLocks_ if we use moving bit for synchronization?

Copy link
Member

Choose a reason for hiding this comment

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

If I remember correctly, we still want to use chainedItemLocks_ to allow modifying the chain even if there is someone holding Handle to the parent item.

Is it still true for this implementation?

return false;
}
if (!flagSet || alreadyExclusive) {
// when we mark a chained item as moving it - it may not
Copy link
Member

Choose a reason for hiding this comment

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

When it will not be in the mmContainer?

@@ -3086,8 +3287,10 @@ bool CacheAllocator<CacheTrait>::moveForSlabRelease(
++itemMovingAttempts) {
stats_.numMoveAttempts.inc();

// Nothing to move and the key is likely also bogus for chained items.
// Nothing to move - in the case that tryMoving failed
Copy link
Member

Choose a reason for hiding this comment

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

Would it be possible to add support for SlabRelease in a separate PR? I think if we leave the current logic (just return false for moveForSlabRelease for chained item, everything should work fine (chained items will always be evicted).

It would make reviewing (and testing) simpler.

@igchor
Copy link
Member

igchor commented Apr 13, 2023

@byrnedj Can you also write down why we want to synchronize on either parent or head item (I forgot)? Why not just synchronize on the child? I know that this changes the semantics a bit, but I'm just wondering if there are any fundamental reasons we can't do that.

As I'm thinking about this right now, it seems to me that we should be able to make it work. One problem with such approach would be evicting the chain if moving fails (because we cannot use markForEviction when moving, we need to try to mark the parent for eviction). But we can just unmark the child, try to mark the parent and loop again if that fails.

This way, the only thing we would need to do on read path is to modify each loop that iterates over the chain to obtain handle for each child / manually check isMoving(). We could even probably just reuse moveChainedItem if we'd generalize the code for replacingInMMContainer. Something like this: igchor@f62b7af (does not compile yet)

We would also need to skip creating the handles for child items in the thread that actually moves the item between chains (otherwise we would have a deadlock - that thread would wait on item that it itself marked as moving) but it also seems doable.

Comment on lines +331 to 335
if (isChained && (curValue & kAccessRefMask) > 1) {
return false;
} else if (!isChained && (curValue & kAccessRefMask) != 0) {
return false;
}

Choose a reason for hiding this comment

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

I think it could be simplified to be something like this::

if ((curValue & kAccessRefMask) != isChained ? 1 : 0) {
    return false;
}

item.unmarkMoving();
// TODO entire chain just gets evicted since moveForSlabRelease
// returns false
XDCHECK(!item.isMoving());
Copy link
Member

@igchor igchor Apr 21, 2023

Choose a reason for hiding this comment

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

Why this assert? Can't item still be moving in this case? E.g. due to markForSlabRelease

@@ -1697,12 +1533,25 @@ CacheAllocator<CacheTrait>::findEviction(TierId tid, PoolId pid, ClassId cid) {
chainedItem_
? &toRecycle_->asChainedItem().getParentItem(compressor_)
: nullptr;
auto l_ = chainedItem_
Copy link
Member

Choose a reason for hiding this comment

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

I don;t think we should take a lock without checking if the item is chained first - it will have significant overhead I think

@byrnedj
Copy link
Author

byrnedj commented May 26, 2023

This is implemented in #84

@byrnedj byrnedj closed this May 26, 2023
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