package com.linkedin.databus.core.util;
/*
*
* Copyright 2013 LinkedIn Corp. All rights reserved
*
* 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.
*
*/
import java.util.Arrays;
import java.util.Iterator;
import java.util.PriorityQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
import org.apache.log4j.Logger;
/**
* brief: A class that allows readers and writers to check out locks for operating on ranges at a time
* @author sdas
*
*/
public class RangeBasedReaderWriterLock {
public static final String MODULE = RangeBasedReaderWriterLock.class.getName();
public static final Logger LOG = Logger.getLogger(MODULE);
private static final long MAX_LOCK_WAIT_MS = 60000;
public class LockToken implements Comparable<LockToken> {
protected Range _id;
private final String _ownerName;
private final long _createTime;
private long _lastUpdateTime;
protected LockToken(Range id, String ownerName) {
_id = id;
_ownerName = ownerName;
_createTime = System.currentTimeMillis();
_lastUpdateTime = _createTime;
}
public Range getRange() {
return _id;
}
public String getOwnerName()
{
return _ownerName;
}
public void setRangeStart(long newStart)
{
_id.start = newStart;
_lastUpdateTime = System.currentTimeMillis();
}
@Override
public String toString()
{
return "{ownerName:" + _ownerName + ", range:" + _id +
", created:" + _createTime + ", lastUpdated:" + _lastUpdateTime +
"}";
}
public String toString(BufferPositionParser parser)
{
return "{ownerName:" + _ownerName + ", range:" + _id.toString(parser) + "}";
}
@Override
public int compareTo(LockToken o)
{
return _id.compareTo(o._id);
}
}
private final PriorityQueue<LockToken> readerRanges;
private final ReentrantLock mutex;
private final Condition writesPossible;
private final Condition readsPossible;
private Range writerRange;
private boolean writerIn;
private boolean writerWaiting;
public RangeBasedReaderWriterLock() {
readerRanges = new PriorityQueue<LockToken>(100);
writerRange = new Range(-1, 0);
writerIn = false;
mutex = new ReentrantLock();
writesPossible = mutex.newCondition();
readsPossible = mutex.newCondition();
}
public LockToken acquireReaderLock(long startOffset, long endOffset, BufferPositionParser parser,
String ownerName)
throws InterruptedException, TimeoutException
{
boolean debug = LOG.isDebugEnabled();
if (debug)
{
LOG.debug("Asked to acquire reader lock from " + parser.toString(startOffset) +
" to " + parser.toString(endOffset) + " for " + ownerName);
}
//LOG.info(Thread.currentThread().getName() + "-Asked to acquire reader lock from " + parser.toString(startOffset) + " to " + parser.toString(endOffset));
Range readerRange = new Range(startOffset, endOffset);
mutex.lock();
try
{
boolean timeout = false;
while (writerIn && writerRange.intersects(readerRange))
{
if ( debug )
{
LOG.debug("Waiting for reads to be possible since writer is In. Reader Range is :" + readerRange.toString(parser)
+ ". Writer Range is :" + writerRange.toString(parser));
}
if (timeout)
throw new TimeoutException();
if (!readsPossible.await(MAX_LOCK_WAIT_MS, TimeUnit.MILLISECONDS))
timeout = true;
if ( debug )
{
LOG.info("Waiting for reads to be possible: coming out of wait");
}
}
LockToken returnVal = new LockToken(readerRange, ownerName);
readerRanges.add(returnVal);
if (debug)
{
LOG.debug("Returning with reader lock from " + parser.toString(startOffset) + " to " + parser.toString(endOffset));
}
return returnVal;
}
finally
{
mutex.unlock();
}
}
public void shiftReaderLockStart(LockToken lockId, long newStartOffset, BufferPositionParser parser) {
if (LOG.isDebugEnabled())
{
LOG.debug("Being asked to shift reader lock start to " + parser.toString(newStartOffset) +
" for " + lockId);
}
mutex.lock();
try
{
boolean lockFound = readerRanges.remove(lockId);
assert lockFound : "lock:" + lockId + "; this:" + toString();
lockId.setRangeStart(newStartOffset);
readerRanges.add(lockId);
writesPossible.signal();
}
finally
{
mutex.unlock();
}
}
public void shiftReaderLockStartIfWriterWaiting(LockToken lockId, long newStartOffset, BufferPositionParser parser) {
if (LOG.isDebugEnabled())
{
LOG.debug("Being asked to shift reader lock start to " + parser.toString(newStartOffset) + ";writerWaiting = " + writerWaiting);
}
if (writerWaiting) // it is okay to check this value outside the protected section, we don't care if we make a mistake
{
mutex.lock();
try
{
readerRanges.remove(lockId);
lockId.setRangeStart(newStartOffset);
readerRanges.add(lockId);
writesPossible.signal();
}
finally
{
mutex.unlock();
}
}
}
public void releaseReaderLock(LockToken lockId) {
if (LOG.isDebugEnabled())
{
LOG.debug("Being asked to release reader lock " + lockId);
}
mutex.lock();
try
{
boolean readerLockRemoved = readerRanges.remove(lockId);
assert readerLockRemoved : "lock:" + lockId + "; this:" + toString();
writesPossible.signal();
}
finally
{
mutex.unlock();
}
}
public void acquireWriterLock(long start, long end, BufferPositionParser parser)
throws InterruptedException, TimeoutException
{
long startOffset = parser.address(start);
long endOffset = parser.address(end);
boolean debug = LOG.isDebugEnabled();
if (debug)
LOG.debug("Acquiring writer lock from " + parser.toString(start) + " to " + parser.toString(end));
mutex.lock();
try
{
boolean timeout = false;
while (!readerRanges.isEmpty() &&
Range.contains(startOffset, endOffset, parser.address(readerRanges.peek()._id.start)))
{
if ( debug )
{
LOG.debug("Entering wait because reader(s) exist: Writer Range: ["
+ parser.toString(start) + "(Address:" + parser.toString(startOffset) + ")-"
+ parser.toString(end) + "(Address:" + parser.toString(endOffset) +
")]. Nearest Reader Range :" + readerRanges.peek().toString(parser));
}
if (timeout)
{
LOG.error("timed out waiting for a write lock for [" + parser.toString(start) +
"," + parser.toString(end) + "); this: " + this );
throw new TimeoutException();
}
for (LockToken token: readerRanges)
{
LOG.info(token.toString(parser));
}
writerWaiting = true;
if (!writesPossible.await(MAX_LOCK_WAIT_MS, TimeUnit.MILLISECONDS))
timeout = true;
if ( debug )
LOG.debug("Writer coming out of wait");
}
writerWaiting = false;
writerIn = true;
writerRange.start = start;
writerRange.end = end;
}
finally
{
mutex.unlock();
}
}
public void releaseWriterLock(BufferPositionParser parser) {
if (LOG.isDebugEnabled())
{
LOG.debug("Releasing writer lock from " + parser.toString(writerRange.start) + " to " + parser.toString(writerRange.end));
}
mutex.lock();
try
{
writerIn = false;
readsPossible.signalAll();
}
finally
{
mutex.unlock();
}
}
public String toString(BufferPositionParser parser, boolean doSort)
{
StringBuilder strBuilder = new StringBuilder();
strBuilder.append("[writerIn:" + writerIn).append(",WriterWaiting:");
strBuilder.append(writerWaiting).append(",WriterRange:").append(writerRange.toString(parser));
strBuilder.append("\nReader Ranges:\n");
if ( !doSort)
{
Iterator<LockToken> it = readerRanges.iterator();
while (it.hasNext())
{
strBuilder.append(it.next().toString(parser)).append("\n");
}
} else {
LockToken[] ranges = new LockToken[readerRanges.size()];
readerRanges.toArray(ranges);
Arrays.sort(ranges);
for (int i = 0 ; i < ranges.length; i++)
{
strBuilder.append(ranges[i].toString(parser)).append("\n");
}
}
return strBuilder.toString();
}
// package private getters for unit-tests
PriorityQueue<LockToken> getReaderRanges()
{
return readerRanges;
}
public boolean isWriterIn()
{
return writerIn;
}
public boolean isWriterWaiting()
{
return writerWaiting;
}
public Range getWriterRange()
{
return writerRange;
}
public int getNumReaders()
{
return readerRanges.size();
}
@Override
public String toString()
{
mutex.lock();
try
{
return "{readerRanges:" + readerRanges +", writerRange:" + writerRange +
", writerIn:" + writerIn + ", writerWaiting:" + writerWaiting + "}";
}
finally
{
mutex.unlock();
}
}
}