/* * Copyright 2009-2016 Tilmann Zaeschke. All rights reserved. * * This file is part of ZooDB. * * ZooDB is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * ZooDB is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with ZooDB. If not, see <http://www.gnu.org/licenses/>. * * See the README and COPYING files for further information. */ package org.zoodb.internal.server; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.nio.file.Path; import java.util.List; import org.zoodb.internal.Node; import org.zoodb.internal.client.AbstractCache; import org.zoodb.internal.server.DiskIO.PAGE_TYPE; import org.zoodb.internal.server.index.FreeSpaceManager; import org.zoodb.internal.server.index.PagedOidIndex; import org.zoodb.internal.server.index.SchemaIndex; import org.zoodb.internal.util.DBLogger; import org.zoodb.internal.util.RWSemaphore; import org.zoodb.tools.ZooConfig; /** * * @author Tilmann Zaeschke */ class SessionManager { private static final long ID_FAULTY_PAGE = Long.MIN_VALUE; private final FreeSpaceManager fsm; private final StorageChannel file; private final Path path; private int count = 0; private final RootPage rootPage; private final int[] rootPages = new int[2]; private int rootPageID = 0; //hmm... private final SchemaIndex schemaIndex; private final PagedOidIndex oidIndex; private final StorageChannelOutput fileOut; private final RWSemaphore<DiskAccess> lock = new RWSemaphore<DiskAccess>(); private final TxManager txManager; public SessionManager(Path path) { this.path = path; fsm = new FreeSpaceManager(); file = createPageAccessFile(path, "rw", fsm); StorageChannelInput in = file.getReader(false); //read header in.seekPageForRead(PAGE_TYPE.DB_HEADER, 0); int fid = in.readInt(); if (fid != DiskIO.DB_FILE_TYPE_ID) { throw DBLogger.newFatal("This is not a ZooDB file (illegal file ID: " + fid + ")"); } int maj = in.readInt(); int min = in.readInt(); if (maj != DiskIO.DB_FILE_VERSION_MAJ) { throw DBLogger.newFatal("Illegal major file version: " + maj + "." + min + "; Software version: " + DiskIO.DB_FILE_VERSION_MAJ + "." + DiskIO.DB_FILE_VERSION_MIN); } if (min != DiskIO.DB_FILE_VERSION_MIN) { throw DBLogger.newFatal("Illegal minor file version: " + maj + "." + min + "; Software version: " + DiskIO.DB_FILE_VERSION_MAJ + "." + DiskIO.DB_FILE_VERSION_MIN); } int pageSize = in.readInt(); if (pageSize != ZooConfig.getFilePageSize()) { //TODO actually, in this case would should just close the file and reopen it with the //correct page size. throw DBLogger.newFatal("Incompatible page size: " + pageSize); } //main directory rootPage = new RootPage(); rootPages[0] = in.readInt(); rootPages[1] = in.readInt(); //check root pages //we have two root pages. They are used alternatingly. long r0 = checkRoot(in, rootPages[0]); long r1 = checkRoot(in, rootPages[1]); if (r0 > r1) { rootPageID = 0; } else { rootPageID = 1; } if (r0 == ID_FAULTY_PAGE && r1 == ID_FAULTY_PAGE) { String m = "Database is corrupted and cannot be recoverd. Please restore from backup."; DBLogger.severe(m); throw DBLogger.newFatal(m); } //readMainPage in.seekPageForRead(PAGE_TYPE.ROOT_PAGE, rootPages[rootPageID]); //read main directory (page IDs) //tx ID long txId = in.readLong(); this.txManager = new TxManager(txId); //User table int userPage = in.readInt(); //OID table int oidPage1 = in.readInt(); //schemata int schemaPage1 = in.readInt(); //indices int indexPage = in.readInt(); //free space index int freeSpacePage = in.readInt(); //page count (required for recovery of crashed databases) int pageCount = in.readInt(); //last used oid - this may be larger than the last stored OID if the last object was deleted long lastUsedOid = in.readLong(); //OIDs oidIndex = new PagedOidIndex(file, oidPage1, lastUsedOid); //dir for schemata schemaIndex = new SchemaIndex(file, schemaPage1, false); //free space index fsm.initBackingIndexLoad(file, freeSpacePage, pageCount); rootPage.set(userPage, oidPage1, schemaPage1, indexPage, freeSpacePage, pageCount); fileOut = file.getWriter(false); } /** * Writes the main page. * @param pageCount */ private void writeMainPage(int userPage, int oidPage, int schemaPage, int indexPage, int freeSpaceIndexPage, int pageCount, StorageChannelOutput out, long lastUsedOid, long txId) { rootPageID = (rootPageID + 1) % 2; out.seekPageForWrite(PAGE_TYPE.ROOT_PAGE, rootPages[rootPageID]); //********** // When updating this, also update checkRoot()! //********** //tx ID out.writeLong(txId); //User table out.writeInt(userPage); //OID table out.writeInt(oidPage); //schemata out.writeInt(schemaPage); //indices out.writeInt(indexPage); //free space index out.writeInt(freeSpaceIndexPage); //page count out.writeInt(pageCount); //last used oid out.writeLong(lastUsedOid); //tx ID. Writing the tx ID twice should ensure that the data between the two has been //written correctly. out.writeLong(txId); } private long checkRoot(StorageChannelInput in, int pageId) { in.seekPageForRead(PAGE_TYPE.ROOT_PAGE, pageId); long txID1 = in.readLong(); //skip the data for (int i = 0; i < 8; i++) { in.readInt(); } long txID2 = in.readLong(); if (txID1 == txID2) { return txID1; } DBLogger.severe("Main page is faulty: " + pageId + ". Will recover from previous " + "page version."); return ID_FAULTY_PAGE; } public DiskAccessOneFile createSession(Node node, AbstractCache cache) { //Create the session first, because it locks the SessionManager! DiskAccessOneFile session = new DiskAccessOneFile(node, cache, this); count++; if (count > 1) { txManager.setMultiSession(); } return session; } private static StorageChannel createPageAccessFile(Path path, String options, FreeSpaceManager fsm) { String dbPath = path.toString(); try { Class<?> cls = Class.forName(ZooConfig.getFileProcessor()); Constructor<?> con = cls.getConstructor(String.class, String.class, Integer.TYPE, FreeSpaceManager.class); StorageChannel paf = (StorageChannel) con.newInstance(dbPath, options, ZooConfig.getFilePageSize(), fsm); return paf; } catch (Exception e) { if (e instanceof InvocationTargetException) { Throwable t2 = e.getCause(); if (DBLogger.USER_EXCEPTION.isAssignableFrom(t2.getClass())) { throw (RuntimeException)t2; } } throw DBLogger.newFatal("path=" + dbPath, e); } } void close() { count--; if (count == 0) { DBLogger.debugPrintln(1, "Closing DB file: " + path); fsm.getFile().close(); SessionFactory.removeSession(this); } } Path getPath() { return path; } FreeSpaceManager getFsm() { return fsm; } StorageChannel getFile() { return file; } void commitInfrastructure(int oidPage, int schemaPage1, long lastUsedOid, long txId) { int userPage = rootPage.getUserPage(); //not updated currently int indexPage = rootPage.getIndexPage(); //TODO remove this? //This needs to be written last, because it is updated by other write methods which add //new pages to the FSM. int freePage = fsm.write(); int pageCount = fsm.getPageCount(); if (!rootPage.isDirty(userPage, oidPage, schemaPage1, indexPage, freePage)) { return; } rootPage.set(userPage, oidPage, schemaPage1, indexPage, freePage, pageCount); // flush the file including all splits file.flush(); writeMainPage(userPage, oidPage, schemaPage1, indexPage, freePage, pageCount, fileOut, lastUsedOid, txId); //Second flush to update root pages. file.flush(); //tell FSM that new free pages can now be reused. fsm.notifyCommit(); //refresh pos-index iterators, if any exist. //TODO not necessary at the moment..., all tests (e.g. Test_62) pass anyway. //refresh() is performed through the session object. //schemaIndex.refreshIterators(); txManager.deRegisterTx(txId); } RootPage getRootPage() { return rootPage; } long getNextTxId() { return txManager.getNextTxId(); } SchemaIndex getSchemaIndex() { return schemaIndex; } PagedOidIndex getOidIndex() { return oidIndex; } RWSemaphore<DiskAccess> getLock() { return lock; } /** * @param isTrialRun Indicate whether the tx history should be updated or not. In trial runs, * the history is never updated. * @return A list of conflicting objects or {@code null} if there are no conflicts */ List<Long> checkForConflicts(long txId, TxContext txContext, boolean isTrialRun) { return txManager.addUpdates(txId, txContext, isTrialRun); } TxManager getTxManager() { return txManager; } public boolean isLocked() { return lock.isLocked(); } }