All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.swirlds.virtualmap.internal.reconnect.TwoPhasePessimisticTraversalOrder Maven / Gradle / Ivy

Go to download

Swirlds is a software platform designed to build fully-distributed applications that harness the power of the cloud without servers. Now you can develop applications with fairness in decision making, speed, trust and reliability, at a fraction of the cost of traditional server-based platforms.

There is a newer version: 0.58.3
Show newest version
/*
 * Copyright (C) 2024 Hedera Hashgraph, LLC
 *
 * 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 com.swirlds.virtualmap.internal.reconnect;

import static com.swirlds.virtualmap.internal.Path.ROOT_PATH;

import com.swirlds.common.merkle.synchronization.task.ReconnectNodeCount;
import com.swirlds.virtualmap.internal.Path;
import java.util.Deque;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentLinkedDeque;
import java.util.concurrent.atomic.AtomicReferenceArray;

/**
 * Virtual node traversal policy to traverse the virtual tree bottom-up. It contains two phases,
 * the first includes internal nodes, the second is for leaves.
 *
 * 

The first phase. At the leaf parent rank (the rank of the first leaf path, minus 1) all * internal nodes are split into chunks. Every chunk has a start rank (leaf parent rank, or * first leaf rank for the part of the tree on top of the last rank leaves) and width (number * of internal nodes in the chunk at its rank). For every chunk, its starting path is sent first, * this is the first path at chunk's rank. * *

When a response for an internal node is received from the teacher: if the node is clean, * and it's the left child, a request for its parent node is sent. If the parent is clean and * left, a request is sent for the grand parent, and so on. If the node is dirty, the next * request for this chunk is sent at its starting rank again, skipping all nodes in clear * sub-trees. * *

Since responses are received independently of requests, it may happen that a request * for every chunk are sent, but no responses are there yet. In this case, this class makes a * pessimistic assumption that the previously sent path is dirty, and proceeds to the next * path at chunk's starting rank. It may result in redundant nodes sent (a clean node is sent * before a response for one of its clean parents is received), but it increases overall * throughput. * *

The second phase starts when requests for all internal nodes in all chunks have been * sent (but responses may not have been recorded). At this phase, all leaves are traversed * from the first leaf path to the last leaf path, skipping paths with clean parents. At * this step some clean leaves may be sent redundantly, too. */ public class TwoPhasePessimisticTraversalOrder implements NodeTraversalOrder { private ReconnectNodeCount nodeCount; private long reconnectFirstLeafPath; private long reconnectLastLeafPath; private final Set cleanNodes = ConcurrentHashMap.newKeySet(); // Number of parent node chunks processed in parallel in phase 1 private int chunkCount; // The rank of top-most nodes in every chunk. For example, if chunks are started at // rank 20, and there are 512 chunks, it means chunk height is 11, and chunk stop // rank is 9. Top-most paths of all chunks are at rank 9 private int chunksStopRank; // Start ranks for every chunk. A chunk may start at first leaf rank or first leaf rank - 1 private AtomicReferenceArray chunkStartRanks; // Start path for every chunk. This path is on the chunk's start rank private AtomicReferenceArray chunkStartPaths; // Chunk width, i.e. number of paths in the chunk, at the chunk's start rank private AtomicReferenceArray chunkWidths; // Index of the chunk, in which the last internal path at phase 1 was sent private int lastSentPathChunk; // For every chunk, a list of paths to check next. Initially this is a starting // path for every chunk. Then this list may contain clean nodes' parents. If for a chunk // this list is not empty, paths from the list are sent before "pessimistic" paths // for this chunk private AtomicReferenceArray> chunkNextToCheckPaths; // For every chunk, the next "pessimistic" internal node in the chunk to check. These // nodes are at chunk starting ranks. If such a path appears to be clean, it's skipped // up to the next dirty path or the end of the chunk private Map chunkNextPessimisticPaths; // Used during phase 2 private long lastLeafPath = Path.INVALID_PATH; public TwoPhasePessimisticTraversalOrder() {} @Override public void start(final long firstLeafPath, final long lastLeafPath, final ReconnectNodeCount nodeCount) { this.reconnectFirstLeafPath = firstLeafPath; this.reconnectLastLeafPath = lastLeafPath; this.nodeCount = nodeCount; final int leafParentRank = Path.getRank(firstLeafPath) - 1; if (leafParentRank < 5) { chunkCount = 0; return; // no phase 1, just iterate over all leaves } // Higher the stop rank, less number of chunks. Half of the tree height seems to work well chunksStopRank = leafParentRank / 2; chunkCount = 1 << chunksStopRank; // Height of chunks starting from leaf parent rank. Chunks starting from first leaf rank // will be of minChunkHeight + 1 height final int minChunkHeight = leafParentRank - chunksStopRank; final long firstPathInLeafParentRank = Path.getLeftGrandChildPath(0, leafParentRank); chunkStartPaths = new AtomicReferenceArray<>(chunkCount); chunkWidths = new AtomicReferenceArray<>(chunkCount); chunkStartRanks = new AtomicReferenceArray<>(chunkCount); chunkNextToCheckPaths = new AtomicReferenceArray<>(chunkCount); chunkNextPessimisticPaths = new ConcurrentHashMap<>(chunkCount); for (int i = 0; i < chunkCount; i++) { final long p = firstPathInLeafParentRank + ((long) i << minChunkHeight); final int startRank; final long startPath; final long chunkWidth; if (Path.getLeftChildPath(p) + (2L << minChunkHeight) <= reconnectFirstLeafPath) { startRank = leafParentRank + 1; startPath = Path.getLeftChildPath(p); chunkWidth = 2L << minChunkHeight; } else { startRank = leafParentRank; startPath = p; chunkWidth = 1L << minChunkHeight; } chunkStartPaths.set(i, startPath); chunkStartRanks.set(i, startRank); chunkWidths.set(i, chunkWidth); chunkNextToCheckPaths.set(i, new ConcurrentLinkedDeque<>()); chunkNextPessimisticPaths.put(i, chunkStartPaths.get(i)); } lastSentPathChunk = -1; } @Override public void nodeReceived(final long path, final boolean isClean) { final boolean isLeaf = path >= reconnectFirstLeafPath; if (isLeaf) { nodeCount.incrementLeafCount(); if (isClean) { nodeCount.incrementRedundantLeafCount(); } } else { if (path != 0) { assert chunkCount > 0; final int chunk = getPathChunk(path); if (isClean) { cleanNodes.add(path); // Keep cleanNodes lean. If a parent is clean, its children are clean, too, no // need to keep them in the set cleanNodes.remove(Path.getLeftChildPath(path)); cleanNodes.remove(Path.getRightChildPath(path)); // If clean and left, add the parent to the list of paths to check. Even if // the parent is higher in the tree than chunkStopRank if ((path != 1) && Path.isLeft(path)) { chunkNextToCheckPaths.get(chunk).addFirst(Path.getParentPath(path)); } } else { // At the chunk start rank, every other path (i.e. all right paths) are skipped by // default. If a left sibling is clean, there is no need to check the right sibling, // as a request for the parent will be sent anyway. However, if the left sibling // is dirty, the right sibling may be either dirty, or clean, so a request for it // should be sent final int chunkStartRank = chunkStartRanks.get(chunk); final int pathRank = Path.getRank(path); if ((pathRank == chunkStartRank) && Path.isLeft(path)) { chunkNextToCheckPaths.get(chunk).addLast(path + 1); } } } nodeCount.incrementInternalCount(); if (isClean) { nodeCount.incrementRedundantInternalCount(); } } } @Override public long getNextPathToSend() { long result = -1; if (lastLeafPath == -1) { for (int i = 0; i < chunkCount; i++) { final int chunk = (lastSentPathChunk + 1 + i) % chunkCount; // Check the queue first. If not empty, return a path from there (if not clean) final Deque toCheck = chunkNextToCheckPaths.get(chunk); result = toCheck.isEmpty() ? Path.INVALID_PATH : toCheck.pollFirst(); while ((result != Path.INVALID_PATH) && hasCleanParent(result)) { result = toCheck.isEmpty() ? Path.INVALID_PATH : toCheck.pollFirst(); } if (result != Path.INVALID_PATH) { lastSentPathChunk = chunk; break; } // Otherwise proceed to the next pessimistic path at chunk start rank final long nextPessimisticPath = chunkNextPessimisticPaths.get(chunk); if (nextPessimisticPath == Path.INVALID_PATH) { continue; } result = skipCleanPaths(nextPessimisticPath, getLastChunkPath(chunk)); if (result == Path.INVALID_PATH) { // All remaining paths at chunk start rank are clear. Mark the chunk as done // and try the next chunk chunkNextPessimisticPaths.put(chunk, Path.INVALID_PATH); continue; } // Only left paths are sent at the chunk start rank. If a left path is dirty, then its // right sibling is scheduled to check in nodeReceived(), otherwise a parent will be // checked, and there is no need to send a request for the right sibling assert Path.isLeft(result); lastSentPathChunk = chunk; // Skip to the next left path long next = result + 2; if (next > getLastChunkPath(chunk)) { next = Path.INVALID_PATH; } chunkNextPessimisticPaths.put(chunk, next); break; } } if (result == -1) { result = getNextLeafPath(); } return result; } private long getNextLeafPath() { long path = lastLeafPath == Path.INVALID_PATH ? reconnectFirstLeafPath : lastLeafPath + 1; if ((path > reconnectLastLeafPath) || (reconnectFirstLeafPath < 0)) { return Path.INVALID_PATH; } long result = skipCleanPaths(path, reconnectLastLeafPath); assert (result == Path.INVALID_PATH) || (result >= reconnectFirstLeafPath); return lastLeafPath = result; } private int getPathChunk(long path) { int rank = Path.getRank(path); if (rank < chunksStopRank) { // This may happen if the whole chunk is clean path = Path.getLeftGrandChildPath(path, chunksStopRank - rank); rank = chunksStopRank; } final long pathAtTopRank = Path.getGrandParentPath(path, rank - chunksStopRank); return (int) (pathAtTopRank - Path.getLeftGrandChildPath(0, chunksStopRank)); } private long getLastChunkPath(final int chunk) { return chunkStartPaths.get(chunk) + chunkWidths.get(chunk) - 1; } private boolean hasCleanParent(final long path) { long parent = Path.getParentPath(path); boolean clean = false; while ((parent > 0) && !clean) { clean = cleanNodes.contains(parent); parent = Path.getParentPath(parent); } return clean; } /** * Skip all clean paths starting from the given path at the same rank, un until the limit. If * all paths are clean to the very limit, Path.INVALID_PATH is returned */ private long skipCleanPaths(long path, final long limit) { long result = skipCleanPaths(path); while ((result < limit) && (result != path)) { path = result; result = skipCleanPaths(path); } return (result <= limit) ? result : Path.INVALID_PATH; } /** * For a given path, find its highest parent path in cleanNodes. If such a parent exists, * skip all paths at the original paths's rank in the parent sub-tree and return the first * path after that. If no clean parent is found, the original path is returned */ private long skipCleanPaths(final long path) { assert path > 0; long parent = Path.getParentPath(path); long cleanParent = Path.INVALID_PATH; int parentRanksAbove = 1; int cleanParentRanksAbove = 1; while (parent != ROOT_PATH) { if (cleanNodes.contains(parent)) { cleanParent = parent; cleanParentRanksAbove = parentRanksAbove; } parentRanksAbove++; parent = Path.getParentPath(parent); } final long result; if (cleanParent == Path.INVALID_PATH) { // no clean parent found result = path; } else { result = Path.getRightGrandChildPath(cleanParent, cleanParentRanksAbove) + 1; } assert result >= path; return result; } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy