/*
*
* * Copyright 2014 Orient Technologies LTD (info(at)orientechnologies.com)
* *
* * Licensed 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.
* *
* * For more information: http://www.orientechnologies.com
*
*/
package com.orientechnologies.orient.core.storage.cache;
import com.orientechnologies.common.directmemory.OByteBufferPool;
import com.orientechnologies.common.log.OLogManager;
import com.orientechnologies.orient.core.storage.impl.local.paginated.wal.OLogSequenceNumber;
import java.nio.ByteBuffer;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicLong;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author Andrey Lomakin
* @since 05.08.13
*/
public class OCachePointer {
private static final int WRITERS_OFFSET = 32;
private static final int READERS_MASK = 0xFFFFFFFF;
private final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
private final AtomicInteger referrersCount = new AtomicInteger();
private final AtomicLong readersWritersReferrer = new AtomicLong();
private final AtomicInteger usagesCounter = new AtomicInteger();
private volatile OLogSequenceNumber lastFlushedLsn;
private volatile WritersListener writersListener;
private final ByteBuffer buffer;
private final OByteBufferPool bufferPool;
private final ThreadLocal<ByteBuffer> threadLocalBuffer = new ThreadLocal<ByteBuffer>() {
@Override
protected ByteBuffer initialValue() {
if (buffer != null) {
final ByteBuffer b = buffer.duplicate();
b.position(0);
b.order(buffer.order());
return b;
}
return null;
}
};
private final long fileId;
private final long pageIndex;
public OCachePointer(final ByteBuffer buffer, final OByteBufferPool bufferPool, final OLogSequenceNumber lastFlushedLsn,
final long fileId, final long pageIndex) {
this.lastFlushedLsn = lastFlushedLsn;
this.buffer = buffer;
this.bufferPool = bufferPool;
this.fileId = fileId;
this.pageIndex = pageIndex;
}
public void setWritersListener(WritersListener writersListener) {
this.writersListener = writersListener;
}
public long getFileId() {
return fileId;
}
public long getPageIndex() {
return pageIndex;
}
public OLogSequenceNumber getLastFlushedLsn() {
return lastFlushedLsn;
}
public void setLastFlushedLsn(final OLogSequenceNumber lastFlushedLsn) {
this.lastFlushedLsn = lastFlushedLsn;
}
public void incrementReadersReferrer() {
long readersWriters = readersWritersReferrer.get();
int readers = getReaders(readersWriters);
int writers = getWriters(readersWriters);
readers++;
assert readers == 1;
while (!readersWritersReferrer.compareAndSet(readersWriters, composeReadersWriters(readers, writers))) {
readersWriters = readersWritersReferrer.get();
readers = getReaders(readersWriters);
writers = getWriters(readersWriters);
readers++;
assert readers == 1;
}
final WritersListener wl = writersListener;
if (wl != null) {
if (writers > 0 && readers == 1)
wl.removeOnlyWriters(fileId, pageIndex);
}
incrementReferrer();
}
public void decrementReadersReferrer() {
long readersWriters = readersWritersReferrer.get();
int readers = getReaders(readersWriters);
int writers = getWriters(readersWriters);
readers--;
assert readers == 0;
while (!readersWritersReferrer.compareAndSet(readersWriters, composeReadersWriters(readers, writers))) {
readersWriters = readersWritersReferrer.get();
readers = getReaders(readersWriters);
writers = getWriters(readersWriters);
readers--;
assert readers == 0;
}
final WritersListener wl = writersListener;
if (wl != null) {
if (writers > 0 && readers == 0)
wl.addOnlyWriters(fileId, pageIndex);
}
decrementReferrer();
}
public void incrementWritersReferrer() {
long readersWriters = readersWritersReferrer.get();
int readers = getReaders(readersWriters);
int writers = getWriters(readersWriters);
writers++;
assert writers == 1;
while (!readersWritersReferrer.compareAndSet(readersWriters, composeReadersWriters(readers, writers))) {
readersWriters = readersWritersReferrer.get();
readers = getReaders(readersWriters);
writers = getWriters(readersWriters);
writers++;
assert writers == 1;
}
incrementReferrer();
}
public void decrementWritersReferrer() {
long readersWriters = readersWritersReferrer.get();
int readers = getReaders(readersWriters);
int writers = getWriters(readersWriters);
writers--;
assert writers == 0;
while (!readersWritersReferrer.compareAndSet(readersWriters, composeReadersWriters(readers, writers))) {
readersWriters = readersWritersReferrer.get();
readers = getReaders(readersWriters);
writers = getWriters(readersWriters);
writers--;
assert writers == 0;
}
final WritersListener wl = writersListener;
if (wl != null) {
if (readers == 0 && writers == 0)
wl.removeOnlyWriters(fileId, pageIndex);
}
decrementReferrer();
}
/**
* DEBUG only !!!
*
* @return Whether pointer lock (read or write )is acquired
*/
public boolean isLockAcquiredByCurrentThread() {
return readWriteLock.getReadHoldCount() > 0 || readWriteLock.isWriteLockedByCurrentThread();
}
public void incrementReferrer() {
referrersCount.incrementAndGet();
}
public void decrementReferrer() {
final int rf = referrersCount.decrementAndGet();
if (rf == 0 && buffer != null) {
bufferPool.release(buffer);
}
if (rf < 0)
throw new IllegalStateException("Invalid direct memory state, number of referrers cannot be negative " + rf);
}
public ByteBuffer getSharedBuffer() {
return threadLocalBuffer.get();
}
public ByteBuffer getExclusiveBuffer() {
return buffer;
}
public void acquireExclusiveLock() {
readWriteLock.writeLock().lock();
}
public boolean tryAcquireExclusiveLock() {
return readWriteLock.writeLock().tryLock();
}
public void releaseExclusiveLock() {
readWriteLock.writeLock().unlock();
}
public void acquireSharedLock() {
readWriteLock.readLock().lock();
}
public void releaseSharedLock() {
readWriteLock.readLock().unlock();
}
public boolean tryAcquireSharedLock() {
return readWriteLock.readLock().tryLock();
}
@Override
protected void finalize() throws Throwable {
super.finalize();
boolean needInfo = false;
if (getReaders(readersWritersReferrer.get()) != 0) {
needInfo = true;
OLogManager.instance().error(this, "OCachePointer.finalize: readers != 0");
}
if (getWriters(readersWritersReferrer.get()) != 0) {
needInfo = true;
OLogManager.instance().error(this, "OCachePointer.finalize: writers != 0");
}
if (needInfo && buffer != null)
bufferPool.logTrackedBufferInfo("finalizing", buffer);
if (referrersCount.get() > 0 && buffer != null) {
if (!needInfo)
bufferPool.logTrackedBufferInfo("finalizing", buffer);
bufferPool.release(buffer);
}
}
@Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
OCachePointer that = (OCachePointer) o;
buffer.position(0);
that.buffer.position(0);
if (buffer != null ? !buffer.equals(that.buffer) : that.buffer != null)
return false;
return true;
}
@Override
public int hashCode() {
return buffer != null ? buffer.hashCode() : 0;
}
@Override
public String toString() {
return "OCachePointer{" + "referrersCount=" + referrersCount + ", usagesCount=" + usagesCounter + '}';
}
private long composeReadersWriters(int readers, int writers) {
return ((long) writers) << WRITERS_OFFSET | readers;
}
private int getReaders(long readersWriters) {
return (int) (readersWriters & READERS_MASK);
}
private int getWriters(long readersWriters) {
return (int) (readersWriters >>> WRITERS_OFFSET);
}
public interface WritersListener {
void addOnlyWriters(long fileId, long pageIndex);
void removeOnlyWriters(long fileId, long pageIndex);
}
}