| /* |
| |
| Derby - Class org.apache.derby.impl.store.raw.data.AllocPage |
| |
| Licensed to the Apache Software Foundation (ASF) under one or more |
| contributor license agreements. See the NOTICE file distributed with |
| this work for additional information regarding copyright ownership. |
| The ASF licenses this file to you under the Apache License, Version 2.0 |
| (the "License"); you may not use this file except in compliance with |
| the License. You may obtain a copy of the License at |
| |
| http://www.apache.org/licenses/LICENSE-2.0 |
| |
| Unless required by applicable law or agreed to in writing, software |
| distributed under the License is distributed on an "AS IS" BASIS, |
| WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| See the License for the specific language governing permissions and |
| limitations under the License. |
| |
| */ |
| |
| package org.apache.derby.impl.store.raw.data; |
| |
| import org.apache.derby.shared.common.sanity.SanityManager; |
| |
| import org.apache.derby.iapi.reference.SQLState; |
| |
| import org.apache.derby.iapi.services.io.TypedFormat; |
| import org.apache.derby.iapi.services.io.FormatIdUtil; |
| import org.apache.derby.iapi.services.io.StoredFormatIds; |
| |
| import org.apache.derby.iapi.error.StandardException; |
| |
| import org.apache.derby.iapi.store.raw.ContainerHandle; |
| import org.apache.derby.iapi.store.raw.Loggable; |
| import org.apache.derby.iapi.store.raw.PageKey; |
| import org.apache.derby.iapi.store.raw.PageTimeStamp; |
| import org.apache.derby.iapi.store.raw.RawStoreFactory; |
| |
| import org.apache.derby.iapi.store.raw.log.LogInstant; |
| import org.apache.derby.iapi.store.raw.xact.RawTransaction; |
| |
| import org.apache.derby.impl.store.raw.data.BaseContainerHandle; |
| import org.apache.derby.impl.store.raw.data.BasePage; |
| import org.apache.derby.impl.store.raw.data.PageVersion; |
| |
| import java.io.IOException; |
| |
| import java.io.ObjectOutput; |
| import java.io.ObjectInput; |
| |
| import org.apache.derby.iapi.services.io.ArrayInputStream; |
| |
| /** |
| An allocation page of the file container. |
| <P> |
| This class extends a normal Stored page, with the exception that a hunk of |
| space may be 'borrowed' by the file container to store the file header. |
| <P> |
| The borrowed space is not visible to the alloc page even though it is |
| present in the page data array. It is accessed directly by the |
| FileContainer. Any change made to the borrowed space is not managed or |
| seen by the allocation page. |
| <P> |
| The reason for having this borrowed space is so that the container header |
| does not need to have a page of its own. |
| |
| <P><B>Page Format</B><BR> |
| An allocation page extends a stored page, the on disk format is different |
| from a stored page in that N bytes are 'borrowed' by the container and the |
| page header of an allocation page will be slightly bigger than a normal |
| stored page. This N bytes are stored between the page header and the |
| record space. |
| <P> |
| The reason why this N bytes can't simply be a row is because it needs to be |
| statically accessible by the container object to avoid a chicken and egg |
| problem of the container object needing to instantiate an alloc page object |
| before it can be objectified, and an alloc page object needing to |
| instantiate a container object before it can be objectified. So this N |
| bytes must be stored outside of the normal record interface yet it must be |
| settable because only the first alloc page has this borrowed space. Other |
| (non-first) alloc page have N == 0. |
| |
| <PRE> |
| [ borrowed ] |
| +----------+-------------+---+---------+-------------------+-------------+--------+ |
| | FormatId | page header | N | N bytes | alloc extend rows | slot offset |checksum| |
| +----------+-------------+---+---------+-------------------+-------------+--------+ |
| </PRE> |
| |
| N is a byte that indicates the size of the borrowed space. Once an alloc |
| page is initialized, the value of N cannot change. |
| <P> |
| The maximum space that can be borrowed by the container is 256 bytes. |
| <P> |
| The allocation page are of the same page size as any other pages in the |
| container. The first allocation page of the FileContainer starts at the |
| first physical byte of the container. Subsequent allocation pages are |
| chained via the nextAllocPageOffset. Each allocation page is expected to |
| manage at least 1000 user pages (for 1K page size) so this chaining may not |
| be a severe performance hit. The logical -> physical mapping of an |
| allocation page is stored in the previous allocation page. The container |
| object will need to maintain this mapping. |
| <P> |
| The following fields are stored in the page header |
| <PRE> |
| @derby.formatId RAW_STORE_ALLOC_PAGE |
| @derby.purpose manage page allocation |
| @derby.upgrade |
| @derby.diskLayout |
| FormatId(int) |
| StoredPageHeader see StoredPage |
| nextAllocPageNubmer(long) the next allocation page's number |
| nextAllocPageOffset(long) the file offset of the next allocation page |
| reserved1(long) reserved for future usage |
| reserved2(long) reserved for future usage |
| reserved3(long) reserved for future usage |
| reserved4(long) reserved for future usage |
| N(byte) the size of the borrowed container info |
| containerInfo(byte[N]) the content of the borrowed container info |
| AllocExtent the one and only extent on this alloc page |
| |
| @derby.endFormat |
| </PRE> |
| |
| <P> |
| The allocation page contains allocation extent rows. In this first cut |
| implementation, there is only 1 allocation extent row per allocation page. |
| <P> |
| The allocation extent row is an externalizable object and is directly |
| written on to the page by the alloc page. In other words, it will not be |
| converted in to a storeableRow. This is to cut down overhead, enhance |
| performance and gives more control of the size and layout of the allocation |
| extent row to the alloc page. |
| <P> |
| <HR WIDTH="100%"> |
| <BR> DETAIL implmentation notes <BR> |
| <HR WIDTH="100%"> |
| <P> |
| Create Container - an embryonic allocation page is formatted on disk by a |
| spcial static function to avoid instantiating a full AllocPage object. |
| This embryonic allocation has enough information that it can find the |
| file header and not much else. Then the allocation page is perperly |
| initiated by creating the first extent. |
| <P> |
| Open Container - A static AllocPage method will be used to read off the |
| container information directly from disk. Even if |
| the first alloc page (page 0) is already in the page cache, it will not be |
| used because cleaning the alloc page will introduce a deadlock if the |
| container is not in the container cache. Long term, the first alloc page |
| should probably live in the container cache rather than in the page cache. |
| <P> |
| Get Page - The first alloc page (page 0) will be read into the page cache. |
| Continue to follow the alloc page chain until the alloc page that manages |
| the specified page is found. From the alloc page, the physical offset of |
| the specified page is located. |
| <P> |
| Cleaning alloc page - the alloc page is written out the same way any page |
| is written out. The container object will provide a call back to the alloc |
| page to write the current version of the container object back into the |
| borrowed space before the alloc page itself is written out. |
| <P> |
| Cleaning the container object - get the the first alloc page, dirty it and |
| clean it (which will cause it to call the container object to write itself |
| out into the borrowed space). The versioning of the container is |
| independent of the versioning of the alloc page. The container version is |
| stored inside the borrowed space and is opaque to the alloc page. |
| <P> |
| For the fields in an allocation extent row |
| |
| @see AllocExtent |
| */ |
| |
| |
| public class AllocPage extends StoredPage |
| { |
| /* |
| * typed format |
| */ |
| public static final int FORMAT_NUMBER = StoredFormatIds.RAW_STORE_ALLOC_PAGE; |
| // format Id must fit in 4 bytes |
| |
| /** |
| Return my format identifier. |
| */ |
| public int getTypeFormatId() { |
| return StoredFormatIds.RAW_STORE_ALLOC_PAGE; |
| } |
| |
| /***************************************************************** |
| * alloc page header |
| *****************************************************************/ |
| private long nextAllocPageNumber; // if isLast, nextAllocPageNumber == INVALID_PAGE_NUMBER |
| private long nextAllocPageOffset; |
| private long reserved1; |
| private long reserved2; |
| private long reserved3; |
| private long reserved4; |
| |
| private AllocExtent extent; |
| |
| private int borrowedSpace; |
| |
| /***************************************************************** |
| * constants |
| *****************************************************************/ |
| |
| /* |
| * allocation page header |
| * 8 bytes long next alloc page number |
| * 8 bytes long next alloc page physical offset |
| * 8 bytes long reserved1 |
| * 8 bytes long reserved2 |
| * 8 bytes long reserved3 |
| * 8 bytes long reserved4 |
| */ |
| protected static final int ALLOC_PAGE_HEADER_OFFSET = |
| StoredPage.PAGE_HEADER_OFFSET + StoredPage.PAGE_HEADER_SIZE; |
| |
| protected static final int ALLOC_PAGE_HEADER_SIZE = 8+8+(4*8); |
| |
| /* borrowed_SPACE_OFFSET is where the borrowed space len is kept */ |
| protected static final int BORROWED_SPACE_OFFSET = |
| ALLOC_PAGE_HEADER_OFFSET + ALLOC_PAGE_HEADER_SIZE; |
| |
| /* size of the borrowed space length */ |
| protected static final int BORROWED_SPACE_LEN = 1; // 1 byte to store the containerInfo length |
| |
| /* |
| * BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN is the beginning offset of |
| * the borrowed space |
| */ |
| |
| /* |
| * the entire borrowed space must live within MAX_BORROWED_SPACE of the |
| * alloc page |
| */ |
| protected static final int MAX_BORROWED_SPACE = |
| RawStoreFactory.PAGE_SIZE_MINIMUM / 5; // cannot take more then 1/5 of the page |
| |
| public AllocPage() |
| { |
| super(); |
| } |
| |
| /* |
| * overwriting StoredPage methods |
| */ |
| |
| protected int getMaxFreeSpace() { |
| |
| // the maximum free space is reduced by the allocation page header the |
| // size of the borrowed space. In all allocation page except the first |
| // one, there is no borrowed space and this is indeed the max free |
| // space. In the first allocation page, need to further subtract |
| // the borrowed space |
| |
| return super.getMaxFreeSpace() - ALLOC_PAGE_HEADER_SIZE - |
| BORROWED_SPACE_LEN - borrowedSpace; |
| } |
| |
| |
| /* |
| * Methods of cachedPage - create, read and write up a page |
| * Overwriting StoredPage's CachedPage methods |
| */ |
| |
| /** |
| * Create a new alloc page. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void createPage(PageKey newIdentity, PageCreationArgs args) |
| throws StandardException |
| { |
| |
| // We need to set borrowedSpace before we call super.createPage() so |
| // that totalSpace is initialized correctly (DERBY-3116). |
| borrowedSpace = args.containerInfoSize; |
| |
| super.createPage(newIdentity, args); |
| |
| if (SanityManager.DEBUG) |
| { |
| // MAX_BORROWED_SPACE can't be bigger than what can be represented in 1 byte space |
| SanityManager.ASSERT(MAX_BORROWED_SPACE <= 255); |
| |
| if (!(borrowedSpace + BORROWED_SPACE_LEN + BORROWED_SPACE_OFFSET |
| < MAX_BORROWED_SPACE)) |
| { |
| SanityManager.THROWASSERT( |
| "borrowedSpace too big = " + borrowedSpace); |
| } |
| SanityManager.ASSERT(pageData != null); |
| } |
| pageData[BORROWED_SPACE_OFFSET] = (byte)borrowedSpace; |
| |
| // remember that the borrowed space have been wiped out now, it |
| // needs to be put back when the page is written out. |
| // blot out borrowed space before checksum is verified |
| if (borrowedSpace > 0) |
| { |
| clearSection(BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN, borrowedSpace); |
| } |
| |
| // init the rest of the header and the allocation extent |
| nextAllocPageNumber = ContainerHandle.INVALID_PAGE_NUMBER; |
| nextAllocPageOffset = 0; |
| reserved1 = reserved2 = reserved3 = reserved4 = 0; |
| |
| // calculate how much space we have left for the extent map |
| if (SanityManager.DEBUG) { |
| // totalSpace used to be incorrect (DERBY-3116), so check it here |
| int maxFreeSpace = getMaxFreeSpace(); |
| SanityManager.ASSERT(totalSpace == maxFreeSpace, |
| "totalSpace = " + totalSpace + |
| ", getMaxFreeSpace() = " + maxFreeSpace); |
| } |
| |
| // the pages this extent is going to manage starts from pageNum+1 |
| // starting physical offset is pageSize*(pageNum+1) since we have |
| // no logical to physical mapping yet... |
| extent = createExtent(newIdentity.getPageNumber()+1, getPageSize(), |
| 0 /* pagesAlloced */, totalSpace); |
| } |
| |
| private AllocExtent createExtent(long pageNum, int pageSize, int pagesAlloced, int availspace) |
| { |
| int maxPages = AllocExtent.MAX_RANGE(availspace); |
| |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(maxPages > 8, "cannot manage > 8 pages"); |
| |
| |
| if (SanityManager.DEBUG) |
| { |
| if (SanityManager.DEBUG_ON(TEST_MULTIPLE_ALLOC_PAGE)) |
| { |
| maxPages = 2; // 2 pages per alloc page |
| } |
| } |
| |
| return new AllocExtent(pageNum*pageSize, // starting offset |
| pageNum, // extent start page number |
| pagesAlloced, // #pages already allocated |
| pageSize, // page size |
| maxPages); // max #pages to manage |
| } |
| |
| /** |
| Initialize in memory structure using the buffer in pageData |
| |
| @exception StandardException If the page cannot be read correctly, or is inconsistent. |
| */ |
| protected void initFromData(FileContainer myContainer, PageKey newIdentity) |
| throws StandardException |
| { |
| if (pageData.length < BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, newIdentity)); |
| } |
| |
| byte n = pageData[BORROWED_SPACE_OFFSET]; |
| borrowedSpace = (int)n; |
| |
| if (pageData.length < BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN + n) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, newIdentity)); |
| } |
| |
| // blot out borrowed space before checksum is verified |
| if (borrowedSpace > 0) |
| { |
| clearSection(BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN, borrowedSpace); |
| } |
| |
| super.initFromData(myContainer, newIdentity); |
| |
| try |
| { |
| // now init alloc page header fields |
| readAllocPageHeader(); |
| |
| // now init the allocation extent - read it from offset |
| int offset = BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN + borrowedSpace; |
| extent = readExtent(offset); |
| } |
| catch (IOException ioe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, newIdentity)); |
| } |
| catch (ClassNotFoundException cnfe) |
| { |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, cnfe, newIdentity)); |
| } |
| } |
| |
| /** |
| Write the page out |
| |
| @exception StandardException If the page cannot be written |
| */ |
| protected void writePage(PageKey identity) throws StandardException |
| { |
| try |
| { |
| updateAllocPageHeader(); // write out the next alloc page chain |
| |
| // blot out borrowed space before checksum is calculated - even |
| // though the page is first read in with borrowed space blotted |
| // out, whenever this page got cleaned the container will overlay |
| // the container info in the borrowed space. |
| int n = (int)pageData[BORROWED_SPACE_OFFSET]; |
| if (SanityManager.DEBUG) |
| { |
| if (n != borrowedSpace) |
| SanityManager.THROWASSERT( |
| "different borrowed space " + n + ", " + borrowedSpace); |
| } |
| if (n > 0) |
| { |
| clearSection(BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN, n); |
| } |
| |
| int offset = BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN + n; |
| writeExtent(offset); |
| } |
| catch (IOException ioe) |
| { |
| // i/o methods on the byte array have thrown an IOException |
| throw dataFactory.markCorrupt( |
| StandardException.newException( |
| SQLState.DATA_CORRUPT_PAGE, ioe, identity)); |
| } |
| |
| // let store page write out the rest and do the checksum |
| super.writePage(identity); |
| } |
| |
| |
| private void readAllocPageHeader() throws IOException |
| { |
| ArrayInputStream lrdi = rawDataIn; |
| lrdi.setPosition(ALLOC_PAGE_HEADER_OFFSET); |
| |
| nextAllocPageNumber = lrdi.readLong(); |
| nextAllocPageOffset = lrdi.readLong(); |
| reserved1 = lrdi.readLong(); |
| reserved2 = lrdi.readLong(); |
| reserved3 = lrdi.readLong(); |
| reserved4 = lrdi.readLong(); |
| } |
| |
| private void updateAllocPageHeader() throws IOException |
| { |
| // rawDataOut and logicalDataOut are defined by StoredPage |
| rawDataOut.setPosition(ALLOC_PAGE_HEADER_OFFSET); |
| logicalDataOut.writeLong(nextAllocPageNumber); |
| logicalDataOut.writeLong(nextAllocPageOffset); |
| logicalDataOut.writeLong(0); // reserved1 |
| logicalDataOut.writeLong(0); // reserved2 |
| logicalDataOut.writeLong(0); // reserved3 |
| logicalDataOut.writeLong(0); // reserved4 |
| } |
| |
| private AllocExtent readExtent(int offset) |
| throws IOException, ClassNotFoundException |
| { |
| ArrayInputStream lrdi = rawDataIn; |
| rawDataIn.setPosition(offset); |
| AllocExtent newExtent = new AllocExtent(); |
| newExtent.readExternal(lrdi); |
| |
| // in 1.3 or later, make sure the upgrade from before 1.3 is legal. |
| if (SanityManager.DEBUG) |
| { |
| int max_range = AllocExtent.MAX_RANGE(getMaxFreeSpace()); |
| long extent_start = newExtent.getFirstPagenum(); |
| long extent_end = newExtent.getExtentEnd(); |
| |
| // extent_start + max_range - 1 is the absolute last page this |
| // extent can hope to manage. See if it did the calculation |
| // correctly after upgrade. |
| |
| if ((extent_start+max_range-1) < extent_end) |
| { |
| SanityManager.THROWASSERT( |
| "extent range exceed what extent's space can handle "); |
| } |
| } |
| |
| return newExtent; |
| } |
| |
| private void writeExtent(int offset) throws IOException |
| { |
| // rawDataOut and logicalDataOut are defined by StoredPage |
| rawDataOut.setPosition(offset); |
| extent.writeExternal(logicalDataOut); |
| } |
| |
| /* |
| * borrowed space management |
| */ |
| |
| /** |
| Write the container information into the container information area. |
| |
| @param containerInfo the container information |
| |
| @param epage the allocation page data which may not be fully formed, |
| but is guarenteed to be big enough to cover the area inhibited by the |
| container info |
| |
| @param create if create, write out the length of the container info |
| also. Else check to make sure epage's original container info is of the |
| same length |
| |
| @exception StandardException standard Derby error policy |
| */ |
| public static void WriteContainerInfo(byte[] containerInfo, |
| byte[] epage, |
| boolean create) |
| throws StandardException |
| { |
| int N = (containerInfo == null) ? 0 : containerInfo.length; |
| |
| if (SanityManager.DEBUG) |
| { |
| if (create) |
| SanityManager.ASSERT( |
| containerInfo != null, "containerInfo is null"); |
| |
| SanityManager.ASSERT(epage != null, "page array is null"); |
| |
| if (!((containerInfo == null) || |
| ((containerInfo.length + BORROWED_SPACE_OFFSET + |
| BORROWED_SPACE_LEN) < epage.length))) |
| { |
| SanityManager.THROWASSERT( |
| "containerInfo too big for page array: " + |
| containerInfo.length); |
| } |
| |
| if (BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN + N >= |
| MAX_BORROWED_SPACE) |
| SanityManager.THROWASSERT( |
| "exceed max borrowable space: " + N); |
| } |
| |
| if ((N + BORROWED_SPACE_LEN + BORROWED_SPACE_OFFSET) > epage.length) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.THROWASSERT( |
| "exceed max borrowable space on page: " + N); |
| } |
| |
| if (create) |
| { |
| epage[BORROWED_SPACE_OFFSET] = (byte)N; |
| } |
| else |
| { |
| int oldN = (int)epage[BORROWED_SPACE_OFFSET]; |
| if (oldN != N) |
| { |
| throw StandardException.newException( |
| SQLState.DATA_CHANGING_CONTAINER_INFO, |
| oldN, |
| N); |
| } |
| } |
| |
| if (N != 0) |
| System.arraycopy(containerInfo, 0, epage, |
| BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN, |
| N); |
| } |
| |
| /** |
| Extract the container information from epage. |
| |
| @param containerInfo where to put the extracted information |
| |
| @param epage the allocation page which has the container information. |
| Epage may not be fully formed, but is guarenteed to be big enough to |
| cover the area inhibited by the container info |
| */ |
| public static void ReadContainerInfo(byte[] containerInfo, |
| byte[] epage) |
| throws StandardException |
| { |
| int N = (int)epage[BORROWED_SPACE_OFFSET]; |
| |
| if (SanityManager.DEBUG) |
| { |
| if (N != containerInfo.length) |
| SanityManager.THROWASSERT("N not what is expected : " + N); |
| |
| if (BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN + N |
| >= MAX_BORROWED_SPACE) |
| { |
| SanityManager.THROWASSERT("exceed max borrowable space: " + N); |
| } |
| } |
| |
| if (N != 0) |
| { |
| try { |
| System.arraycopy( |
| epage, BORROWED_SPACE_OFFSET+BORROWED_SPACE_LEN, |
| containerInfo, 0, N); |
| } catch (ArrayIndexOutOfBoundsException ioobe) { |
| throw StandardException.newException ( |
| SQLState.DATA_UNEXPECTED_EXCEPTION, |
| StandardException.newException ( |
| SQLState.UNABLE_TO_ARRAYCOPY, |
| ioobe, |
| epage.length, containerInfo.length, |
| MAX_BORROWED_SPACE, |
| BORROWED_SPACE_OFFSET, |
| BORROWED_SPACE_LEN, |
| BORROWED_SPACE_OFFSET + BORROWED_SPACE_LEN, |
| N, |
| org.apache.derby.iapi.util.StringUtil.hexDump( |
| epage) |
| ) |
| ); |
| } |
| } |
| } |
| |
| |
| /* |
| * specific methods to AllocPage |
| */ |
| |
| /** |
| Return the next free page number after given page number |
| */ |
| public long nextFreePageNumber(long pnum) |
| { |
| return extent.getFreePageNumber(pnum); |
| } |
| |
| |
| /** |
| Add a page which is managed by this alloc page. |
| Return the page number of the newly added page. |
| |
| <BR> MT - thread aware (latched) |
| |
| @param mycontainer (future) allows the alloc page to call back to the |
| container to grow the container by creating and syncing multiple |
| pages at once |
| @param ntt the nested top action that is the allocation transaction. |
| NTT will comit before the user transaction |
| @param userHandle the container handle that is opened by the user |
| transaction. Use the userHandle to latch the new page so that |
| it may remain latched after NTT is committed so the user |
| transaction can guarentee to have an empty page |
| |
| @exception StandardException If the page cannot be added |
| */ |
| public void addPage(FileContainer mycontainer, long newPageNumber, |
| RawTransaction ntt, BaseContainerHandle userHandle) throws StandardException |
| { |
| // RESOLVED: |
| // |
| // to prevent allocating a free page before the freeing transaction has |
| // commit, need to grab the DEALLOCATE_PROTECTION_HANDLE |
| // the lock probably should be gotten in FileContainer |
| // and not here |
| |
| // page allocation is logged under the nested top action |
| owner.getAllocationActionSet(). |
| actionAllocatePage( |
| ntt, this, newPageNumber, |
| AllocExtent.ALLOCATED_PAGE, AllocExtent.FREE_PAGE); |
| } |
| |
| |
| /* |
| Deallocate page |
| */ |
| public void deallocatePage(BaseContainerHandle userHandle, long pageNumber) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(isLatched()); |
| } |
| |
| // RESOLVED: |
| // |
| // to prevent this page from being freed before this transaction commits, |
| // need to grab the DEALLOCATE_PROTECTION_HANDLE lock on the |
| // deallocated page |
| // the lock probably should be gotten in FileContainer |
| // and not here |
| |
| owner.getAllocationActionSet(). |
| actionAllocatePage(userHandle.getTransaction(), |
| this, pageNumber, AllocExtent.DEALLOCATED_PAGE, |
| AllocExtent.ALLOCATED_PAGE); |
| } |
| |
| /* |
| * update unfilled page information |
| * We will be using inputExtent's unfilledPage bitmap as the new bitmap, so |
| * caller of this routine need to not touch the bitmap after this call (in |
| * other words, call this ONLY in allocationCache invalidate and throw away |
| * the reference to the bitImpl) |
| */ |
| protected void updateUnfilledPageInfo(AllocExtent inputExtent) |
| { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(isLatched()); |
| } |
| |
| // update the unfilled page bit map unlogged - it is just a hint, not |
| // worth logging it - don't dirty the page either, since we didn't log |
| // it. It will be dirtied soon enough by addPage or deallocPage, |
| // that is the only reasons why we are invalidataing the |
| // allocation cache and updating the unfilled page info. |
| // If we dirty the page, the BI will be copied to the side log |
| extent.updateUnfilledPageInfo(inputExtent); |
| |
| } |
| |
| public boolean canAddFreePage(long lastAllocatedPage) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched()); |
| |
| if (extent.isRetired()) |
| return false; |
| |
| // if we want to try allocating not from the beginning of the bit map |
| // and this alloc page is before that point and this is not the last |
| // alloc page, then skip over this alloc page |
| if (lastAllocatedPage != ContainerHandle.INVALID_PAGE_NUMBER && |
| extent.getLastPagenum() <= lastAllocatedPage && |
| !isLast()) |
| return false; |
| |
| // Else we either want to start examining from this alloc page, or this |
| // is the last page, see if we can add a page. |
| return extent.canAddFreePage(lastAllocatedPage); |
| } |
| |
| public long getNextAllocPageOffset() |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT( |
| !isLast(), "next alloc page not present for last alloc page"); |
| |
| SanityManager.ASSERT(isLatched()); |
| } |
| |
| return nextAllocPageOffset; |
| } |
| |
| public void chainNewAllocPage(BaseContainerHandle allocHandle, |
| long newAllocPageNum, long newAllocPageOffset) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched()); |
| if (SanityManager.DEBUG_ON(FileContainer.SPACE_TRACE)) |
| SanityManager.DEBUG(FileContainer.SPACE_TRACE, |
| "chaining new alloc page " + |
| newAllocPageNum + " to " + |
| getPageNumber()); |
| } |
| |
| owner.getAllocationActionSet(). |
| actionChainAllocPage(allocHandle.getTransaction(), |
| this, newAllocPageNum, newAllocPageOffset); |
| } |
| |
| public long getNextAllocPageNumber() |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched()); |
| SanityManager.ASSERT( |
| !isLast(), "next alloc page not present for last alloc page"); |
| } |
| return nextAllocPageNumber; |
| } |
| |
| public boolean isLast() |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched()); |
| return nextAllocPageNumber == ContainerHandle.INVALID_PAGE_NUMBER; |
| } |
| |
| /* |
| * get the last pagenumber currently managed by this alloc page |
| */ |
| public long getLastPagenum() |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched()); |
| |
| return extent.getLastPagenum(); |
| } |
| |
| /* |
| * get the largest page number this alloc page can manage. |
| * This is the different from the last pagenumber currently managed by this |
| * alloc page unless the alloc page is full and all the pages have been |
| * allocated |
| */ |
| public long getMaxPagenum() |
| { |
| return extent.getExtentEnd(); |
| } |
| |
| /* |
| * get the last preallocated pagenumber managed by this alloc page |
| */ |
| protected long getLastPreallocPagenum() |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched()); |
| |
| return extent.getLastPreallocPagenum(); |
| } |
| |
| |
| protected int getPageStatus(long pageNumber) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched()); |
| |
| return extent.getPageStatus(pageNumber); |
| } |
| |
| |
| /** |
| Do the actual page allocation/deallocation/ree underneath a log operation. |
| Change the page status to new status |
| |
| @exception StandardException If the page cannot be allocated |
| */ |
| protected void setPageStatus(LogInstant instant, long pageNumber, int newStatus) throws StandardException |
| { |
| if (SanityManager.DEBUG) { |
| SanityManager.ASSERT(isLatched(), "page is not latched"); |
| SanityManager.ASSERT(extent != null, "extent is null"); |
| } |
| |
| logAction(instant); |
| |
| switch(newStatus) |
| { |
| case AllocExtent.ALLOCATED_PAGE: |
| extent.allocPage(pageNumber); |
| break; |
| case AllocExtent.DEALLOCATED_PAGE: |
| extent.deallocPage(pageNumber); |
| break; |
| case AllocExtent.FREE_PAGE: |
| extent.deallocPage(pageNumber); |
| break; |
| } |
| } |
| |
| /** |
| Chain the next page number and offset underneath a log record |
| |
| @exception StandardException Standard Derby error policy |
| */ |
| protected void chainNextAllocPage(LogInstant instant, |
| long newAllocPageNum, |
| long newAllocPageOffset) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched(), "page is not latched"); |
| |
| logAction(instant); |
| |
| nextAllocPageNumber = newAllocPageNum; |
| nextAllocPageOffset = newAllocPageOffset; |
| } |
| |
| /** |
| * Compress free pages. |
| * <p> |
| * Compress the free pages at the end of the range maintained by |
| * this allocation page. All pages being compressed should be FREE. |
| * Only pages in the last allocation page can be compressed. |
| * <p> |
| * |
| * @param instant log address for this operation. |
| * @param new_highest_page The new highest page on this allocation |
| * page. The number is the offset of the page |
| * in the array of pages maintained by this |
| * allocation page, for instance a value of 0 |
| * indicates all page except the first one are |
| * to be truncated. If all pages are |
| * truncated then the offset is set to -1. |
| * @param num_pages_truncated The number of allocated pages in this |
| * allocation page prior to the truncate. |
| * Note that all pages from NewHighestPage+1 |
| * through newHighestPage+num_pages_truncated |
| * should be FREE. |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected void compressSpace( |
| LogInstant instant, |
| int new_highest_page, |
| int num_pages_truncated) |
| throws StandardException |
| { |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched(), "page is not latched"); |
| SanityManager.ASSERT(isLast(), "compress on non last alloc page."); |
| //Derby-606. newHighestPage = -1, is a valid case as it means that |
| //all the pages in this extent can be compressed. |
| SanityManager.ASSERT(new_highest_page >= -1, "negative new high page."); |
| } |
| |
| logAction(instant); |
| |
| extent.compressPages(new_highest_page, num_pages_truncated); |
| } |
| |
| /** |
| * Handle undo of compress space operation. |
| **/ |
| protected void undoCompressSpace( |
| LogInstant instant, |
| int new_highest_page, |
| int num_pages_truncated) |
| throws StandardException |
| { |
| logAction(instant); |
| |
| extent.undoCompressPages(new_highest_page, num_pages_truncated); |
| |
| } |
| |
| public String toString() |
| { |
| if (SanityManager.DEBUG) |
| { |
| String str = |
| "*** Alloc page ***\n" + |
| "nextAllocPageNumber = " + nextAllocPageNumber + |
| "\nnextAllocPageOffset = " + nextAllocPageOffset + |
| "\nreserved1 = " + reserved1 + |
| "\nreserved2 = " + reserved2 + |
| "\nreserved3 = " + reserved3 + |
| "\nreserved4 = " + reserved4 + |
| "\nborrowedSpace = " + borrowedSpace + |
| "\nextent = " + extent.toDebugString() + "\n" + |
| super.toString(); |
| return str; |
| } |
| else |
| { |
| return null; |
| } |
| } |
| |
| |
| /** |
| Return a copy of the allocExtent to be cached by the container. |
| the container must take care to maintain its coherency by |
| invalidating the cache before any update. |
| */ |
| protected AllocExtent getAllocExtent() |
| { |
| return extent; |
| |
| // return new AllocExtent(extent); |
| } |
| |
| /** |
| Preallocate user page if needed. |
| |
| @param myContainer the container object |
| @param preAllocThreshold start preallocating after this threshold |
| @param preAllocSize preallocate this number of pages |
| */ |
| protected void preAllocatePage(FileContainer myContainer, |
| int preAllocThreshold, |
| int preAllocSize) |
| { |
| if (SanityManager.DEBUG) |
| SanityManager.ASSERT(isLatched(), "page is not latched"); |
| |
| long lastPreallocatedPagenum = extent.getLastPreallocPagenum(); |
| |
| if (lastPreallocatedPagenum < preAllocThreshold) |
| return; |
| |
| // don't pre-allocate more than we the extent can handle - this is |
| // because if I preallocate the next alloc page as a stored page, |
| // that's going to be problem when we try to get it as an alloc page |
| // later. We don't handle changing from a store page type to an alloc |
| // page type on disk very well. |
| if (extent.getExtentEnd() < (lastPreallocatedPagenum+preAllocSize)) |
| preAllocSize = (int)(extent.getExtentEnd() - lastPreallocatedPagenum); |
| |
| if (preAllocSize <= 0) |
| return; |
| |
| // pre-allocate - only a container knows how to write pages |
| // preAllocSize may exceed what this allocation page can really |
| // handle, but no harm done. The next allocation page will benefit |
| // from the work we have done... |
| int n = myContainer.preAllocate(lastPreallocatedPagenum, preAllocSize); |
| |
| if (n > 0) // successfully preallocated some pages |
| { |
| // this is purely a performance issue during runtime. During |
| // recovery, any page that is actually initialized will have its |
| // own initPage log record. Update extent's preAllocpageNumber |
| // unlogged. |
| // |
| // We could have logged a redo-only log record, but we are counting |
| // on myContainer.preAllocate to do the right thing if we recovered |
| // and have out of date preallocate information. A reason why |
| // logging this is undesirable is that the alloc page may think the |
| // preallocation happened, but the container may actually choose to |
| // lie about it - if it thinks there is no advantage in actually |
| // doing the I/O now. So best to leave it alone. |
| extent.setLastPreallocPagenum(lastPreallocatedPagenum + n); |
| |
| // don't dirty the page - the new preAlloc page number is only set |
| // in memory. A page should only get 'dirtied' by a log operation, |
| // we are cheating here. Same with updating the extentStatus bit |
| // without logging. |
| } |
| |
| } |
| |
| /** |
| * compress out empty pages at end of container. |
| * <p> |
| * Call the extent to update the data structure make the bit map look |
| * like contiguous free pages at the end of the extent no longer exist. |
| * Similar to preallocate do the operation unlogged, need to force the |
| * change to the extent before actually removing the space from the |
| * file. |
| * <p> |
| * The sequence is: |
| * 1) update extent data structure |
| * 2) force extent changes to disk |
| * 3) truncate pages |
| * |
| * If the system crashes between 1 and 2 then no changes are on disk. |
| * If the system crashes between 2 and 3 then there are extra pages in |
| * the file that extent does not know about, this is the same case |
| * as preallocation which the code already handes. It will handle |
| * any set of pages from 0 to all of the intended pages being |
| * truncated. The next allocate looks at actual size of file as |
| * does the right thing. |
| * |
| * <p> |
| * MT - expect Container level X lock |
| * |
| * @exception StandardException Standard exception policy. |
| **/ |
| protected boolean compress( |
| RawTransaction ntt, |
| FileContainer myContainer) |
| throws StandardException |
| { |
| boolean all_pages_compressed = false; |
| |
| if (SanityManager.DEBUG) |
| { |
| SanityManager.ASSERT(isLatched(), "page is not latched"); |
| } |
| |
| int last_valid_page_bit = extent.compress(owner, ntt, this); |
| |
| if (last_valid_page_bit >= 0) |
| { |
| // a non-negative return means that pages can be returned to |
| // the operating system. |
| myContainer.truncatePages(extent.getPagenum(last_valid_page_bit)); |
| |
| if (last_valid_page_bit == 0) |
| { |
| // all pages of the extent have been returned to OS. |
| all_pages_compressed = true; |
| } |
| } |
| |
| return(all_pages_compressed); |
| } |
| |
| /********************************************************************* |
| * Extent Testing |
| * |
| * Use these strings to simulate error conditions for |
| * testing purposes. |
| * |
| *********************************************************************/ |
| public static final String TEST_MULTIPLE_ALLOC_PAGE = SanityManager.DEBUG ? "TEST_MULTI_ALLOC_PAGE" : null; |
| } |