/* * Licensed to the Apache Software Foundation (ASF) under one or more * contributor license agreements. See the NOTICE file distributed with * this work for additional information regarding copyright ownership. * The ASF licenses this file to You 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 org.apache.ignite.internal.processors.igfs; import java.io.Externalizable; import java.io.IOException; import java.io.ObjectInput; import java.io.ObjectOutput; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.apache.ignite.binary.BinaryObjectException; import org.apache.ignite.binary.BinaryRawReader; import org.apache.ignite.binary.BinaryRawWriter; import org.apache.ignite.binary.BinaryReader; import org.apache.ignite.binary.BinaryWriter; import org.apache.ignite.binary.Binarylizable; import org.apache.ignite.internal.util.tostring.GridToStringInclude; import org.apache.ignite.internal.util.typedef.F; import org.apache.ignite.internal.util.typedef.internal.S; import org.apache.ignite.lang.IgniteUuid; import org.jetbrains.annotations.Nullable; import static org.apache.ignite.internal.processors.igfs.IgfsFileAffinityRange.RANGE_STATUS_MOVED; /** * Auxiliary class that is responsible for managing file affinity keys allocation by ranges. */ public class IgfsFileMap implements Externalizable, Binarylizable { /** */ private static final long serialVersionUID = 0L; /** Sorted list of ranges in ascending order. */ @GridToStringInclude private List<IgfsFileAffinityRange> ranges; /** * Empty constructor. */ public IgfsFileMap() { // No-op. } /** * Constructs same file map as passed in. * * @param old Old map. */ public IgfsFileMap(@Nullable IgfsFileMap old) { if (old != null && old.ranges != null) { ranges = new ArrayList<>(old.ranges.size()); ranges.addAll(old.ranges); } } /** * Gets affinity key from file map based on block start offset. * * @param blockOff Block start offset (divisible by block size). * @param includeMoved If {@code true} then will return affinity key for ranges marked as moved. * Otherwise will return null for such ranges. * @return Affinity key. */ public IgniteUuid affinityKey(long blockOff, boolean includeMoved) { if (ranges == null) return null; assert !ranges.isEmpty(); // Range binary search. int leftIdx = 0, rightIdx = ranges.size() - 1; IgfsFileAffinityRange leftRange = ranges.get(leftIdx); IgfsFileAffinityRange rightRange = ranges.get(rightIdx); // If block offset is less than start of first range, we don't have affinity key. if (leftRange.less(blockOff)) return null; if (leftRange.belongs(blockOff)) return leftRange.status() != RANGE_STATUS_MOVED ? leftRange.affinityKey() : includeMoved ? leftRange.affinityKey() : null; if (rightRange.greater(blockOff)) return null; if (rightRange.belongs(blockOff)) return rightRange.status() != RANGE_STATUS_MOVED ? rightRange.affinityKey() : includeMoved ? leftRange.affinityKey() : null; while (rightIdx - leftIdx > 1) { int midIdx = (leftIdx + rightIdx) / 2; IgfsFileAffinityRange midRange = ranges.get(midIdx); if (midRange.belongs(blockOff)) return midRange.status() != RANGE_STATUS_MOVED ? midRange.affinityKey() : includeMoved ? leftRange.affinityKey() : null; // If offset is less then block start, update right index. if (midRange.less(blockOff)) rightIdx = midIdx; else { assert midRange.greater(blockOff); leftIdx = midIdx; } } // Range was not found. return null; } /** * Updates range status in file map. Will split range into two ranges if given range is a sub-range starting * from the same offset. * * @param range Range to update status. * @param status New range status. */ public void updateRangeStatus(IgfsFileAffinityRange range, int status) { if (ranges == null) throw new IgfsInvalidRangeException("Failed to update range status (file map is empty) " + "[range=" + range + ", ranges=null]"); assert !ranges.isEmpty(); // Check last. int lastIdx = ranges.size() - 1; IgfsFileAffinityRange last = ranges.get(lastIdx); if (last.startOffset() == range.startOffset()) { updateRangeStatus0(lastIdx, last, range, status); return; } // Check first. int firstIdx = 0; IgfsFileAffinityRange first = ranges.get(firstIdx); if (first.startOffset() == range.startOffset()) { updateRangeStatus0(firstIdx, first, range, status); return; } // Binary search. while (lastIdx - firstIdx > 1) { int midIdx = (firstIdx + lastIdx) / 2; IgfsFileAffinityRange midRange = ranges.get(midIdx); if (midRange.startOffset() == range.startOffset()) { updateRangeStatus0(midIdx, midRange, range, status); return; } // If range we are looking for is less if (midRange.less(range.startOffset())) lastIdx = midIdx; else { assert midRange.greater(range.startOffset()); firstIdx = midIdx; } } throw new IgfsInvalidRangeException("Failed to update map for range (corresponding map range " + "was not found) [range=" + range + ", status=" + status + ", ranges=" + ranges + ']'); } /** * Deletes range from map. * * @param range Range to delete. */ public void deleteRange(IgfsFileAffinityRange range) { if (ranges == null) throw new IgfsInvalidRangeException("Failed to remove range (file map is empty) " + "[range=" + range + ", ranges=null]"); assert !ranges.isEmpty(); try { // Check last. int lastIdx = ranges.size() - 1; IgfsFileAffinityRange last = ranges.get(lastIdx); if (last.regionEqual(range)) { assert last.status() == RANGE_STATUS_MOVED; ranges.remove(last); return; } // Check first. int firstIdx = 0; IgfsFileAffinityRange first = ranges.get(firstIdx); if (first.regionEqual(range)) { assert first.status() == RANGE_STATUS_MOVED; ranges.remove(first); return; } // Binary search. while (lastIdx - firstIdx > 1) { int midIdx = (firstIdx + lastIdx) / 2; IgfsFileAffinityRange midRange = ranges.get(midIdx); if (midRange.regionEqual(range)) { assert midRange.status() == RANGE_STATUS_MOVED; ranges.remove(midIdx); return; } // If range we are looking for is less if (midRange.less(range.startOffset())) lastIdx = midIdx; else { assert midRange.greater(range.startOffset()); firstIdx = midIdx; } } } finally { if (ranges.isEmpty()) ranges = null; } throw new IgfsInvalidRangeException("Failed to remove range from file map (corresponding map range " + "was not found) [range=" + range + ", ranges=" + ranges + ']'); } /** * Updates range status at given position (will split range into two if necessary). * * @param origIdx Original range index. * @param orig Original range at index. * @param update Range being updated. * @param status New status for range. */ private void updateRangeStatus0(int origIdx, IgfsFileAffinityRange orig, IgfsFileAffinityRange update, int status) { assert F.eq(orig.affinityKey(), update.affinityKey()); assert ranges.get(origIdx) == orig; if (orig.regionEqual(update)) ranges.set(origIdx, new IgfsFileAffinityRange(update, status)); else { // If range was expanded, new one should be larger. assert orig.endOffset() > update.endOffset(); ranges.set(origIdx, new IgfsFileAffinityRange(update, status)); ranges.add(origIdx + 1, new IgfsFileAffinityRange(update.endOffset() + 1, orig.endOffset(), orig.affinityKey())); } } /** * Gets full list of ranges present in this map. * * @return Unmodifiable list of ranges. */ public List<IgfsFileAffinityRange> ranges() { if (ranges == null) return Collections.emptyList(); return Collections.unmodifiableList(ranges); } /** * Adds range to the list of already existing ranges. Added range must be located after * the last range in this map. If added range is adjacent to the last range in the map, * added range will be concatenated to the last one. * * @param range Range to add. */ public void addRange(IgfsFileAffinityRange range) { if (range == null || range.empty()) return; // We cannot add range in the middle of the file. if (ranges == null) { ranges = new ArrayList<>(); ranges.add(range); return; } assert !ranges.isEmpty(); IgfsFileAffinityRange last = ranges.get(ranges.size() - 1); // Ensure that range being added is located to the right of last range in list. assert last.greater(range.startOffset()) : "Cannot add range to middle of map [last=" + last + ", range=" + range + ']'; // Try to concat last and new range. IgfsFileAffinityRange concat = last.concat(range); // Simply add range to the end of the list if they are not adjacent. if (concat == null) ranges.add(range); else ranges.set(ranges.size() - 1, concat); } /** {@inheritDoc} */ @Override public void writeExternal(ObjectOutput out) throws IOException { if (ranges == null) out.writeInt(-1); else { assert !ranges.isEmpty(); out.writeInt(ranges.size()); for (IgfsFileAffinityRange range : ranges) out.writeObject(range); } } /** {@inheritDoc} */ @Override public void readExternal(ObjectInput in) throws IOException, ClassNotFoundException { int size = in.readInt(); if (size > 0) { ranges = new ArrayList<>(size); for (int i = 0; i < size; i++) ranges.add((IgfsFileAffinityRange)in.readObject()); } } /** {@inheritDoc} */ @Override public void writeBinary(BinaryWriter writer) throws BinaryObjectException { BinaryRawWriter out = writer.rawWriter(); if (ranges == null) out.writeInt(-1); else { assert !ranges.isEmpty(); out.writeInt(ranges.size()); for (IgfsFileAffinityRange range : ranges) out.writeObject(range); } } /** {@inheritDoc} */ @Override public void readBinary(BinaryReader reader) throws BinaryObjectException { BinaryRawReader in = reader.rawReader(); int size = in.readInt(); if (size > 0) { ranges = new ArrayList<>(size); for (int i = 0; i < size; i++) ranges.add((IgfsFileAffinityRange)in.readObject()); } } /** {@inheritDoc} */ @Override public String toString() { return S.toString(IgfsFileMap.class, this); } }