package com.tesora.dve.locking.impl; /* * #%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 com.tesora.dve.debug.DebugHandle; import com.tesora.dve.exceptions.PECodingException; import com.tesora.dve.locking.LocalLockState; import com.tesora.dve.locking.LockMode; import com.tesora.dve.lockmanager.LockClient; import com.tesora.dve.resultset.ResultRow; import java.util.*; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicBoolean; public class LocalLockStateImpl implements LocalLockState { long sharedCount; long exclusiveCount; LockClient owner; ArrayList<ParkSeat> blockers; int blockedExclusive = 0; int blockedShared = 0; boolean immutable; EnumSet<LockMode> allowedGrants; public LocalLockStateImpl() { this.sharedCount = 0L; this.exclusiveCount = 0L; this.owner = null; this.blockers = new ArrayList<ParkSeat>(); this.allowedGrants = EnumSet.noneOf(LockMode.class); immutable = false; } public LocalLockStateImpl(LocalLockStateImpl other) { this.sharedCount = other.sharedCount; this.exclusiveCount = other.exclusiveCount; this.owner = other.owner; this.blockedShared = other.blockedShared; this.blockedExclusive =other.blockedExclusive; this.allowedGrants = other.allowedGrants; this.immutable = false; this.blockers = new ArrayList<ParkSeat>(); for (ParkSeat park : other.blockers){ ParkSeat copy = new ParkSeat(park); this.blockers.add(copy); } } @Override public LocalLockStateImpl mutableCopy() { return new LocalLockStateImpl(this); } @Override public void setAllowedGrants(EnumSet<LockMode> grants){ this.allowedGrants = EnumSet.copyOf(grants); } public List<Latch> nextUnblockSet(){ if (isBlockersEmpty()) return Collections.emptyList(); if ( isNextBlockerExclusive() ){ return nextExclusiveUnblockSet(); } else return nextSharedUnblockSet(); } public List<Latch> nextExclusiveUnblockSet(){ if (isBlockersEmpty()) return Collections.emptyList(); ArrayList<Latch> retList = new ArrayList<Latch>(); for (ParkSeat seat : blockers){ if ( seat.isBlocked() && seat.blockMode() == LockMode.EXCLUSIVE){ retList.add(seat); break; } } return retList; } public List<Latch> nextSharedUnblockSet(){ ArrayList<Latch> firstShares = new ArrayList<Latch>(); ListIterator<ParkSeat> listIter = blockers.listIterator(); while (listIter.hasNext()){ ParkSeat caller = listIter.next(); if (!caller.isBlocked()) continue; if (caller.blockMode() != LockMode.EXCLUSIVE){ firstShares.add(caller); } else { //workaround for stall issue. if (firstShares.size() > 0) break; else continue; } } blockedShared -= firstShares.size(); return firstShares; } @Override public void unlatch(List<Latch> tickets){ for (Latch ticket : tickets) ticket.unlatch(); } @Override public void addShowRow(String name, LockMode globalDeclare, List<ResultRow> rows) { //TODO: Not happy about the column meta being in ClusterLockManager and the rows being here, need to get them into the same place. /* 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); */ for (ParkSeat seat : blockers){ ResultRow row = new ResultRow(); row.addResultColumn(name); //lock_name if (seat.client == null) row.addResultColumn(); else row.addResultColumn(seat.client.getName()); row.addResultColumn(seat.isBlocked() ? "blocked" : "acquired" ); row.addResultColumn("" + globalDeclare); row.addResultColumn("" + allowedGrants); if (seat.sharedCount < 0) throw new RuntimeException("share count less than zero?"); if (seat.exclusiveCount < 0) throw new RuntimeException("exclusive count less than zero?"); row.addResultColumn(seat.sharedCount); row.addResultColumn(seat.exclusiveCount); row.addResultColumn(seat.reason); rows.add(row); } } public ParkSeat queueBlocker(LockClient client,LockMode mode, String reason){ int index = findSeat(client,Thread.currentThread()); ParkSeat blockEntry; if (index < 0){ blockEntry = new ParkSeat(client,Thread.currentThread(),reason, mode); blockers.add(blockEntry); } else { ParkSeat existing = blockers.get(index); if ((existing.sharedCount > 0) && (existing.exclusiveCount == 0) && (mode == LockMode.EXCLUSIVE)) throw new IllegalMonitorStateException("Cannot upgrade a shared lock to an exclusive lock"); blockEntry = new ParkSeat(client,Thread.currentThread(),reason, mode, existing.sharedCount,existing.exclusiveCount ); blockers.set(index,blockEntry); } if (mode == LockMode.EXCLUSIVE) blockedExclusive ++; else if (mode == LockMode.SHARED) blockedShared ++; return blockEntry; } @Override public boolean isBlockersEmpty() { return blockedExclusive == 0 && blockedShared == 0; } @Override public boolean isNextBlockerExclusive() { if (blockedExclusive == 0) return false; else { for (ParkSeat seat : blockers){ if (seat.isBlocked()) return seat.blockMode() == LockMode.EXCLUSIVE; } } return false; } @Override public boolean isNextBlockerShared() { if (blockedShared == 0) return false; else { for (ParkSeat seat : blockers){ if (seat.isBlocked()) return seat.blockMode() == LockMode.SHARED; } } return false; } @Override public Latch acquire(LockClient client, final LockMode mode, Thread caller, String reason){ if (!allowedGrants.contains(mode) || !canAcquire(client,mode,caller)) return queueBlocker(client,mode, reason); if (mode == LockMode.SHARED) incShare(client,caller, reason); else if (mode == LockMode.EXCLUSIVE) incExclusive(client,caller, reason); else throw new PECodingException("Unexpected lock mode on inc, "+mode); return new Latch() { public LockMode blockMode() {return mode;} public boolean isAcquired() {return true;} public void awaitUnlatch() {} public void unlatch() {} }; } @Override public void release(LockClient client,LockMode mode, Thread caller, String reason){ if (mode == LockMode.SHARED) decShare(client,caller,reason); else if (mode == LockMode.EXCLUSIVE) decExclusive(client,caller,reason); else throw new PECodingException("Unexpected lock mode on inc, "+mode); } public void incShare(LockClient client,Thread caller, String reason) { if (!canAcquireShared(client,caller)) throw new PECodingException("Cannot acquire shared lock, not current exclusive owner. Caller should have been blocked"); sharedCount++; updateUsage(client,caller, 1, 0, reason); } public void decShare(LockClient client,Thread caller, String reason) { if (isExclusive() && !isOwner(client)) throw new IllegalMonitorStateException("Cannot release shared lock, not current exclusive owner"); if ( sharedCount <=0 ) throw new IllegalMonitorStateException("Cannot release shared lock, no matching acquire"); sharedCount --; updateUsage(client,caller, -1, 0, reason); } public void incExclusive(LockClient client,Thread caller, String reason) { if (!canAcquireExclusive(client,caller)){ throw new PECodingException("Cannot acquire exclusive lock, not current exclusive owner. Caller should have been blocked"); } this.exclusiveCount ++; this.owner = client; updateUsage(client,caller, 0, 1, reason); } public void decExclusive(LockClient client,Thread caller, String reason) { if (isExclusive() && !isOwner(client)) throw new IllegalMonitorStateException("Cannot release exclusive lock, not current exclusive owner"); if ( exclusiveCount <=0 ) throw new IllegalMonitorStateException("Cannot release exclusive lock, no matching acquire"); exclusiveCount--; if (exclusiveCount == 0) owner = null; updateUsage(client,caller, 0, -1, reason); } private void updateUsage(LockClient client, Thread caller, int deltaShared, int deltaExclusive, String reason) { int index = findSeat(client,caller); if (index < 0) { blockers.add(new ParkSeat(client,caller, reason, deltaShared, deltaExclusive)); } else { ParkSeat existing = blockers.get(index); ParkSeat newSeat = new ParkSeat(client,caller, reason, existing.sharedCount + deltaShared, existing.exclusiveCount + deltaExclusive); if (newSeat.isUnused()) blockers.remove(index); else blockers.set(index, newSeat); } } private int findSeat(LockClient client,Thread caller) { int index = -1; for (int i=0;i< blockers.size();i++){ ParkSeat seat = blockers.get(i); if (seat.sameClient(client)){ index = i; break; } } return index; } public boolean canAcquire(LockClient client, LockMode mode, Thread caller){ if (mode == LockMode.SHARED) return canAcquireShared(client,caller); else if (mode == LockMode.EXCLUSIVE) return canAcquireExclusive(client,caller); else throw new PECodingException("Unexpected lock mode on canAcquire, "+mode); } public boolean canAcquireExclusive(LockClient client,Thread caller){ if (!allowedGrants.contains(LockMode.EXCLUSIVE)) return false; if (!isUnlocked() && (isShared() || !isOwner(client))) return false;//we already have a local share lock, or an exclusive and we aren't the owner for (ParkSeat seat : blockers){ if (seat.sameClient(client)) return true; if ( seat.isBlocked()) return false;//we already have someone else for a lock, get in queue. } return true; } public boolean canAcquireShared(LockClient client,Thread caller){ if (!allowedGrants.contains(LockMode.SHARED)) return false; if ( isExclusive() ) return false;//we already have a local exclusive lock for (ParkSeat seat : blockers){ if (seat.sameClient(client)) return true; if ( seat.isBlocked() && seat.blockMode() == LockMode.EXCLUSIVE) return false;//we already have someone else for a lock, get in queue. } //no entries. return true; } @Override public boolean isUnlocked(){ return this.exclusiveCount == 0 && this.sharedCount == 0; } @Override public boolean isUnused(){ return this.blockers.isEmpty();//no blocked threads, no active threads. } @Override public boolean isExclusive() { return this.exclusiveCount != 0; } @Override public boolean isShared(){ return this.exclusiveCount == 0 && this.sharedCount > 0; } @Override public boolean isOwner(LockClient client){ return this.owner == client; } public String toString(){ return String.format("(shared=%s,exclusive=%s,owner=%s)",sharedCount,exclusiveCount,owner); } @Override public void writeTo(DebugHandle displayOut) { displayOut.entry("sharedCount", sharedCount); displayOut.entry("exclusiveCount", exclusiveCount); displayOut.entry("owner", owner); displayOut.entry("allowedGrants", allowedGrants); displayOut.entry("users","["); DebugHandle users = displayOut.nesting(); for (ParkSeat usage : blockers){ users.line("" + usage); } displayOut.line("]"); } public static class ParkSeat implements Latch { LockClient client; Thread caller; String reason; int sharedCount; int exclusiveCount; CountDownLatch latch = new CountDownLatch(1); LockMode blockedMode; AtomicBoolean unlatched = new AtomicBoolean(false); AtomicBoolean finished = new AtomicBoolean(false); //only called by queueBlocker public ParkSeat(LockClient client,Thread caller, String reason,LockMode mode) { this.client = client; this.caller = caller; this.reason = reason; this.blockedMode = mode; this.sharedCount = 0; this.exclusiveCount = 0; unlatched.set(false); finished.set(false); } //only called by updateUsage after an acquire or release public ParkSeat(LockClient client,Thread caller, String reason, int sharedCount, int exclusiveCount) { this.client = client; this.caller = caller; this.reason = reason; this.blockedMode = null; this.sharedCount = sharedCount; this.exclusiveCount = exclusiveCount; this.finished.set(true); this.unlatch(); } //only called by queueBlocker public ParkSeat(LockClient client,Thread caller, String reason, LockMode mode,int sharedCount, int exclusiveCount) { this.client = client; this.caller = caller; this.reason = reason; this.blockedMode = mode; this.sharedCount = sharedCount; this.exclusiveCount = exclusiveCount; unlatched.set(false); this.finished.set(false); } //copy constructor, called by deep copy of local lock state public ParkSeat(ParkSeat other){ this.client = other.client; this.caller = other.caller; this.reason = other.reason; this.blockedMode = other.blockedMode; this.sharedCount = other.sharedCount; this.exclusiveCount = other.exclusiveCount; //THESE ARE THE SAME UNDERLYING ATOMICS / LATCHES! this.unlatched = other.unlatched; this.finished = other.finished; this.latch = other.latch; } public boolean sameClient(LockClient otherClient){ if (this.client == null) return otherClient == null; else return this.client.equals(otherClient); } public LockMode blockMode(){ return blockedMode; } public boolean isAcquired(){ return this.blockedMode == null; } public boolean isBlocked(){ return this.blockedMode != null; } public String getReason(){ return reason; } public boolean isUnused(){ return isAcquired() && sharedCount == 0 && exclusiveCount == 0; } public void awaitUnlatch() { // System.out.printf("*** parking %s, mode = %s\n", Thread.currentThread(), blockedMode); if (Thread.currentThread() != caller) throw new IllegalStateException("Only creator of latch can block on it"); for(;;){ try { latch.await(); break; } catch (InterruptedException e) { //ignore, not interruptable. } finally { finished.set(true); } } // System.out.printf("*** unparked %s, mode = %s\n", Thread.currentThread(), blockedMode); } public void unlatch(){ latch.countDown(); unlatched.set(true); } public String toString(){ return String.format("[client=%s,reason=%s,blockmode=%s,blocked=%s,acquired=%s,thread=%s,shared=%s,exclusive=%s]",this.client,this.reason,this.blockedMode,isBlocked(),isAcquired(),caller,sharedCount,exclusiveCount); } } }