public async Task CreateConnectionsAsync() { try { while (!this.cancellation.Token.IsCancellationRequested) { while (this.connectedPeers.Count < 32 && !this.cancellation.Token.IsCancellationRequested) { ConnectedPeer nextPeer = await CreateNextPeerAsync(); // this method must block and only run on one thread, it's not thread safe if (nextPeer == null) { this.logger.LogInformation("Out off connection candidates..."); break; } _ = Task.Run(() => ConnectAndRunAsync( nextPeer)); // Connecting and running happens on max X threads, so that we can build up connections quickly } this.logger.LogInformation("Waiting 30 seconds.."); await Task.Delay(30000, this.cancellation.Token).ContinueWith(_ => { }); // we do not want to have a TaskCancelledException here } DisconnectAsync(); } catch (Exception e) { this.logger.LogCritical(e.Message); this.FaultReason = e; } }
async Task HandleFailedConnectedPeerAsync(Exception e, ConnectedPeer connection) { if (connection == null) // there was no connection available { return; } if (e == null) // explicit disconnect { Debug.Assert(connection.PeerState.HasFlag(ConnectedPeer.State.Disconnecting)); Debug.Assert(connection.PeerState.HasFlag(ConnectedPeer.State.Disposed)); } else { Debug.Assert(connection.PeerState.HasFlag(ConnectedPeer.State.Failed)); Debug.Assert(connection.PeerState.HasFlag(ConnectedPeer.State.Disposed)); } if (PeerManager.ShouldRecordError(e, this.cancellation.Token.IsCancellationRequested, connection.ToString(), this.logger)) { // set these properties on the loaded connection instance and not only in the repository, so that we can // use the cached collection of Peer objects connection.RemotePeer.LastError = DateTime.UtcNow; await this.peerRepository.UpdatePeerLastErrorAsync(connection.RemotePeer.Id, connection.RemotePeer.LastError); } this.connectedPeers.TryRemove(connection.RemotePeer.Id, out _); }
async Task RunNetworkPeerAsync(ConnectedPeer connectedPeer) { Debug.Assert(connectedPeer.PeerState.HasFlag(ConnectedPeer.State.Connected)); try { await connectedPeer.VersionHandshakeAsync(this.chatClientConfiguration.UserAgentName, this.sessionNonce, this.cancellation.Token); connectedPeer.PeerState |= ConnectedPeer.State.VersionHandshake; this.ownAddresses.TryAdd(connectedPeer.SelfFromPeer.Id, connectedPeer.SelfFromPeer); // update also loaded (cached) instance and not only the repo connectedPeer.RemotePeer.PeerServices = connectedPeer.PeerVersionPayload.Services; // update services connectedPeer.RemotePeer.LastSeen = DateTime.UtcNow; // update success connectedPeer.RemotePeer.LastError = DateTime.MaxValue; // clear error await this.peerRepository.UpdatePeerAfterHandshakeAsync(connectedPeer.RemotePeer); this.messageRelayAddressReceiver.ReceiveMessageRelayRecordAsync(connectedPeer.RemotePeer.IPAddress, connectedPeer.RemotePeer.ProtocolPort, connectedPeer.RemotePeer.PeerServices, connectedPeer.PeerVersionPayload.UserAgent.Text); await connectedPeer.GetAddressesAsync(AddToAddressBookIfNotExistsAsync, this.cancellation.Token); connectedPeer.PeerState |= ConnectedPeer.State.AddrReceived; } catch (Exception e) { await HandleFailedConnectedPeerAsync(e, connectedPeer); } }
public async Task ConnectAndRunAsync(ConnectedPeer createdInstance) { var connectedInstance = await CreateConnectedPeerBlockingOrThrowAsync(createdInstance); if (connectedInstance != null) { this.logger.LogInformation( $"Successfully created connected peer {createdInstance}, loading off to new thread."); await RunNetworkPeerAsync(createdInstance); } }
async Task <ConnectedPeer> CreateConnectedPeerBlockingOrThrowAsync(ConnectedPeer connectedPeer) { try { connectedPeer.PeerState |= ConnectedPeer.State.Connecting; await connectedPeer.ConnectAsync(); this.ownAddresses.TryAdd(connectedPeer.SelfFromSocket.Id, connectedPeer.SelfFromSocket); connectedPeer.PeerState |= ConnectedPeer.State.Connected; return(connectedPeer); } catch (Exception e) { await HandleFailedConnectedPeerAsync(e, connectedPeer); return(null); } }
public async Task <ConnectedPeer> CreateNextPeerAsync() { IReadOnlyList <Peer> allPeers = await this.peerRepository.GetAllPeersAsync(); if (allPeers.Count == 0) { this.logger.LogWarning("No peer addresses in the store - can't select a connection candidate!"); return(null); } // peers, shortest success ago first var hotPeers = allPeers .Where(x => !IsExcluded( x)) // e.g. if it's our own address, if we are already connected, or the last error is less than X minutes ago .OrderByDescending(x => x.PeerServices.HasFlag(PeerServices.MessageRelay)) .ThenBy(x => DateTimeOffset.UtcNow - x.LastSeen) // then, try the best candidates first .ThenByDescending(x => DateTimeOffset.UtcNow - x.LastError) // then, try the candidates where the last error is longest ago .ToList(); if (hotPeers.Count == 0) { this.logger.LogDebug( "After applying the filtering rules, no connection candidates remain for selection."); return(null); } var candidate = hotPeers[0]; this.logger.LogDebug( $"Selected connection candidate {candidate}, last seen {DateTimeOffset.UtcNow - candidate.LastSeen} ago, last error {DateTimeOffset.UtcNow - candidate.LastError} ago."); ConnectedPeer connectedPeer = new ConnectedPeer(candidate, this.loggerFactory); bool addSuccess = this.connectedPeers.TryAdd(connectedPeer.RemotePeer.Id, connectedPeer); Debug.Assert(addSuccess, $"Bug: Peer {candidate} was already in the ConnectedPeers dictionary - that should not happen."); return(connectedPeer); }