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.clock.MonotonicLongClock;
import com.tesora.dve.debug.DebugHandle;
import com.tesora.dve.locking.IntentEntry;
import com.tesora.dve.membership.GroupTopic;
import com.tesora.dve.membership.MembershipView;
import com.tesora.dve.locking.GlobalLockState;
import com.tesora.dve.locking.LockMode;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.net.InetSocketAddress;
import java.util.*;
import java.util.concurrent.atomic.AtomicLong;
public class GlobalLockStateImpl implements GlobalLockState {
static final Logger log = LoggerFactory.getLogger(GlobalLockStateImpl.class);
MonotonicLongClock clock;
GroupTopic<IntentEntry> topic;
MembershipView membership;
ArrayList<IntentEntry> registrations;
AtomicLong transmitCount;
public GlobalLockStateImpl(MonotonicLongClock clock, GroupTopic<IntentEntry> topic, MembershipView membership) {
this.clock = clock;
this.topic = topic;
this.membership = membership;
this.registrations = new ArrayList<IntentEntry>();
this.registrations.add( new IntentEntry(membership.getMyAddress(), LockMode.UNLOCKED,clock.nextLongTimestamp()) );
this.transmitCount = new AtomicLong(0L);
}
GlobalLockStateImpl(MonotonicLongClock clock, GroupTopic<IntentEntry> topic, MembershipView membership, ArrayList<IntentEntry> registrations, AtomicLong transmitted) {
this.clock = clock;
this.topic = topic;
this.membership = membership;
this.registrations = registrations;
this.transmitCount = transmitted;
}
public Runnable republish(final String message){
if (findFor(membership.getMyAddress()) >= 0){ //TODO: if this is >=0, it causes a hang on shutdown, but >0 seems wrong, since it won't rebroadcast if we are first in list.
return new SimplePublishTicket( freshenMyEntry(true) ){
public void run() {
log.warn(String.format(message));
super.run();
}
};
} else
return RunnableNoop.NOOP;
}
public Runnable membershipChange(MembershipView newView){
this.membership = newView;
ListIterator<IntentEntry> iter = registrations.listIterator();
final ArrayList<IntentEntry> dropped = new ArrayList<IntentEntry>();
while (iter.hasNext()){
IntentEntry entry = iter.next();
if ( !entry.isFor(this.membership.getMyAddress()) && (!this.membership.isInQuorum() || !this.membership.activeQuorumMembers().contains(entry.member)) ){
dropped.add(entry);
iter.remove();
}
}
if (dropped.size() > 0){
Collections.sort(registrations);
}
return republish(String.format("Quorum membership changed, dropping entries for , %s",dropped));
}
@Override
public GlobalLockState mutableCopy() {
ArrayList<IntentEntry> copyRegs = new ArrayList<IntentEntry>();
copyRegs.addAll(registrations);
return new GlobalLockStateImpl(this.clock,this.topic,this.membership,copyRegs, transmitCount);
}
IntentEntry myEntry(){
int index = findFor(membership.getMyAddress());
if (index < 0)
return null;
else
return registrations.get(index);
}
@Override
public LockMode getDeclaredMode(){
int myIndex = findFor(membership.getMyAddress());
return registrations.get(myIndex).state;
}
public boolean isAcquiring(){
return getDeclaredMode().isAcquiring();
}
@Override
public EnumSet<LockMode> getGrantedModes(){
verifyInQuorum();
int myIndex = findFor(membership.getMyAddress());
if (myIndex < 0)
return EnumSet.noneOf(LockMode.class);
return getGrantedModes(myIndex);
}
private void verifyInQuorum() throws IllegalMonitorStateException {
if (! membership.isInQuorum() )
throw new IllegalMonitorStateException("Current member is not in quorum, cannot process lock request");
}
public boolean isGranted(LockMode mode){
verifyInQuorum();
return getGrantedModes().contains(mode);
}
public EnumSet<LockMode> getGrantedModes(int index){
IntentEntry entry = registrations.get(index);
if ( !fullyAcknowledged( index ) )
return EnumSet.of(LockMode.UNLOCKED);
EnumSet<LockMode> actualGrant = entry.state.wouldGrant().clone();
for (int i=0;i < index;i++){
IntentEntry existing = registrations.get(i);
if (existing.state.conflictsWith(LockMode.EXCLUSIVE)){
actualGrant.remove(LockMode.EXCLUSIVE);
}
if (existing.state.conflictsWith(LockMode.SHARED)){
actualGrant.remove(LockMode.SHARED);
}
if (actualGrant.size() == 0)
break;
}
if (actualGrant.size() == 0)
return EnumSet.of(LockMode.UNLOCKED);
else
return actualGrant;
}
public EnumSet<LockMode> getBlockedModes(){
int myIndex = findFor(membership.getMyAddress());
return getBlockedModes(myIndex);
}
public boolean isBlocking(LockMode mode){
return getBlockedModes().contains(mode);
}
public EnumSet<LockMode> getBlockedModes(int index){
IntentEntry entry = registrations.get(index);
EnumSet<LockMode> blocking = EnumSet.noneOf(LockMode.class);
for (int i=index+1;i < registrations.size();i++){
IntentEntry existing = registrations.get(i);
if (existing.state.conflictsWith(entry.state))
blocking.add( existing.state.wantedMode() );
if (blocking.size() == 2) //both, so exit.
break;
}
return blocking;
}
public boolean fullyAcknowledged(int index){
return numberOfMissingAcksForEntry(index) <=0;
}
public int numberOfMissingAcksForEntry(int index){
return membership.activeQuorumMembers().size() - numberThatHaveAckedEntry(index);
}
public int numberThatHaveAckedEntry(int index){
if (index < 0)
return 0;
IntentEntry entry = registrations.get(index);
int count = 0;
for (int i=0;i<registrations.size();i++){
if (entry.isAckedBy(registrations.get(i)))
count ++;
}
return count;
}
public int numberNotAckedByEntry(int index){
if (index < 0)
return registrations.size();
IntentEntry entry = registrations.get(index);
int count = 0;
for (int i=0;i<registrations.size();i++){
if (!registrations.get(i).isAckedBy(entry))
count ++;
}
return count;
}
@Override
public Runnable acquiringExclusive() {
verifyInQuorum();
// System.out.println(membership.getMyAddress() + " acq excl");
IntentEntry entry = new IntentEntry(membership.getMyAddress(), LockMode.ACQUIRING_EXCLUSIVE,clock.nextLongTimestamp());
return process(entry,true);
}
@Override
public Runnable acquiringShared() {
verifyInQuorum();
IntentEntry entry = new IntentEntry(membership.getMyAddress(), LockMode.ACQUIRING_SHARED,clock.nextLongTimestamp());
return process(entry,true);
}
@Override
public Runnable unlocking() {
verifyInQuorum();
IntentEntry entry = new IntentEntry(membership.getMyAddress(), LockMode.UNLOCKED,clock.nextLongTimestamp());
return process(entry,true);
}
@Override
public Runnable downgradingToShared() {
verifyInQuorum();
if (!isGranted(LockMode.EXCLUSIVE))
throw new IllegalStateException("Cannot downgrade to shared, not granted exclusive");
IntentEntry entry = new IntentEntry(membership.getMyAddress(), LockMode.SHARED, clock.nextLongTimestamp(),myEntry().lastUnlockedOrAcquired);
return process(entry,true);
}
int findFor(InetSocketAddress someAddr){
for (int i=0;i < registrations.size();i++){
IntentEntry entry = registrations.get(i);
if (entry.isFor(someAddr))
return i;
}
return -1;
}
@Override
public boolean isStale(IntentEntry someEntry) {
updateClockIfNeeded(someEntry);
// System.out.println(membership.getMyAddress() + " :: onMessage entry (via is stale) ==> "+entry);
if (someEntry.isFor(membership.getMyAddress()) && membership.activeQuorumMembers().size() > 1)
return true;
//ignore messages that aren't from active quorum members.
if (!membership.activeQuorumMembers().contains(someEntry.member))
return true;
//
// int index = findFor(entry.member);
// if (index < 0)
// return false;
// IntentEntry existing = registrations.get(index);
// boolean isStale = entry.published <= existing.published;
// System.out.printf("stale check[recvr=%s,stale=%s] existing=%s , entry=%s\n",membership.getMyAddress(),isStale,existing,entry);
// return isStale;
return false;
}
@Override
public Runnable process(IntentEntry someEntry) {
return process(someEntry,false);
}
protected Runnable process(IntentEntry someEntry,boolean locallyTriggered) {
updateClockIfNeeded(someEntry);
if (!membership.isInQuorum())
return RunnableNoop.NOOP;
boolean causedTransition = false;
int updatedEntryIndex = findFor(someEntry.member);
int myIndex = findFor(membership.getMyAddress());
IntentEntry myEntry;
if (myIndex >= 0)
myEntry = registrations.get(myIndex);
else
myEntry = null;
boolean needsAck = false;
if (updatedEntryIndex >=0 && !registrations.get(updatedEntryIndex).isPublishedBefore(someEntry) ){ //same entry
return RunnableNoop.NOOP;
}
if (updatedEntryIndex < 0){
registrations.add(someEntry);
needsAck = true;
} else {
IntentEntry existing = registrations.get(updatedEntryIndex);
needsAck = existing.isEnteredBefore(someEntry);
registrations.set(updatedEntryIndex,someEntry);
}
Collections.sort(registrations);//need to sort to get ordering for isGrantedXXX() stuff to work right.
myIndex = findFor(membership.getMyAddress());
IntentEntry myEntryAfterInsert = myEntry();
long newTS = clock.nextLongTimestamp();
EnumSet<LockMode> grantedModes = getGrantedModes();
if (myEntryAfterInsert != null && grantedModes.contains(myEntryAfterInsert.state.wantedMode())){
IntentEntry element = new IntentEntry(membership.getMyAddress(), myEntryAfterInsert.state.wantedMode(), newTS, myEntryAfterInsert.lastUnlockedOrAcquired);
registrations.set(myIndex, element);
causedTransition = true;
} else if (myEntryAfterInsert != null && myEntryAfterInsert.state == LockMode.ACQUIRING_EXCLUSIVE && grantedModes.contains(LockMode.SHARED)){
IntentEntry element = new IntentEntry(membership.getMyAddress(), LockMode.ACQUIRING_EXCLUSIVE_WITH_SHARE, newTS, myEntryAfterInsert.lastUnlockedOrAcquired);
registrations.set(myIndex, element);
causedTransition = true;
}
// int numberWaitingForAfterTransition = numberOfMissingAcksForEntry( findFor(membership.getMyAddress() ));
// boolean remoteRequested = (!someEntry.isFor(membership.getMyAddress()) && someEntry.wantsAck());
// boolean remoteOutOfDate = (numberINeedToAckBeforeTransition > 0 || numberWaitingForAfterTransition >0);
boolean remoteOutOfDate = (!someEntry.isFor(membership.getMyAddress()) && needsAck );
// boolean triggeredUpdate = locallyTriggered || causedTransition || remoteOutOfDate;
boolean triggeredUpdate = locallyTriggered || remoteOutOfDate;
// System.out.printf("%s, received %s, needed to ack %s messages, locallyTriggered=%s, causedTransition=%s, sendingUpdate=%s\n",membership.getMyAddress(),someEntry,numberINeedToAckBeforeTransition,locallyTriggered,causedTransition,triggeredUpdate);
if (triggeredUpdate){
//freshen up timestamp before we transmit.
return new SimplePublishTicket( freshenMyEntry(locallyTriggered) );
} else {
Collections.sort(registrations);
// System.out.printf("%s, needed to ack %s messages, and was missing %s acks, not sending\n",membership.getMyAddress(),numberNeedingMyAcksBefore,missingAcksForMe);
return RunnableNoop.NOOP;
}
}
private IntentEntry freshenMyEntry(boolean locallyTriggered) {
int myIndex;
IntentEntry myAfter;
myIndex = findFor(membership.getMyAddress());
myAfter = registrations.get(myIndex);
IntentEntry element = new IntentEntry(membership.getMyAddress(), myAfter.state, clock.nextLongTimestamp() , myAfter.lastUnlockedOrAcquired);
registrations.set(myIndex, element);
myAfter = element;
Collections.sort(registrations);
return myAfter;
}
@Override
public void writeTo(DebugHandle displayOut){
int index = findFor(membership.getMyAddress());
IntentEntry myEntry = registrations.get(index);
displayOut.entry("membership", membership);
displayOut.entry("state", myEntry.state);
displayOut.entry("transmitCount", transmitCount.get());
displayOut.entry("needAcksFrom", numberOfMissingAcksForEntry(index));
displayOut.entry("needMeToAck", numberNotAckedByEntry(index));
displayOut.entry("granted", getGrantedModes(index));
displayOut.entry("blocking", getBlockedModes(index));
for (IntentEntry entry : registrations){
displayOut.line("" + entry + " [" + (myEntry.isAckedBy(entry) ? "ACKS ME]" : "ACKS ME NOT]"));
}
}
void updateClockIfNeeded(IntentEntry someEntry) {
clock.ensureLaterThanLong(someEntry.published);
clock.ensureLaterThanLong(someEntry.lastUnlockedOrAcquired);
}
public String toString(){
int index = findFor(membership.getMyAddress());
IntentEntry entry = myEntry();
return String.format("TopicGroupList[membership=%s,entry.state=%s,entries=%s,needAcksFrom=%s, needMeToAck=%s, transmitCount=%s]", membership, entry.state, registrations.size(), numberOfMissingAcksForEntry(index), numberNotAckedByEntry(index), transmitCount.get());
}
public class SimplePublishTicket implements Runnable {
IntentEntry entry;
public SimplePublishTicket(IntentEntry entry) {
this.entry = entry;
}
@Override
public void run() {
// System.out.printf("Member %s , sending ==> %s\n",membership.getMyAddress(),entry);
transmitCount.incrementAndGet();
topic.publish(entry);
}
public String toString(){
return String.format("PublishTicket(%s,%s)",membership.getMyAddress(),entry);
}
}
}