com.offbynull.voip.kademlia.FindSubcoroutine.java Source code

Java tutorial

Introduction

Here is the source code for com.offbynull.voip.kademlia.FindSubcoroutine.java

Source

/*
 * Copyright (c) 2015, Kasra Faghihi, All rights reserved.
 * 
 * This library 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 library 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 library.
 */
package com.offbynull.voip.kademlia;

import com.offbynull.coroutines.user.Continuation;
import com.offbynull.peernetic.core.actor.Context;
import com.offbynull.peernetic.core.actor.helpers.AddressTransformer;
import com.offbynull.peernetic.core.actor.helpers.IdGenerator;
import com.offbynull.peernetic.core.actor.helpers.RequestSubcoroutine;
import com.offbynull.peernetic.core.actor.helpers.Subcoroutine;
import com.offbynull.peernetic.core.actor.helpers.SubcoroutineRouter;
import com.offbynull.peernetic.core.actor.helpers.SubcoroutineRouter.AddBehaviour;
import com.offbynull.peernetic.core.actor.helpers.SubcoroutineRouter.Controller;
import com.offbynull.peernetic.core.actor.helpers.SubcoroutineRouter.ForwardResult;
import static com.offbynull.peernetic.core.gateways.log.LogMessage.info;
import com.offbynull.peernetic.core.shuttle.Address;
import static com.offbynull.voip.kademlia.AddressConstants.ROUTER_EXT_HANDLER_RELATIVE_ADDRESS;
import com.offbynull.voip.kademlia.externalmessages.FindRequest;
import com.offbynull.voip.kademlia.externalmessages.FindResponse;
import com.offbynull.voip.kademlia.model.Id;
import com.offbynull.voip.kademlia.model.IdXorMetricComparator;
import com.offbynull.voip.kademlia.model.Node;
import com.offbynull.voip.kademlia.model.NodeNotFoundException;
import com.offbynull.voip.kademlia.model.Router;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.TreeSet;
import org.apache.commons.lang3.Validate;

final class FindSubcoroutine implements Subcoroutine<List<Node>> {

    private final Address subAddress;
    private final Address timerAddress;
    private final Address logAddress;
    private final AddressTransformer addressTransformer;
    private final IdGenerator idGenerator;

    private final Router router;
    private final Id baseId;
    private final Id findId;
    private final int maxResults;
    private final int maxConcurrentRequests;

    private final boolean advertiseSelf;
    private final boolean ignoreSelf;

    public FindSubcoroutine(Address subAddress, State state, Id findId, int maxResults, boolean advertiseSelf,
            boolean ignoreSelf) {
        Validate.notNull(subAddress);
        Validate.notNull(state);
        Validate.notNull(findId);
        Validate.isTrue(maxResults >= 0); // why would anyone ever want 0 results? let thru anyways

        this.subAddress = subAddress;

        this.timerAddress = state.getTimerAddress();
        this.logAddress = state.getLogAddress();
        this.addressTransformer = state.getAddressTransformer();
        this.idGenerator = state.getIdGenerator();

        this.router = state.getRouter();
        this.baseId = state.getBaseId();
        this.findId = findId;
        this.maxResults = maxResults;
        this.maxConcurrentRequests = state.getMaxConcurrentRequestsPerFind();

        this.advertiseSelf = advertiseSelf;
        this.ignoreSelf = ignoreSelf;
    }

    @Override
    public Address getAddress() {
        return subAddress;
    }

    @Override
    public List<Node> run(Continuation cnt) throws Exception {
        Context ctx = (Context) cnt.getContext();

        ctx.addOutgoingMessage(subAddress, logAddress, info("Finding {}", findId));

        // Set up subcoroutine router
        Address routerAddress = subAddress.appendSuffix("finderreq" + idGenerator.generate());
        SubcoroutineRouter msgRouter = new SubcoroutineRouter(routerAddress, ctx);
        Controller msgRouterController = msgRouter.getController();

        // Get initial set of nodes to query from routing table
        List<Node> startNodes = router.find(findId, maxResults, false); // do not include stale nodes, we only want to contact alive nodes
        ctx.addOutgoingMessage(subAddress, logAddress,
                info("Route table entries closest to {}: {}", findId, startNodes));

        // Create sorted set of nodes to contact
        IdXorMetricComparator idClosenessComparator = new IdXorMetricComparator(findId);
        TreeSet<Node> contactSet = new TreeSet<>((x, y) -> idClosenessComparator.compare(x.getId(), y.getId()));
        contactSet.addAll(startNodes);

        // Create a sorted set of nodes to retain closest nodes in
        TreeSet<Node> closestSet = new TreeSet<>((x, y) -> idClosenessComparator.compare(x.getId(), y.getId()));

        // Execute requests
        Map<Subcoroutine<?>, Node> requestSubcoroutineToNodes = new HashMap<>(); // executing requests
        Set<Id> queriedSet = new HashSet<>(); // ids that have already been queried
        while (true) {
            // If there's room left to query more contacts that are closer to findId, do so... 
            while (msgRouterController.size() < maxConcurrentRequests && !contactSet.isEmpty()) {
                // Get next farthest away node to contact
                Node contactNode = contactSet.pollLast();

                // Add it to set of set of ids that have already been queried.. if it's already there, it means that it's already been
                // queried by this find, so skip it...
                boolean added = queriedSet.add(contactNode.getId());
                if (!added) {
                    continue;
                }

                // Add it to the set of closest nodes (will be removed if node fails to respond)
                closestSet.add(contactNode);

                // If we already have maxResult closer nodes to findId, skip this node
                if (closestSet.size() > maxResults) {
                    Node removedNode = closestSet.pollLast();
                    if (removedNode == contactNode) {
                        continue;
                    }
                }

                // Initialize query
                Address destinationAddress = addressTransformer.toAddress(contactNode.getLink())
                        .appendSuffix(ROUTER_EXT_HANDLER_RELATIVE_ADDRESS);
                RequestSubcoroutine<FindResponse> reqSubcoroutine = new RequestSubcoroutine.Builder<FindResponse>()
                        .sourceAddress(routerAddress, idGenerator).destinationAddress(destinationAddress)
                        .timerAddress(timerAddress)
                        .request(new FindRequest(advertiseSelf ? baseId : null, findId, maxResults))
                        .addExpectedResponseType(FindResponse.class).attemptInterval(Duration.ofSeconds(2L))
                        .maxAttempts(5).throwExceptionIfNoResponse(false).build();

                ctx.addOutgoingMessage(subAddress, logAddress, info("Querying node {}", contactNode));

                // Add query to router
                msgRouterController.add(reqSubcoroutine, AddBehaviour.ADD_PRIME_NO_FINISH);
                requestSubcoroutineToNodes.put(reqSubcoroutine, contactNode);
            }

            // If there are no more requests running, it means we're finished
            if (msgRouterController.size() == 0) {
                ctx.addOutgoingMessage(subAddress, logAddress, info("Find complete: {}", closestSet));
                return new ArrayList<>(closestSet);
            }

            // Wait for next messange forward to the router
            cnt.suspend();
            ForwardResult fr = msgRouter.forward();

            // If a request completed from the forwarded message
            if (fr.isForwarded() && fr.isCompleted()) { // calling isCompleted by itself may throw an exception, check isForwarded first
                // Get response
                FindResponse findResponse = (FindResponse) fr.getResult();

                if (findResponse == null) {
                    // If failure, then mark as stale and remove from closest
                    // DONT BOTHER WITH TRYING TO CALCULATE LOCKING/UNLOCKING LOGIC. THE LOGIC WILL BECOME EXTREMELY CONVOLUTED. THE QUERY
                    // DID 5 REQUEST. IF NO ANSWER WAS GIVEN IN THE ALLOTED TIME, THEN MARK AS STALE!
                    Node contactedNode = requestSubcoroutineToNodes.remove(fr.getSubcoroutine());
                    try {
                        // not allowed to mark self as stale -- we may want to find self, but if we do and it's not responsive dont try to
                        // mark it as stale
                        if (!contactedNode.getId().equals(baseId)) {
                            router.stale(contactedNode);
                        }
                    } catch (NodeNotFoundException nnfe) { // may have been removed (already marked as stale) / may not be in routing tree
                        // Do nothing
                    }
                    closestSet.remove(contactedNode);
                } else {
                    // If success, then add returned nodes to contacts
                    Node[] nodes = findResponse.getNodes();
                    contactSet.addAll(Arrays.asList(nodes));

                    // If we don't want to find our own ID / query ourselves... remove any reference to our own ID in the contactSet
                    // TODO: optimize this by removing before it's added to contactSet
                    if (ignoreSelf) {
                        contactSet.removeIf(x -> x.getId().equals(baseId));
                    }
                }
            }
        }
    }

}