protected long takeEmptyPage()

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);
            }
        }
    }