private @NotNull PageImpl insertNewPage(final int pageIndex,
                                          final IntFunction<PageImpl> uninitializedPageFactory) {

    //Don't try to be lock-free on updates, just avoid holding the _global_ lock during IO:
    // 1) put blankPage under the pagesLock,
    // 2) release the pagesLock,
    // 3) load page outside the pagesLock, under per-page lock
    final IntRef insertionIndexRef = new IntRef();
    final PageImpl blankPage;
    pagesLock.lock();
    try {
      final PageImpl alreadyInsertedPage = findPageOrInsertionIndex(pages, pageIndex, insertionIndexRef);
      if (alreadyInsertedPage != null) {
        //race: somebody inserted the page between lock-free speculative check and locked re-check
        return alreadyInsertedPage;
      }

      if (insertionIndexRef.get() < 0) {
        // (page == null && insertionIndex < 0) => no space remains in the table
        final int newTableSize = (int)Math.ceil((pagesCount / loadFactor) * GROWTH_FACTOR);
        rehashToSize(newTableSize);

        final PageImpl mustBeNull = findPageOrInsertionIndex(pages, pageIndex, insertionIndexRef);
        if (mustBeNull != null) {
          throw new AssertionError(
            "Bug: first lookup(pageIndex: " + pageIndex + ") founds nothing, same search after resize must find nothing too " + pages);
        }
        if (insertionIndexRef.get() < 0) {
          throw new AssertionError("Bug: table just resized {length: " + pages.length() + ", pages: " + pagesCount + "} => " +
                                   "there must be a free slot for pageIndex: " + pageIndex);
        }
      }

      blankPage = uninitializedPageFactory.apply(pageIndex);

      final int insertionIndex = insertionIndexRef.get();
      pages.set(insertionIndex, blankPage);
      pagesCount++;

      if (pagesCount > pages.length() * loadFactor) {
        final int newTableSize = (int)Math.ceil((pagesCount / loadFactor) * GROWTH_FACTOR);
        rehashToSize(newTableSize);

        //RC: page table need not only enlargement, but also shrinking. PageTable grows big
        //    temporarily, while we read the file intensively -- but after that period the
        //    table should shrink. Contrary to the enlargement, shrinking is implemented by
        //    maintenance thread, in the background. This is because a lot of entries in the
        //    enlarged table would be tombstones (pages already reclaimed, but entries remain)
        //    and maintaining the count of those tombstones is not convenient -- Page status
        //    changes (...->tombstone) are generally detached from PageTable logic, and better
        //    be kept that way. But the maintenance thread regularly scans all the pages anyway,
        //    so it could easily count (tombstones vs alive) entries, and trigger shrinking if
        //    there are <30-40% entries are alive.
      }
    }
    finally {
      pagesLock.unlock();
    }

    return blankPage;


    //blankPage.pageLock().writeLock().lock();
    //try {
    //  if (blankPage.isTombstone()) {
    //    throw new ClosedStorageException("Storage is already closed");
    //  }
    //  if (!blankPage.isNotReadyYet()) {
    //    //possible if short-circuit (NOT_READY_YET->TOMBSTONE) transition of storage close happens
    //    throw new AssertionError("Page must be {NOT_READY_YET, TOMBSTONE}, but " + blankPage);
    //  }
    //  blankPage.prepareForUse(pageContentLoader);
    //  if (blankPage.isNotReadyYet()) {
    //    //RC: Page state could be any of {USABLE, ABOUT_TO_UNMAP, TOMBSTONE} here -- but not NOT_READY_YET.
    //    //    USABLE is the obvious case, but {ABOUT_TO_UNMAP, TOMBSTONE} could happen if new page
    //    //    allocation races with storage closing: in such a scenario the page could be entombed async-ly
    //    //    immediately after .prepareForUse() made it USABLE -- nothing prevents it, since page
    //    //    has usageCount=0 still.
    //    throw new AssertionError("Page must be {USABLE, ABOUT_TO_UNMAP, TOMBSTONE}, but " + blankPage);
    //  }
    //  if (blankPage.isDirty()) {
    //    throw new AssertionError("Page must NOT be dirty just after .pageLoader: " + blankPage);
    //  }
    //  return blankPage;
    //}
    //finally {
    //  blankPage.pageLock().writeLock().unlock();
    //}
  }
private @NotNull PageImpl insertNewPage(final int pageIndex,
                                          final IntFunction<PageImpl> uninitializedPageFactory)