protected BasePage newPage()

in java/org.apache.derby.engine/org/apache/derby/impl/store/raw/data/FileContainer.java [1564:2041]


	protected BasePage newPage(BaseContainerHandle userHandle,
							   RawTransaction ntt,
							   BaseContainerHandle allocHandle,
							   boolean isOverflow) 
		 throws StandardException 
	{
		// NOTE: we are single threaded thru this method, see MT comment

		boolean useNTT = (ntt != null);

		// if ntt is null, use user transaction
		if (!useNTT)
			ntt = userHandle.getTransaction();

		long lastPage;			// last allocated page
		long lastPreallocPage;	// last pre-allcated page
        long pageNumber =
            ContainerHandle.INVALID_PAGE_NUMBER; // init to appease compiler
                                // the page number of the new page
		PageKey pkey;			// the identity of the new page
		boolean reuse;			// if true, we are trying to reuse a page

		/* in case the page recommeded by allocPage is not committed yet, may
		/* need to retry a couple of times */
		boolean retry;
		int numtries = 0;

        int maxTries = InterruptStatus.MAX_INTERRUPT_RETRIES;

        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 is 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 container until the alloc page is 
                     * unlatched.  Those who already have a page handle into 
                     * this container are 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.
                     */
                    try {
                        allocPage =
                            findAllocPageForAdd(allocHandle, ntt, startSearch);
                    } catch (InterruptDetectedException e) {
                        // Retry. We needed to back all the way up here in the
                        // case of the container having been closed due to an
                        // interrupt on another thread, since that thread's
                        // recovery needs the monitor to allocCache which we
                        // hold. We release it when we do "continue" below.
                        if (--maxTries > 0) {
                            // Clear firstAllocPageNumber, i.e. undo side
                            // effect of makeAllocPage, so retry will work
                            firstAllocPageNumber =
                                ContainerHandle.INVALID_PAGE_NUMBER;
                            retry = true;

                            // Wait a bit so recovery can take place before
                            // we re-grab monitor on "this" (which recovery
                            // needs) and retry writeRAFHeader.
                            try {
                                Thread.sleep(
                                    InterruptStatus.INTERRUPT_RETRY_SLEEP);
                            } catch (InterruptedException ee) {
                                // This thread received an interrupt as
                                // well, make a note.
                                InterruptStatus.setInterrupted();
                            }

                            continue;
                        } else {
                            throw StandardException.newException(
                                SQLState.FILE_IO_INTERRUPTED, e);
                        }
                    }


					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 
			    // 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

                PageCreationArgs createPageArgs = new PageCreationArgs(
                        StoredPage.FORMAT_NUMBER,
                        prealloced ? 0 : (noIO ? 0 : CachedPage.WRITE_SYNC),
                        pageSize,
                        spareSpace,
                        minimumRecordSize,
                        0 /* containerInfoSize - unused for StoredPage */);

			    // 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

                try
                {
			    page = initPage(allocHandle, pkey, createPageArgs, pageOffset,
				    			reuse, isOverflow);
                }
                catch (StandardException se)
                {
                    if (SanityManager.DEBUG) {
                        SanityManager.DEBUG_PRINT("FileContainer",
                            "got exception from initPage:"  +
                            "\nreuse = " + reuse +
                            "\nsyncFlag = " + createPageArgs.syncFlag +
                            "\nallocPage = " + allocPage
                            );
                    }
                    allocCache.dumpAllocationCache();

                    throw se;
                }

			    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 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 deallocated 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 (DERBY-2337).

				    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
	}