-
Notifications
You must be signed in to change notification settings - Fork 4
[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
Conversation
Item* candidate_; | ||
if (!lastTier && toRecycle_->isChainedItem()) { | ||
const auto parentKey = toRecycleParent_->getKey(); | ||
auto lock = chainedItemLocks_.tryLockExclusive(parentKey); |
There was a problem hiding this comment.
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()) ) { |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
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(); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Cleanup?
e704d65
to
6d847fe
Compare
@@ -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) { |
There was a problem hiding this comment.
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_) |
There was a problem hiding this comment.
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()) { |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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)) { |
There was a problem hiding this comment.
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 == NULLand
ref != 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); |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
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?
cachelib/allocator/Refcount.h
Outdated
return false; | ||
} | ||
if (!flagSet || alreadyExclusive) { | ||
// when we mark a chained item as moving it - it may not |
There was a problem hiding this comment.
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 |
There was a problem hiding this comment.
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.
@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 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. |
e775da1
to
228a3e2
Compare
if (isChained && (curValue & kAccessRefMask) > 1) { | ||
return false; | ||
} else if (!isChained && (curValue & kAccessRefMask) != 0) { | ||
return false; | ||
} |
There was a problem hiding this comment.
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()); |
There was a problem hiding this comment.
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_ |
There was a problem hiding this comment.
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
a6f8593
to
267f7d7
Compare
item for moving. Do not support slab release logic yet.
…cRef and markMoving
This is implemented in #84 |
This change is