/* * INESC-ID, Instituto de Engenharia de Sistemas e Computadores Investigação e Desevolvimento em Lisboa * Copyright 2013 INESC-ID and/or its affiliates and other * contributors as indicated by the @author tags. All rights reserved. * See the copyright.txt in the distribution for a full listing of * individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 3.0 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.infinispan.transaction.gmu; import org.infinispan.configuration.cache.Configuration; import org.infinispan.container.versioning.EntryVersion; import org.infinispan.container.versioning.InequalVersionComparisonResult; import org.infinispan.container.versioning.VersionGenerator; import org.infinispan.container.versioning.gmu.GMUReadVersion; import org.infinispan.container.versioning.gmu.GMUVersion; import org.infinispan.container.versioning.gmu.GMUVersionGenerator; import org.infinispan.factories.annotations.Inject; import org.infinispan.factories.annotations.Start; import org.infinispan.factories.annotations.Stop; import org.infinispan.transaction.LocalTransaction; import org.infinispan.transaction.gmu.manager.CommittedTransaction; import org.infinispan.util.Util; import org.infinispan.util.concurrent.IsolationLevel; import org.infinispan.util.logging.Log; import org.infinispan.util.logging.LogFactory; import java.io.BufferedWriter; import java.io.IOException; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.LinkedList; import java.util.Set; import static org.infinispan.container.versioning.InequalVersionComparisonResult.*; import static org.infinispan.container.versioning.gmu.GMUVersion.NON_EXISTING; import static org.infinispan.transaction.gmu.GMUHelper.toGMUVersion; import static org.infinispan.transaction.gmu.GMUHelper.toGMUVersionGenerator; /** * @author Pedro Ruivo * @author Sebastiano Peluso * @since 5.2 */ public class CommitLog { private static final Log log = LogFactory.getLog(CommitLog.class); private GMUVersion mostRecentVersion; private VersionEntry currentVersion; private GMUVersionGenerator versionGenerator; private boolean enabled = false; @Inject public void inject(VersionGenerator versionGenerator, Configuration configuration) { if (configuration.locking().isolationLevel() == IsolationLevel.SERIALIZABLE) { this.versionGenerator = toGMUVersionGenerator(versionGenerator); } enabled = this.versionGenerator != null; } //AFTER THE VersionVCFactory @Start(priority = 31) public void start() { if (!enabled) { return; } currentVersion = new VersionEntry(toGMUVersion(versionGenerator.generateNew()), Collections.emptySet(), 0); mostRecentVersion = toGMUVersion(versionGenerator.generateNew()); } @Stop public void stop() { } public final void initLocalTransaction(LocalTransaction localTransaction) { if (!enabled) { return; } GMUVersion transactionVersion; synchronized (this) { transactionVersion = versionGenerator.updatedVersion(mostRecentVersion); } localTransaction.setTransactionVersion(transactionVersion); } public final synchronized GMUVersion getCurrentVersion() { assertEnabled(); //versions are immutable GMUVersion version = versionGenerator.updatedVersion(mostRecentVersion); //GMUVersion version = versionGenerator.updatedVersion(currentVersion.getVersion()); if (log.isTraceEnabled()) { log.tracef("getCurrentVersion() ==> %s", version); } return version; } public final EntryVersion getOldestVersion() { VersionEntry iterator; synchronized (this) { iterator = currentVersion; } while (iterator.getPrevious() != null) { iterator = iterator.getPrevious(); } return iterator.getVersion(); } public final EntryVersion getEntry(EntryVersion entryVersion) { GMUVersion gmuEntryVersion = toGMUVersion(entryVersion); VersionEntry versionEntry; synchronized (this) { versionEntry = currentVersion; } while (versionEntry != null) { if (versionEntry.getVersion().getThisNodeVersionValue() == gmuEntryVersion.getThisNodeVersionValue()) { return versionEntry.getVersion(); } versionEntry = versionEntry.getPrevious(); } return getOldestVersion(); } public final GMUVersion getAvailableVersionLessThan(EntryVersion other) { assertEnabled(); if (other == null) { synchronized (this) { return versionGenerator.updatedVersion(mostRecentVersion); //return versionGenerator.updatedVersion(currentVersion.getVersion()); } } GMUVersion gmuVersion = toGMUVersion(other); if (gmuVersion.getThisNodeVersionValue() != NON_EXISTING) { return gmuVersion; } LinkedList<GMUVersion> possibleVersion = new LinkedList<GMUVersion>(); VersionEntry iterator; synchronized (this) { iterator = currentVersion; } while (iterator != null) { if (isLessOrEquals(iterator.getVersion(), gmuVersion)) { possibleVersion.add(iterator.getVersion()); } iterator = iterator.getPrevious(); } return versionGenerator.mergeAndMax(possibleVersion.toArray(new GMUVersion[possibleVersion.size()])); } public final GMUReadVersion getReadVersion(EntryVersion other) { if (other == null) { return null; } GMUVersion gmuVersion = toGMUVersion(other); GMUReadVersion gmuReadVersion = versionGenerator.convertVersionToRead(gmuVersion); VersionEntry iterator; synchronized (this) { iterator = currentVersion; } while (iterator != null) { if (log.isTraceEnabled()) { log.tracef("getReadVersion(...) ==> comparing %s and %s", iterator.getVersion(), gmuReadVersion); } if (iterator.getVersion().getThisNodeVersionValue() <= gmuReadVersion.getThisNodeVersionValue()) { if (!isLessOrEquals(iterator.getVersion(), gmuVersion)) { if (log.isTraceEnabled()) { log.tracef("getReadVersion(...) ==> comparing %s and %s ==> NOT VISIBLE", iterator.getVersion(), gmuReadVersion); } gmuReadVersion.addNotVisibleSubversion(iterator.getVersion().getThisNodeVersionValue(), iterator.getSubVersion()); } else { if (log.isTraceEnabled()) { log.tracef("getReadVersion(...) ==> comparing %s and %s ==> VISIBLE", iterator.getVersion(), gmuReadVersion); } } } else { if (log.isTraceEnabled()) { log.tracef("getReadVersion(...) ==> comparing %s and %s ==> IGNORE", iterator.getVersion(), gmuReadVersion); } } iterator = iterator.getPrevious(); if (log.isTraceEnabled()) { log.tracef("getReadVersion(...) ==> next version: %s", iterator); } } return gmuReadVersion; } public final synchronized void insertNewCommittedVersions(Collection<CommittedTransaction> transactions) { assertEnabled(); for (CommittedTransaction transaction : transactions) { if (log.isTraceEnabled()) { log.tracef("insertNewCommittedVersions(...) ==> add %s", transaction.getCommitVersion()); } VersionEntry current = new VersionEntry(toGMUVersion(transaction.getCommitVersion()), Util.getAffectedKeys(transaction.getModifications(), null), transaction.getSubVersion()); current.setPrevious(currentVersion); currentVersion = current; mostRecentVersion = versionGenerator.mergeAndMax(mostRecentVersion, currentVersion.getVersion()); } if (log.isTraceEnabled()) { log.tracef("insertNewCommittedVersions(...) ==> %s", currentVersion.getVersion()); } notifyAll(); } public final synchronized void updateMostRecentVersion(EntryVersion newVersion) { /* assertEnabled(); GMUVersion gmuEntryVersion = toGMUVersion(newVersion); if (gmuEntryVersion.getThisNodeVersionValue() > mostRecentVersion.getThisNodeVersionValue()) { log.warn("Cannot update the most recent version to a version higher than " + "the current version"); return; } mostRecentVersion = versionGenerator.mergeAndMax(mostRecentVersion, gmuEntryVersion); */ } public final synchronized boolean waitForVersion(EntryVersion version, long timeout) throws InterruptedException { assertEnabled(); if (timeout < 0) { if (log.isTraceEnabled()) { log.tracef("waitForVersion(%s,%s) and current version is %s", version, timeout, currentVersion.getVersion()); } long versionValue = toGMUVersion(version).getThisNodeVersionValue(); while (currentVersion.getVersion().getThisNodeVersionValue() < versionValue) { wait(); } if (log.isTraceEnabled()) { log.tracef("waitForVersion(%s) ==> %s TRUE ?", version, currentVersion.getVersion().getThisNodeVersionValue()); } return true; } long finalTimeout = System.currentTimeMillis() + timeout; long versionValue = toGMUVersion(version).getThisNodeVersionValue(); if (log.isTraceEnabled()) { log.tracef("waitForVersion(%s,%s) and current version is %s", version, timeout, currentVersion.getVersion()); } do { if (currentVersion.getVersion().getThisNodeVersionValue() >= versionValue) { if (log.isTraceEnabled()) { log.tracef("waitForVersion(%s) ==> %s >= %s ?", version, currentVersion.getVersion().getThisNodeVersionValue(), versionValue); } return true; } long waitingTime = finalTimeout - System.currentTimeMillis(); if (waitingTime <= 0) { break; } wait(waitingTime); } while (true); if (log.isTraceEnabled()) { log.tracef("waitForVersion(%s) ==> %s >= %s ?", version, currentVersion.getVersion().getThisNodeVersionValue(), versionValue); } return currentVersion.getVersion().getThisNodeVersionValue() >= versionValue; } public final boolean dumpTo(String filePath) { assertEnabled(); BufferedWriter bufferedWriter = Util.getBufferedWriter(filePath); if (bufferedWriter == null) { return false; } try { VersionEntry iterator; synchronized (this) { //bufferedWriter.write(mostRecentVersion.toString()); iterator = currentVersion; } bufferedWriter.newLine(); while (iterator != null) { iterator.dumpTo(bufferedWriter); iterator = iterator.getPrevious(); } return true; } catch (IOException e) { return false; } finally { Util.close(bufferedWriter); } } /** * removes the older version than {@param minVersion} and returns the minimum usable version to remove the old values * in data container * * @param minVersion the minimum visible version * @return the minimum usable version (to remove entries in data container) */ public final GMUVersion gcOlderVersions(GMUVersion minVersion) { VersionEntry iterator; VersionEntry removeFromHere = null; GMUVersion minimumVisibleVersion = null; synchronized (this) { iterator = currentVersion; } while (iterator != null) { if (isLessOrEquals(iterator.getVersion(), minVersion)) { if (minimumVisibleVersion == null) { minimumVisibleVersion = iterator.getVersion(); removeFromHere = iterator; } } else { minimumVisibleVersion = null; removeFromHere = null; } iterator = iterator.getPrevious(); } while (removeFromHere != null) { VersionEntry previous = removeFromHere.getPrevious(); removeFromHere.setPrevious(null); removeFromHere = previous; } if (log.isTraceEnabled()) { log.tracef("gcOlderVersion(%s) ==> %s", minVersion, minimumVisibleVersion); } return minimumVisibleVersion; } public final int calculateMinimumViewId() { VersionEntry iterator; int minimumViewId; synchronized (this) { minimumViewId = currentVersion.getVersion().getViewId(); iterator = currentVersion.getPrevious(); } while (iterator != null) { minimumViewId = Math.min(minimumViewId, iterator.getVersion().getViewId()); } return minimumViewId; } public boolean tryWaitForVersion(GMUVersion minGMUVersion) { return currentVersion.getVersion().getThisNodeVersionValue() >= minGMUVersion.getThisNodeVersionValue(); } private void assertEnabled() { if (!enabled) { throw new IllegalStateException("Commit Log not enabled!"); } } private boolean isLessOrEquals(EntryVersion version1, EntryVersion version2) { InequalVersionComparisonResult comparisonResult = version1.compareTo(version2); return comparisonResult == BEFORE_OR_EQUAL || comparisonResult == BEFORE || comparisonResult == EQUAL; } private static class VersionEntry { private final GMUVersion version; private final Object[] keysModified; private final int subVersion; private VersionEntry previous; private VersionEntry(GMUVersion version, Set<Object> keysModified, int subVersion) { this.version = version; if (keysModified == null) { this.keysModified = null; } else { this.keysModified = keysModified.toArray(new Object[keysModified.size()]); } this.subVersion = subVersion; } public GMUVersion getVersion() { return version; } public VersionEntry getPrevious() { return previous; } public void setPrevious(VersionEntry previous) { this.previous = previous; } public int getSubVersion() { return subVersion; } @Override public String toString() { return "VersionEntry{" + "version=" + version + ", subVersion=" + subVersion + ", keysModified=" + (keysModified == null ? "ALL" : Arrays.asList(keysModified)) + '}'; } public final void dumpTo(BufferedWriter writer) throws IOException { writer.write(version.toString()); writer.write("="); writer.write((keysModified == null ? "ALL" : Arrays.asList(keysModified).toString())); writer.newLine(); writer.flush(); } } }