/** @param header array length is full page */ private void commitHeader(final long header, final int commitNumber) throws IOException { final PageArray array = mPageArray; p_longPutLE(header, I_MAGIC_NUMBER, MAGIC_NUMBER); p_intPutLE(header, I_PAGE_SIZE, array.pageSize()); p_intPutLE(header, I_COMMIT_NUMBER, commitNumber); // Durably write the new page store header before returning // from this method, to ensure that the manager doesn't start // returning uncommitted pages. This would prevent rollback // from working because the old pages would get overwritten. setHeaderChecksum(header); // Write multiple header copies in the page, in case special recovery is required. int dupCount = pageSize() / MINIMUM_PAGE_SIZE; for (int i = 1; i < dupCount; i++) { p_copy(header, 0, header, i * MINIMUM_PAGE_SIZE, MINIMUM_PAGE_SIZE); } // Ensure all writes are flushed before flushing the header. There's // otherwise no ordering guarantees. Metadata should also be flushed // first, because the header won't affect it. array.sync(true); mHeaderLatch.acquireExclusive(); try { array.writePage(commitNumber & 1, header); mCommitNumber = commitNumber; } finally { mHeaderLatch.releaseExclusive(); } // Final sync to ensure the header is durable. array.syncPage(commitNumber & 1); }
@Override public void readExtraCommitData(byte[] extra) throws IOException { try { mHeaderLatch.acquireShared(); try { readPartial(mCommitNumber & 1, I_EXTRA_DATA, extra, 0, extra.length); } finally { mHeaderLatch.releaseShared(); } } catch (Throwable e) { throw closeOnFailure(e); } }
/** @see _SnapshotPageArray#beginSnapshot */ Snapshot beginSnapshot(_LocalDatabase db) throws IOException { mHeaderLatch.acquireShared(); try { long pageCount, redoPos; long header = p_alloc(MINIMUM_PAGE_SIZE); try { mPageArray.readPage(mCommitNumber & 1, header, 0, MINIMUM_PAGE_SIZE); pageCount = _PageManager.readTotalPageCount(header, I_MANAGER_HEADER); redoPos = _LocalDatabase.readRedoPosition(header, I_EXTRA_DATA); } finally { p_delete(header); } return mPageArray.beginSnapshot(db, pageCount, redoPos); } finally { mHeaderLatch.releaseShared(); } }
@Override public void commit(boolean resume, long header, final CommitCallback callback) throws IOException { // Acquire a shared lock to prevent concurrent commits after callback has released // exclusive lock. CommitLock.Shared shared = mCommitLock.acquireShared(); try { mHeaderLatch.acquireShared(); final int commitNumber = mCommitNumber + 1; mHeaderLatch.releaseShared(); try { if (!resume) { mPageManager.commitStart(header, I_MANAGER_HEADER); } if (callback != null) { // Invoke the callback to ensure all dirty pages get written. callback.prepare(resume, header); } } catch (DatabaseException e) { if (e.isRecoverable()) { throw e; } else { throw closeOnFailure(e); } } try { commitHeader(header, commitNumber); mPageManager.commitEnd(header, I_MANAGER_HEADER); } catch (Throwable e) { throw closeOnFailure(e); } } finally { shared.release(); } }
private _DurablePageDb( final PageArray rawArray, final PageCache cache, final Crypto crypto, final boolean destroy) throws IOException, WrongPageSize { mCrypto = crypto; PageArray array = crypto == null ? rawArray : new CryptoPageArray(rawArray, crypto); mPageArray = new _SnapshotPageArray(array, rawArray, cache); mHeaderLatch = new Latch(); try { int pageSize = mPageArray.pageSize(); checkPageSize(pageSize); if (destroy || mPageArray.isEmpty()) { // Newly created file. mPageManager = new _PageManager(mPageArray); mCommitNumber = -1; // Commit twice to ensure both headers have valid data. long header = p_calloc(pageSize); try { mCommitLock.acquireExclusive(); try { commit(false, header, null); commit(false, header, null); } finally { mCommitLock.releaseExclusive(); } } finally { p_delete(header); } mPageArray.setPageCount(2); } else { // Opened an existing file. // Previous header commit operation might have been interrupted before final // header sync completed. Pages cannot be safely recycled without this. mPageArray.sync(false); long header0 = p_null(); long header1 = p_null(); try { final long header; final int commitNumber; findHeader: { int pageSize0; int commitNumber0, commitNumber1; CorruptDatabaseException ex0; try { header0 = readHeader(0); commitNumber0 = p_intGetLE(header0, I_COMMIT_NUMBER); pageSize0 = p_intGetLE(header0, I_PAGE_SIZE); ex0 = null; } catch (CorruptDatabaseException e) { header0 = p_null(); commitNumber0 = -1; pageSize0 = pageSize; ex0 = e; } if (pageSize0 != pageSize) { throw new WrongPageSize(pageSize, pageSize0); } try { header1 = readHeader(1); commitNumber1 = p_intGetLE(header1, I_COMMIT_NUMBER); } catch (CorruptDatabaseException e) { if (ex0 != null) { // File is completely unusable. throw ex0; } header = header0; commitNumber = commitNumber0; break findHeader; } int pageSize1 = p_intGetLE(header1, I_PAGE_SIZE); if (pageSize0 != pageSize1) { throw new CorruptDatabaseException( "Mismatched page sizes: " + pageSize0 + " != " + pageSize1); } if (header0 == p_null()) { header = header1; commitNumber = commitNumber1; } else { // Modulo comparison. int diff = commitNumber1 - commitNumber0; if (diff > 0) { header = header1; commitNumber = commitNumber1; } else if (diff < 0) { header = header0; commitNumber = commitNumber0; } else { throw new CorruptDatabaseException( "Both headers have same commit number: " + commitNumber0); } } } mHeaderLatch.acquireExclusive(); mCommitNumber = commitNumber; mHeaderLatch.releaseExclusive(); mPageManager = new _PageManager(mPageArray, header, I_MANAGER_HEADER); } finally { p_delete(header0); p_delete(header1); } } } catch (WrongPageSize e) { delete(); closeQuietly(null, this); throw e; } catch (Throwable e) { delete(); throw closeOnFailure(e); } }