in modules/page-memory/src/main/java/org/apache/ignite/internal/pagememory/freelist/PagesList.java [1190:1349]
protected long takeEmptyPage(
int bucket,
@Nullable IoVersions<?> initIoVers,
IoStatisticsHolder statHolder
) throws IgniteInternalCheckedException {
PagesCache pagesCache = getBucketCache(bucket, false);
long pageId;
if (pagesCache != null && (pageId = pagesCache.poll()) != 0L) {
decrementBucketSize(bucket);
log.debug("Take page from pages list cache [list={}, bucket={}, pageId={}]", name(), bucket, pageId);
assert !isReuseBucket(bucket) : "reuse bucket detected";
return pageId;
}
for (int lockAttempt = 0; ; ) {
Stripe stripe = getStripeForTake(bucket);
if (stripe == null) {
return 0L;
}
final long tailId = stripe.tailId;
// Stripe was removed from bucket concurrently.
if (tailId == 0L) {
continue;
}
final long tailPage = acquirePage(tailId, statHolder);
try {
long tailAddr = writeLockPage(tailId, tailPage, bucket, lockAttempt++, null); // Explicit check.
if (tailAddr == 0L) {
continue;
}
if (stripe.empty || stripe.tailId != tailId) {
// Another thread took the last page.
writeUnlock(tailId, tailPage, tailAddr, false);
if (bucketsSize.get(bucket) > 0) {
lockAttempt--; // Ignore current attempt.
continue;
} else {
return 0L;
}
}
assert getPageId(tailAddr) == tailId
: "tailId = " + hexLong(tailId) + ", pageId = " + hexLong(getPageId(tailAddr));
assert getType(tailAddr) == T_PAGE_LIST_NODE
: "tailId = " + hexLong(tailId) + ", type = " + getType(tailAddr);
boolean dirty = false;
long dataPageId;
long recycleId = 0L;
try {
PagesListNodeIo io = PagesListNodeIo.VERSIONS.forPage(tailAddr);
if (io.getNextId(tailAddr) != 0) {
// It is not a tail anymore, retry.
continue;
}
pageId = io.takeAnyPage(tailAddr);
if (pageId != 0L) {
decrementBucketSize(bucket);
dirty = true;
if (isReuseBucket(bucket) && !(itemId(pageId) > 0 && itemId(pageId) <= MAX_ITEM_ID_NUM)) {
throw corruptedFreeListException("Incorrectly recycled pageId in reuse bucket: " + hexLong(pageId), pageId);
}
if (isReuseBucket(bucket)) {
byte flag = getFlag(initIoVers);
PageIo initIo = initIoVers == null ? null : initIoVers.latest();
dataPageId = initRecycledPage0(pageId, flag, initIo);
} else {
dataPageId = pageId;
}
if (io.isEmpty(tailAddr)) {
long prevId = io.getPreviousId(tailAddr);
// If we got an empty page in non-reuse bucket, move it back to reuse list
// to prevent empty page leak to data pages.
if (!isReuseBucket(bucket)) {
if (prevId != 0L) {
Boolean ok = write(prevId, cutTail, null, bucket, FALSE, statHolder);
assert ok == TRUE : ok;
recycleId = recyclePage(tailId, tailAddr);
} else {
stripe.empty = true;
}
} else {
stripe.empty = prevId == 0L;
}
}
} else {
// The tail page is empty, but stripe is not. It might
// happen only if we are in reuse bucket and it has
// a previous page, so, the current page can be collected
assert isReuseBucket(bucket);
long prevId = io.getPreviousId(tailAddr);
assert prevId != 0L;
Boolean ok = write(prevId, cutTail, bucket, FALSE, statHolder);
assert ok == TRUE : ok;
decrementBucketSize(bucket);
byte flag = getFlag(initIoVers);
PageIo pageIo = initIoVers != null ? initIoVers.latest() : null;
dataPageId = initReusedPage(tailId, tailAddr, partitionId(tailId), flag, pageIo);
dirty = true;
}
// If we do not have a previous page (we are at head), then we still can return
// current page but we have to drop the whole stripe. Since it is a reuse bucket,
// we will not do that, but just return 0L, because this may produce contention on
// meta page.
} finally {
writeUnlock(tailId, tailPage, tailAddr, dirty);
}
// Put recycled page (if any) to the reuse bucket after tail is unlocked.
if (recycleId != 0L) {
assert !isReuseBucket(bucket);
reuseList.addForRecycle(new SingletonReuseBag(recycleId));
}
log.debug("Take page from pages list [list={}, bucket={}, dataPageId={}, tailId={}]", name(), bucket, dataPageId, tailId);
return dataPageId;
} finally {
releasePage(tailId, tailPage);
}
}
}