boolean retry;
int numtries = 0;
long startSearch = lastAllocatedPage;
AllocPage allocPage = null; // the alloc page
BasePage page = null; // the new page
try
{
do
{
retry = false; // we don't expect we need to retry
synchronized(allocCache)
{
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(ntt.getId().equals(allocHandle.getTransaction().getId()));
if (useNTT)
SanityManager.ASSERT(!ntt.getId().equals(userHandle.getTransaction().getId()));
}
/* find an allocation page that can handle adding a new page
allocPage is unlatched when the ntt commits
The new page is initialized by the ntt but the latch is
transfered to the user transaction before the allocPage is
unlatched. The allocPage latch prevents almost any other reader or
writer from finding the new page until the ntt is committed and
the new page latched by the user transaction.
(If the page is being reused, it is possible for another xact
which kept a handle on the reused page to find the page during the
transfer UT -> NTT. If this unlikely even occurs and the transfer
fails [see code relating to transfer below], we retry from
the beginning.)
After the NTT commits a reader (getNextPageNumber) may get the page
number of the newly allocated page and it will wait for the new page
and latch it when the user transaction commits, aborts or unlatches
the new page. Whether the user transaction commits or aborts,
the new page stay allocated.
RESOLVE: before NTT rolls back (or commits) the latch is released.
To repopulate the allocation cache, need to get either the container
lock on add page, or get a per allocation page lock.
This blocks all page read (getPage) from accessing this alloc page in
this this container until the alloc page is unlatched. Those who
already have a page handle into this container is unaffected.
In other words, allocation blocks out reader (of any page that is
managed by this alloc page) by the latch on the allocation page.
Note that write page can proceed as usual.
*/
allocPage = findAllocPageForAdd(allocHandle, ntt, startSearch);
allocCache.invalidate(allocPage, allocPage.getPageNumber());
}
if (SanityManager.DEBUG)
{
if (allocPage == null)
allocCache.dumpAllocationCache();
SanityManager.ASSERT(allocPage != null,
"findAllocPageForAdd returned a null alloc page");
}
//
// get the next free page's number.
// for case 1, page number > lastPreallocPage
// for case 2, page number <= lastPage
// for case 3, lastPage < page number <= lastPreallocPage
//
pageNumber = allocPage.nextFreePageNumber(startSearch);
// need to distinguish between the following 3 cases:
// 1) the page has not been allocate or initalized.
// Create it in the page cache and sync it to disk.
// 2) the page is being re-allocated.
// We need to read it in to re-initialize it
// 3) the page has been preallocated.
// Create it in the page cache and don't sync it to disk
//
// first find out the current last initialized page and
// preallocated page before the new page is added
lastPage = allocPage.getLastPagenum();
lastPreallocPage = allocPage.getLastPreallocPagenum();
reuse = pageNumber <= lastPage;
// no address translation necessary
pkey = new PageKey(identity, pageNumber);
if (reuse)
{
// if re-useing a page, make sure the deallocLock on the new
// page is not held. We only need a zero duration lock on
// the new page because the allocPage is latched and this
// is the only thread which can be looking at this
// pageNumber.
RecordHandle deallocLock = BasePage.MakeRecordHandle(pkey,
RecordHandle.DEALLOCATE_PROTECTION_HANDLE);
if (!getDeallocLock(allocHandle, deallocLock,
false /* nowait */,
true /* zeroDuration */))
{
// The transaction which deallocated this page has not
// committed yet. Try going to some other page. If
// this is the first time we fail to get the dealloc
// lock, try from the beginning of the allocated page.
// If we already did that and still fail, keep going
// until we get a brand new page.
if (numtries == 0)
{
startSearch = ContainerHandle.INVALID_PAGE_NUMBER;
lastAllocatedPage = pageNumber;
}
else // continue from where we were
startSearch = pageNumber;
numtries++;
// We have to unlatch the allocPage so that if that
// transaction rolls back, it won't deadlock with this
// transaction.
allocPage.unlatch();
allocPage = null;
retry = true;
}
else
{
// we got the lock, next time start from there
lastAllocatedPage = pageNumber;
}
}
else
{
// we got a new page, next time, start from beginning of
// the bit map again if we suspect there are some some
// deallocated pages
if (numtries > 0)
lastAllocatedPage = ContainerHandle.INVALID_PAGE_NUMBER;
else
lastAllocatedPage = pageNumber;
}
// Retry from the beginning if necessary.
if (retry)
continue;
// If we get past here must have (retry == false)
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(retry == false);
}
// Now we have verified that the allocPage is latched and we can get
// the zeroDuration deallocLock nowait. This means the transaction
// which freed the page has committed. Had that transaction aborted,
// we would have retried.
if (SanityManager.DEBUG)
{
// ASSERT lastPage <= lastPreallocPage
if (lastPage > lastPreallocPage)
SanityManager.THROWASSERT("last page " +
lastPage + " > lastPreallocPage " + lastPreallocPage);
}
// No I/O at all if this new page is requested as part of a create
// and load statement or this new page is in a temporary container.
// In the former case, BaseContainer will allow the MODE_UNLOGGED
// bit to go thru to the nested top transaction alloc handle.
// In the later case, there is no nested top transaction and the
// alloc handle is the user handle, which is UNLOGGED.
boolean noIO = (allocHandle.getMode() & ContainerHandle.MODE_UNLOGGED) ==
ContainerHandle.MODE_UNLOGGED;
// If we do not need the I/O (either because we are in a
// create_unlogged mode or we are dealing with a temp table), don't
// do any preallocation. Otherwise, see if we should be
// pre-Allocating page by now. We don't call it before
// nextFreePageNumber because finding a reusable page may be
// expensive and we don't want to start preAllocation unless there
// is no more reusable page. Unless we are called explicitly to
// bulk increase the container size in a preload or in a create
// container.
if (!noIO && (bulkIncreaseContainerSize ||
(pageNumber > lastPreallocPage &&
pageNumber > PreAllocThreshold)))
{
allocPage.preAllocatePage(this, PreAllocThreshold,
PreAllocSize);
}
// update last preAllocated Page, it may have been changed by the
// preAllocatePage call. We don't want to do the sync if it
// preAllocatePage already took care of it.
lastPreallocPage = allocPage.getLastPreallocPagenum();
boolean prealloced = pageNumber <= lastPreallocPage;
// Argument to the create is an array of ints.
// The array is only used for new page creation or for creating a
// preallocated page, not for reuse.
// 0'th element is the page format
// 1'st element is whether or not to sync the page to disk
// 2'nd element is pagesize
// 3'rd element is spareSpace
int[] createPageArgs = new int[STORED_PAGE_ARG_NUM];
createPageArgs[0] = StoredPage.FORMAT_NUMBER;
createPageArgs[1] = prealloced ? 0 : (noIO ? 0 : CachedPage.WRITE_SYNC);
createPageArgs[2] = pageSize;
createPageArgs[3] = spareSpace;
createPageArgs[4] = minimumRecordSize;
// RESOLVE: right now, there is no re-mapping of pages, so
// pageOffset = pageNumber*pageSize
long pageOffset = pageNumber * pageSize;
// initialize a new user page
// we first use the NTT to initialize the new page - in case the
// allocation failed, it is rolled back with the NTT.
// Later, we transfer the latch to the userHandle so it won't be
// released when the ntt commits
page = initPage(allocHandle, pkey, createPageArgs, pageOffset,
reuse, isOverflow);
if (SanityManager.DEBUG)
{
SanityManager.ASSERT(page != null, "initPage returns null page");
SanityManager.ASSERT(page.isLatched(), "initPage returns unlatched page");
}
// allocate the page in the allocation page bit map
allocPage.addPage(this, pageNumber, ntt, userHandle);
if (useNTT)
{
// transfer the page latch from NTT to UT.
// after the page is unlatched by NTT, it is still protected from being
// found by almost everybody else because the alloc page is still latched and
// the alloc cache is invalidated.
// However (beetle 3942) it is possible for the page to be found by threads
// who specifically ask for this pagenumber (e.g. HeapPostCommit).
// We may find that such a thread has latched the page. We shouldn't wait
// for it because we have the alloc page latch, and this could cause
// deadlock (e.g. HeapPostCommit might call removePage and this would
// wait on the alloc page).
// We may instead find that we can latch the page, but that another thread
// has managed to get hold of it during the transfer and either deallocate
// it or otherwise change it (add rows, delete rows etc.)
// Since this doesn't happen very often, we retry in these 2 cases
// (we give up the alloc page and page and we start this method from scratch).
// If the lock manager were changed to allow latches to be transferred between
// transactions, wouldn't need to unlatch to do the transfer, and would avoid
// having to retry in these cases (beetle 4011).
page.unlatch();
page = null;
// need to find it in the cache again since unlatch also unkept the
// page from the cache
page = (BasePage)pageCache.find(pkey);
page = latchPage(userHandle, page, false /* don't wait, it might deadlock */);
if (page == null ||
// recordCount will only return true if there are no rows
// (including deleted rows)
page.recordCount() != 0 ||
page.getPageStatus() != BasePage.VALID_PAGE)
{
retry = true;
if (page != null)
{
page.unlatch();
page = null;
}
allocPage.unlatch();
allocPage = null;
}
}
// if ntt is null, no need to transfer. Page is latched by user
// transaction already. Will be no need to retry.
// the alloc page is unlatched in the finally block.
}
while (retry == true);
// At this point, should have a page suitable for returning
if (SanityManager.DEBUG)
SanityManager.ASSERT(page.isLatched());
}
catch (StandardException se)
{
if (page != null)
page.unlatch();
page = null;
throw se; // rethrow error
}
finally
{
if (!useNTT && allocPage != null)
{
allocPage.unlatch();
allocPage = null;
}
// NTT is committed by the caller
}
if (SanityManager.DEBUG)
SanityManager.ASSERT(page.isLatched());
// if bulkIncreaseContainerSize is set, that means this newPage call
// may have greatly expanded the container size due to preallocation.
// Regardless of how many page it actually created, reset preAllocSize
// to the default so we won't attempt to always preallocate 1000 pages
// at a time in the future.
if (bulkIncreaseContainerSize)
{
bulkIncreaseContainerSize = false;
PreAllocSize = DEFAULT_PRE_ALLOC_SIZE;
}
if (!isOverflow && page != null)
setLastInsertedPage(pageNumber);
// increase estimated page count - without any synchronization or
// logging, this is an estimate only
if (estimatedPageCount >= 0)
estimatedPageCount++;
if (!this.identity.equals(page.getPageId().getContainerId())) {
if (SanityManager.DEBUG) {
SanityManager.THROWASSERT("just created a new page from a different container"
+ "\n this.identity = " + this.identity
+ "\n page.getPageId().getContainerId() = " + page.getPageId().getContainerId()
+ "\n userHandle is: " + userHandle
+ "\n allocHandle is: " + allocHandle
+ "\n this container is: " + this);
}
throw StandardException.newException(
SQLState.DATA_DIFFERENT_CONTAINER,
this.identity, page.getPageId().getContainerId());
}
return page; // return the newly added page
}