package com.tesora.dve.lockmanager.inmem;
/*
* #%L
* Tesora Inc.
* Database Virtualization Engine
* %%
* Copyright (C) 2011 - 2014 Tesora Inc.
* %%
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License, version 3,
* as published by the Free Software Foundation.
*
* This program 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 Affero General Public License for more details.
*
* You should have received a copy of the GNU Affero General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
* #L%
*/
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import com.tesora.dve.common.MultiMap;
import com.tesora.dve.common.PEConstants;
import com.tesora.dve.lockmanager.*;
import com.tesora.dve.resultset.ColumnSet;
import com.tesora.dve.resultset.IntermediateResultSet;
import com.tesora.dve.resultset.ResultRow;
public class LocalLockManager implements LockManager {
private final Object latch = new Object();
private final Map<LockSpecification,LockQueue> queues = new HashMap<LockSpecification,LockQueue>();
// for show support
private final HashSet<ManagedLock> waiting = new HashSet<ManagedLock>();
@Override
public AcquiredLock acquire(LockClient c, LockSpecification l, LockType type) {
// TODO Auto-generated method stub
ManagedLock ml = new ManagedLock(l,Thread.currentThread(),type, c);
acquireInternal(ml);
synchronized(ml) {
while(!ml.acquiredLock()) try {
ml.wait();
} catch (InterruptedException ie) {
// ignore
}
}
return ml;
}
@Override
public void release(AcquiredLock l) {
releaseInternal((ManagedLock)l);
}
private void reportError(String what) {
throw new IllegalStateException(what);
}
private void grantLock(String reason, ManagedLock ml, Collection<ManagedLock> ofQueue) {
// if ml is an exclusive lock, make sure that it is at the head of the queue
String message =new MultiReaderSingleWriterBehavior().validateGrantLock(ml, ofQueue);
if (message != null)
reportError(message);
ml.setAcquired(reason);
}
private void acquireInternal(ManagedLock ml) {
LockSpecification key = ml.getTarget();
MultiReaderSingleWriterBehavior p = new MultiReaderSingleWriterBehavior();
synchronized(latch) {
LockQueue queue = queues.get(key);
if (queue == null) {
queue = new LockQueue();
queues.put(key,queue);
}
// check for double queueing
if (!p.allowsMultiQueueing()) {
ManagedLock already = queue.getExistingLock(ml.getClient());
if (already != null)
reportError("Double queued lock. Found " + already.toString() + " while enqueueing " + ml.toString());
}
queue.enqueue(ml);
if (queue.getQueue().size() == 1) {
grantLock("empty queue",ml,queue.getQueue());
} else {
if (p.canAcquire(ml, queue.getQueue())) {
grantLock("allowed upon create",ml, queue.getQueue());
} else {
waiting.add(ml);
}
}
}
}
private void releaseInternal(ManagedLock ml) {
LockSpecification key = ml.getTarget();
MultiReaderSingleWriterBehavior p = new MultiReaderSingleWriterBehavior();
synchronized(latch) {
LockQueue queue = queues.get(key);
ManagedLock head = queue.getHead();
ManagedLock eml = queue.dequeue(ml);
if (eml == null)
reportError("No such lock: " + ml.toString());
if (queue.getQueue().isEmpty())
queues.remove(key);
else {
List<ManagedLock> toGrant = p.release(head, queue.getQueue());
for(ManagedLock rml : toGrant) {
waiting.remove(rml);
grantLock("upon release",rml, queue.getQueue());
}
}
}
}
@Override
public String assertNoLocks() {
final StringBuilder buf = new StringBuilder();
getState(new LockInfoCollector() {
@Override
public void onLock(String lockName, ManagedLock ml) {
// TODO Auto-generated method stub
buf.append(lockName).append("; ")
.append(ml.getClient().getName()).append("; ")
.append(ml.getType().toString()).append("; ")
.append(ml.acquiredLock() ? "acquired" : "waiting").append("; ")
.append(ml.getReason()).append("; ")
.append(ml.getTarget().getOriginator()).append(PEConstants.LINE_SEPARATOR);
}
});
if (buf.length() == 0)
return null;
return buf.toString();
}
protected void getState(LockInfoCollector acc) {
MultiMap<LockSpecification,ManagedLock> sortedWaiters = new MultiMap<LockSpecification,ManagedLock>();
synchronized(latch) {
// rebuild waiters as a map
for(ManagedLock ml : waiting) {
sortedWaiters.put(ml.getTarget(), ml);
}
for(Map.Entry<LockSpecification, LockQueue> me : queues.entrySet()) {
String lockName = me.getKey().getName();
for(ManagedLock ml : me.getValue().getQueue()) {
acc.onLock(lockName, ml);
sortedWaiters.remove(me.getKey(),ml);
}
Collection<ManagedLock> sub = sortedWaiters.get(me.getKey());
sortedWaiters.remove(me.getKey());
if (sub == null || sub.isEmpty()) continue;
for(ManagedLock ml : sub) {
acc.onLock(lockName, ml);
}
}
for(LockSpecification ls : sortedWaiters.keySet()) {
Collection<ManagedLock> sub = sortedWaiters.get(ls);
if (sub == null || sub.isEmpty()) continue;
for(ManagedLock ml : sub)
acc.onLock(ls.getName(),ml);
}
}
}
@Override
public IntermediateResultSet showState() {
// since this is entirely internal, make up a format
// lock name, conn, type, state
// where lock name is the lock specification
// conn is the name of the connection
// type is the lock type
// state is either acquired or waiting
ColumnSet cs = new ColumnSet();
cs.addColumn("lock_name", 255, "varchar", java.sql.Types.VARCHAR);
cs.addColumn("connection", 255, "varchar", java.sql.Types.VARCHAR);
cs.addColumn("lock_type", 12, "varchar", java.sql.Types.VARCHAR);
cs.addColumn("state", 12, "varchar", java.sql.Types.VARCHAR);
cs.addColumn("reason", 255, "varchar", java.sql.Types.VARCHAR);
cs.addColumn("originator", 255, "varchar", java.sql.Types.VARCHAR);
final List<ResultRow> rows = new ArrayList<ResultRow>();
getState(new LockInfoCollector() {
@Override
public void onLock(String lockName, ManagedLock ml) {
ResultRow rr = new ResultRow();
rr.addResultColumn(lockName);
rr.addResultColumn(ml.getClient().getName());
rr.addResultColumn(ml.getType().toString());
rr.addResultColumn((ml.acquiredLock() ? "acquired" : "waiting"));
rr.addResultColumn(ml.getReason());
rr.addResultColumn(ml.getTarget().getOriginator());
rows.add(rr);
}
});
return new IntermediateResultSet(cs, rows);
}
interface LockInfoCollector {
void onLock(String lockName, ManagedLock ml);
}
private static class LockQueue {
private final LinkedList<ManagedLock> queue;
private final HashMap<LockClient,ManagedLock> clients;
public LockQueue() {
queue = new LinkedList<ManagedLock>();
clients = new HashMap<LockClient,ManagedLock>();
}
public ManagedLock getExistingLock(LockClient lc) {
return clients.get(lc);
}
public void enqueue(ManagedLock ml) {
queue.add(ml);
clients.put(ml.getClient(), ml);
}
public Collection<ManagedLock> getQueue() {
return queue;
}
public ManagedLock getHead() {
return queue.get(0);
}
public ManagedLock dequeue(ManagedLock ml) {
ManagedLock was = (queue.remove(ml) ? ml : null);
clients.remove(ml.getClient());
return was;
}
}
}