// This updates routing table when receiving a message. // if corresponding bucket for remote peer is not full, just adds remote peer. // otherwise check whether if the least recently used (LRU) peer // is alive to determine evict LRU peer or discard remote peer. private async Task UpdateAsync( Peer rawPeer, CancellationToken cancellationToken = default(CancellationToken)) { if (rawPeer is null) { throw new ArgumentNullException(nameof(rawPeer)); } if (!(rawPeer is BoundPeer peer) || rawPeer.AppProtocolVersion != _appProtocolVersion) { // Don't update peer without endpoint or with different appProtocolVersion. return; } if (cancellationToken.IsCancellationRequested) { throw new TaskCanceledException(); } bool contains = _routing.Contains(peer); BoundPeer evictionCandidate = await _routing.AddPeerAsync(peer); if (evictionCandidate is null) { // added successfully since there was empty space in the bucket if (!contains) { _routing.BucketOf(peer).ReplacementCache.Remove(peer); _logger.Debug($"Added [{peer.Address.ToHex()}] to table"); } } else { _logger.Verbose( "Need to evict {Candidate}; trying...", evictionCandidate); try { if (!_routing.BucketOf(peer).ReplacementCache.Contains(peer)) { _routing.BucketOf(peer).ReplacementCache.Add(peer); } await PingAsync( evictionCandidate, _requestTimeout, cancellationToken); } catch (TimeoutException) { await RemovePeerAsync(evictionCandidate); _logger.Verbose( "Peer ({Candidate}) has been evicted.", evictionCandidate); } } }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ImmutableList <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_routing.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestCandidate = _routing.Neighbors(target, _bucketSize).ToList(); Task[] awaitables = peers.Select(peer => PingAsync(peer, _requestTimeout, cancellationToken) ).ToArray(); try { await Task.WhenAll(awaitables); } catch (AggregateException e) { if (e.InnerExceptions.All(ie => ie is TimeoutException) && e.InnerExceptions.Count == awaitables.Length) { throw new TimeoutException( $"All neighbors found do not respond in {_requestTimeout}." ); } _logger.Error( e, "Some responses from neighbors found unexpectedly terminated: {Exception}", e ); } var findNeighboursTasks = new List <Task>(); Peer closestKnown = closestCandidate.Count == 0 ? null : closestCandidate[0]; for (int i = 0; i < Kademlia.FindConcurrency && i < peers.Count; i++) { if (closestKnown is null || string.CompareOrdinal( Kademlia.CalculateDistance(peers[i].Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) < 1) { findNeighboursTasks.Add(FindPeerAsync( target, peers[i], (depth == -1) ? depth : depth + 1, timeout, cancellationToken)); } } try { await Task.WhenAll(findNeighboursTasks); } catch (TimeoutException) { if (findNeighboursTasks.All(findPeerTask => findPeerTask.IsFaulted)) { throw new TimeoutException( "Timeout exception occurred during ProcessFoundAsync()."); } } }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="history"><see cref="Peer"/>s that already searched.</param> /// <param name="dialHistory"><see cref="Peer"/>s that ping sent.</param> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ConcurrentBag <BoundPeer> history, ConcurrentBag <BoundPeer> dialHistory, IEnumerable <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_table.Contains(peer) && !history.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Verbose("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target).ToList(); List <BoundPeer> closestCandidate = _table.Neighbors(target, _table.BucketSize, false).ToList(); Task[] awaitables = peers .Where(peer => !dialHistory.Contains(peer)) .Select( peer => { dialHistory.Add(peer); return(PingAsync(peer, _requestTimeout, cancellationToken)); } ).ToArray(); try { await Task.WhenAll(awaitables); } catch (Exception) { IEnumerable <AggregateException> exceptions = awaitables .Where(t => t.IsFaulted) .Select(t => t.Exception); foreach (var ae in exceptions) { var isTimeout = false; foreach (var ie in ae.InnerExceptions) { if (ie is PingTimeoutException pte) { peers.Remove(pte.Target); isTimeout = true; break; } } if (isTimeout) { break; } _logger.Warning( ae, "Some responses from neighbors found unexpectedly terminated: {ae}", ae ); } } var findPeerTasks = new List <Task>(); Peer closestKnown = closestCandidate.FirstOrDefault(); var count = 0; foreach (var peer in peers) { if (!(closestKnown is null) && string.CompareOrdinal( Kademlia.CalculateDistance(peer.Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) >= 1) { break; } if (history.Contains(peer)) { continue; } findPeerTasks.Add(FindPeerAsync( history, dialHistory, target, peer, depth == -1 ? depth : depth - 1, timeout, cancellationToken)); if (count++ >= _findConcurrency) { break; } } try { await Task.WhenAll(findPeerTasks); } catch (Exception e) { _logger.Error( e, "Some FindPeer tasks were unexpectedly terminated: {Exception}", e ); } }
/// <summary> /// Process <see cref="Peer"/>s that is replied by sending <see cref="FindNeighbors"/> /// request. /// </summary> /// <param name="history"><see cref="Peer"/>s that already searched.</param> /// <param name="found"><see cref="Peer"/>s that found.</param> /// <param name="target">The target <see cref="Address"/> to search.</param> /// <param name="depth">Target depth of recursive operation. If -1 is given, /// it runs until the closest peer is found.</param> /// <param name="timeout"><see cref="TimeSpan"/> for next depth's /// <see cref="FindPeerAsync"/> operation.</param> /// <param name="cancellationToken">A cancellation token used to propagate notification /// that this operation should be canceled.</param> /// <returns>An awaitable task without value.</returns> /// <exception cref="TimeoutException">Thrown when all peers that found are /// not online.</exception> private async Task ProcessFoundAsync( ConcurrentBag <BoundPeer> history, IEnumerable <BoundPeer> found, Address target, int depth, TimeSpan?timeout, CancellationToken cancellationToken) { List <BoundPeer> peers = found.Where( peer => !peer.Address.Equals(_address) && !_routing.Contains(peer) && !history.Contains(peer)).ToList(); if (peers.Count == 0) { _logger.Debug("No any neighbor received."); return; } peers = Kademlia.SortByDistance(peers, target); List <BoundPeer> closestCandidate = _routing.Neighbors(target, _bucketSize).ToList(); Task[] awaitables = peers.Select(peer => PingAsync(peer, _requestTimeout, cancellationToken) ).ToArray(); try { await Task.WhenAll(awaitables); } catch (Exception e) { _logger.Error( e, "Some responses from neighbors found unexpectedly terminated: {Exception}", e ); } var findPeerTasks = new List <Task>(); Peer closestKnown = closestCandidate.Count == 0 ? null : closestCandidate[0]; var count = 0; foreach (var peer in peers) { if (!(closestKnown is null) && string.CompareOrdinal( Kademlia.CalculateDistance(peer.Address, target).ToHex(), Kademlia.CalculateDistance(closestKnown.Address, target).ToHex() ) >= 1) { break; } if (history.Contains(peer)) { continue; } findPeerTasks.Add(FindPeerAsync( history, target, peer, depth == -1 ? depth : depth - 1, timeout, cancellationToken)); if (count++ >= Kademlia.FindConcurrency) { break; } } try { await Task.WhenAll(findPeerTasks); } catch (Exception e) { _logger.Error( e, "Some FindPeer tasks were unexpectedly terminated: {Exception}", e ); } }