/* * * 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.bookkeeper.bookie; import org.apache.bookkeeper.proto.BookieProtocol; import org.apache.bookkeeper.util.ZeroBuffer; import java.io.IOException; import java.nio.ByteBuffer; import java.util.concurrent.atomic.AtomicInteger; /** * This is a page in the LedgerCache. It holds the locations * (entrylogfile, offset) for entry ids. */ public class LedgerEntryPage { private final static int indexEntrySize = 8; private final int pageSize; private final int entriesPerPage; volatile private EntryKey entryKey = new EntryKey(-1, BookieProtocol.INVALID_ENTRY_ID); private final ByteBuffer page; volatile private boolean clean = true; private final AtomicInteger useCount = new AtomicInteger(); private final AtomicInteger version = new AtomicInteger(0); volatile private int last = -1; // Last update position private final LEPStateChangeCallback callback; public static int getIndexEntrySize() { return indexEntrySize; } public LedgerEntryPage(int pageSize, int entriesPerPage) { this(pageSize, entriesPerPage, null); } public LedgerEntryPage(int pageSize, int entriesPerPage, LEPStateChangeCallback callback) { this.pageSize = pageSize; this.entriesPerPage = entriesPerPage; page = ByteBuffer.allocateDirect(pageSize); this.callback = callback; if (null != this.callback) { callback.onResetInUse(this); } } @Override public String toString() { StringBuilder sb = new StringBuilder(); sb.append(getLedger()); sb.append('@'); sb.append(getFirstEntry()); sb.append(clean ? " clean " : " dirty "); sb.append(useCount.get()); return sb.toString(); } public void usePage() { int oldVal = useCount.getAndIncrement(); if ((0 == oldVal) && (null != callback)) { callback.onSetInUse(this); } } public void releasePage() { int newUseCount = useCount.decrementAndGet(); if (newUseCount < 0) { throw new IllegalStateException("Use count has gone below 0"); } if ((null != callback) && (newUseCount == 0)) { callback.onResetInUse(this); } } private void checkPage() { if (useCount.get() <= 0) { throw new IllegalStateException("Page not marked in use"); } } @Override public boolean equals(Object other) { if (other instanceof LedgerEntryPage) { LedgerEntryPage otherLEP = (LedgerEntryPage) other; return otherLEP.getLedger() == getLedger() && otherLEP.getFirstEntry() == getFirstEntry(); } else { return false; } } @Override public int hashCode() { return (int)getLedger() ^ (int)(getFirstEntry()); } void setClean(int versionOfCleaning) { this.clean = (versionOfCleaning == version.get()); if ((null != callback) && clean) { callback.onSetClean(this); } } boolean isClean() { return clean; } public void setOffset(long offset, int position) { checkPage(); page.putLong(position, offset); version.incrementAndGet(); if (last < position/getIndexEntrySize()) { last = position/getIndexEntrySize(); } this.clean = false; if (null != callback) { callback.onSetDirty(this); } } public long getOffset(int position) { checkPage(); return page.getLong(position); } public void zeroPage() { checkPage(); page.clear(); ZeroBuffer.put(page); last = -1; clean = true; } public void readPage(FileInfo fi) throws IOException { checkPage(); page.clear(); while(page.remaining() != 0) { if (fi.read(page, getFirstEntryPosition()) <= 0) { throw new IOException("Short page read of ledger " + getLedger() + " tried to get " + page.capacity() + " from position " + getFirstEntryPosition() + " still need " + page.remaining()); } } last = getLastEntryIndex(); clean = true; } public ByteBuffer getPageToWrite() { checkPage(); page.clear(); return page; } long getLedger() { return entryKey.getLedgerId(); } int getVersion() { return version.get(); } public EntryKey getEntryKey() { return entryKey; } void setLedgerAndFirstEntry(long ledgerId, long firstEntry) { if (firstEntry % entriesPerPage != 0) { throw new IllegalArgumentException(firstEntry + " is not a multiple of " + entriesPerPage); } this.entryKey = new EntryKey(ledgerId, firstEntry); } long getFirstEntry() { return entryKey.getEntryId(); } long getMaxPossibleEntry() { return entryKey.getEntryId() + entriesPerPage; } long getFirstEntryPosition() { return entryKey.getEntryId() * indexEntrySize; } public boolean inUse() { return useCount.get() > 0; } private int getLastEntryIndex() { for(int i = entriesPerPage - 1; i >= 0; i--) { if (getOffset(i*getIndexEntrySize()) > 0) { return i; } } return -1; } public long getLastEntry() { if (last >= 0) { return last + entryKey.getEntryId(); } else { int index = getLastEntryIndex(); return index >= 0 ? (index + entryKey.getEntryId()) : 0; } } }