/* * Copyright 2008-2009 LinkedIn, Inc * * 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. */ package voldemort.versioning; import java.io.DataInputStream; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.SortedSet; import java.util.TreeMap; import voldemort.annotations.concurrency.NotThreadsafe; import voldemort.utils.ByteUtils; import voldemort.utils.Utils; import com.google.common.collect.Maps; /** * A vector of the number of writes mastered by each node. The vector is stored * sparely, since, in general, writes will be mastered by only one node. This * means implicitly all the versions are at zero, but we only actually store * those greater than zero. * * */ @NotThreadsafe public class VectorClock implements Version, Serializable { private static final long serialVersionUID = 1; private static final int MAX_NUMBER_OF_VERSIONS = Short.MAX_VALUE; /* A map of versions keyed by nodeId */ private final TreeMap<Short, Long> versionMap; /* * The time of the last update on the server on which the update was * performed */ private volatile long timestamp; /** * Construct an empty VectorClock */ public VectorClock() { this(System.currentTimeMillis()); } public TreeMap<Short, Long> getVersionMap() { return versionMap; } public VectorClock(long timestamp) { this.versionMap = new TreeMap<Short, Long>(); this.timestamp = timestamp; } /** * This function is not safe because it may break the pre-condition that * clock entries should be sorted by nodeId * */ @Deprecated public VectorClock(List<ClockEntry> versions, long timestamp) { this.versionMap = new TreeMap<Short, Long>(); this.timestamp = timestamp; for(ClockEntry clockEntry: versions) { this.versionMap.put(clockEntry.getNodeId(), clockEntry.getVersion()); } } /** * Only used for cloning * * @param versionMap * @param timestamp */ private VectorClock(TreeMap<Short, Long> versionMap, long timestamp) { this.versionMap = Utils.notNull(versionMap); this.timestamp = timestamp; } /** * Takes the bytes of a VectorClock and creates a java object from them. For * efficiency reasons the extra bytes can be attached to the end of the byte * array that are not related to the VectorClock * * @param bytes The serialized bytes of the VectorClock */ public VectorClock(byte[] bytes) { this(bytes, 0); } /** * Read the vector clock from the given bytes starting from a particular * offset * * @param bytes The bytes to read from * @param offset The offset to start reading from */ public VectorClock(byte[] bytes, int offset) { if(bytes == null || bytes.length <= offset) throw new IllegalArgumentException("Invalid byte array for serialization--no bytes to read."); int numEntries = ByteUtils.readShort(bytes, offset); int versionSize = bytes[offset + 2]; int entrySize = ByteUtils.SIZE_OF_SHORT + versionSize; int minimumBytes = offset + ByteUtils.SIZE_OF_SHORT + 1 + numEntries * entrySize + ByteUtils.SIZE_OF_LONG; if(bytes.length < minimumBytes) throw new IllegalArgumentException("Too few bytes: expected at least " + minimumBytes + " but found only " + bytes.length + "."); this.versionMap = new TreeMap<Short, Long>(); int index = 3 + offset; for(int i = 0; i < numEntries; i++) { short nodeId = ByteUtils.readShort(bytes, index); long version = ByteUtils.readBytes(bytes, index + ByteUtils.SIZE_OF_SHORT, versionSize); this.versionMap.put(nodeId, version); index += entrySize; } this.timestamp = ByteUtils.readLong(bytes, index); } public static VectorClock createVectorClock(DataInputStream inputStream) { try { final int HEADER_LENGTH = ByteUtils.SIZE_OF_SHORT + ByteUtils.SIZE_OF_BYTE; byte[] header = new byte[HEADER_LENGTH]; inputStream.readFully(header); int numEntries = ByteUtils.readShort(header, 0); byte versionSize = header[ByteUtils.SIZE_OF_SHORT]; int entrySize = ByteUtils.SIZE_OF_SHORT + versionSize; int totalEntrySize = numEntries * entrySize; byte[] vectorClockBytes = new byte[HEADER_LENGTH + totalEntrySize + ByteUtils.SIZE_OF_LONG]; System.arraycopy(header, 0, vectorClockBytes, 0, header.length); inputStream.readFully(vectorClockBytes, HEADER_LENGTH, vectorClockBytes.length - HEADER_LENGTH); return new VectorClock(vectorClockBytes); } catch(IOException e) { throw new IllegalArgumentException("Can't deserialize vectorclock from stream", e); } } public byte[] toBytes() { byte[] serialized = new byte[sizeInBytes()]; toBytes(serialized, 0); return serialized; } public int toBytes(byte[] buf, int offset) { // write the number of versions ByteUtils.writeShort(buf, (short) versionMap.size(), offset); offset += ByteUtils.SIZE_OF_SHORT; // write the size of each version in bytes byte versionSize = ByteUtils.numberOfBytesRequired(getMaxVersion()); buf[offset] = versionSize; offset++; int clockEntrySize = ByteUtils.SIZE_OF_SHORT + versionSize; SortedSet<Short> nodeIds = versionMap.navigableKeySet(); for(Short nodeId: nodeIds) { Long version = versionMap.get(nodeId); ByteUtils.writeShort(buf, nodeId, offset); ByteUtils.writeBytes(buf, version, offset + ByteUtils.SIZE_OF_SHORT, versionSize); offset += clockEntrySize; } ByteUtils.writeLong(buf, this.timestamp, offset); return sizeInBytes(); } public int sizeInBytes() { byte versionSize = ByteUtils.numberOfBytesRequired(getMaxVersion()); return ByteUtils.SIZE_OF_SHORT + 1 + this.versionMap.size() * (ByteUtils.SIZE_OF_SHORT + versionSize) + ByteUtils.SIZE_OF_LONG; } /** * Increment the version info associated with the given node * * @param node The node */ public void incrementVersion(int node, long time) { if(node < 0 || node > Short.MAX_VALUE) throw new IllegalArgumentException(node + " is outside the acceptable range of node ids."); this.timestamp = time; Long version = versionMap.get((short) node); if(version == null) { version = 1L; } else { version = version + 1L; } versionMap.put((short) node, version); if(versionMap.size() >= MAX_NUMBER_OF_VERSIONS) { throw new IllegalStateException("Vector clock is full!"); } } /** * Get new vector clock based on this clock but incremented on index nodeId * * @param nodeId The id of the node to increment * @return A vector clock equal on each element execept that indexed by * nodeId */ public VectorClock incremented(int nodeId, long time) { VectorClock copyClock = this.clone(); copyClock.incrementVersion(nodeId, time); return copyClock; } @Override public VectorClock clone() { return new VectorClock(Maps.newTreeMap(versionMap), this.timestamp); } @Override public boolean equals(Object object) { if(this == object) return true; if(object == null) return false; if(!object.getClass().equals(VectorClock.class)) return false; VectorClock clock = (VectorClock) object; return versionMap.equals(clock.versionMap); } @Override public int hashCode() { return versionMap.hashCode(); } @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("version("); int versionsLeft = versionMap.size(); for(Map.Entry<Short, Long> entry: versionMap.entrySet()) { versionsLeft--; Short node = entry.getKey(); Long version = entry.getValue(); builder.append(node + ":" + version); if(versionsLeft > 0) { builder.append(", "); } } builder.append(")"); builder.append(" ts:" + timestamp); return builder.toString(); } public long getMaxVersion() { long max = -1; for(Long version: versionMap.values()) max = Math.max(version, max); return max; } public VectorClock merge(VectorClock clock) { VectorClock newClock = new VectorClock(); for(Map.Entry<Short, Long> entry: this.versionMap.entrySet()) { newClock.versionMap.put(entry.getKey(), entry.getValue()); } for(Map.Entry<Short, Long> entry: clock.versionMap.entrySet()) { Long version = newClock.versionMap.get(entry.getKey()); if(version == null) { newClock.versionMap.put(entry.getKey(), entry.getValue()); } else { newClock.versionMap.put(entry.getKey(), Math.max(version, entry.getValue())); } } return newClock; } @Override public Occurred compare(Version v) { if(!(v instanceof VectorClock)) throw new IllegalArgumentException("Cannot compare Versions of different types."); return VectorClockUtils.compare(this, (VectorClock) v); } public long getTimestamp() { return this.timestamp; } @Deprecated public List<ClockEntry> getEntries() { List<ClockEntry> clocks = new ArrayList<ClockEntry>(versionMap.size()); for(Map.Entry<Short, Long> entry: versionMap.entrySet()) { clocks.add(new ClockEntry(entry.getKey(), entry.getValue())); } return Collections.unmodifiableList(clocks); } /** * Function to copy values from another VectorClock. This is used for * in-place updates during a Voldemort put operation. * * @param vc The VectorClock object from which the inner values are to be * copied. */ public void copyFromVectorClock(VectorClock vc) { this.versionMap.clear(); this.timestamp = vc.getTimestamp(); for(ClockEntry clockEntry: vc.getEntries()) { this.versionMap.put(clockEntry.getNodeId(), clockEntry.getVersion()); } } }