/// <summary> /// Runs a single peer connection, using the IPC link /// </summary> /// <returns>An awaitable task.</returns> /// <param name="self">This peer's information</param> /// <param name="remote">The remote peer's information</param> /// <param name="connecthandler">The method used to obtain the connection.</param> /// <param name="input">The channel for reading requests.</param> /// <param name="maxparallel">The maximum number of parallel handlers</param> private static async Task RunSingleConnection(PeerInfo self, PeerInfo remote, Func <Task <Stream> > connecthandler, IReadChannel <ConnectionRequest> input, int maxparallel) { // Get the local handler for remote requests var remotehandler = Channels.RemoteRequests.Get(); LeanIPC.IPCPeer connection = null; try { if (connecthandler == null) { throw new ArgumentNullException(nameof(connecthandler)); } if (input == null) { throw new ArgumentNullException(nameof(input)); } log.Debug($"Setting up connection to {remote?.Key}"); // Connect to the remote peer connection = new LeanIPC.IPCPeer(await connecthandler()); // Setup a handler for remote requests, that forwards responses from the remote handler connection.AddUserTypeHandler <Protocol.Request>( async(id, req) => { await connection.SendResponseAsync(id, (await remotehandler.SendConnectionRequestAsync(null, null, id, req)).Response); return(true); } ); var mainTask = connection.RunMainLoopAsync(self != remote); Key targetKey = null; // Grab a connection to update the routing table automatically var routingrequests = Channels.RoutingTableRequests.Get(); log.Debug($"Peer connection running {self.Key}, {self.Address}"); using (var tp = new TaskPool <ConnectionRequest>(maxparallel, (t, ex) => log.Warn("Unexpected error handling request", ex))) while (true) { log.Debug($"Peer connection is waiting for request ..."); // Get either a local or a remote request var req = await input.ReadAsync(); log.Debug($"Peer connection got request, handling on taskpool ..."); await tp.Run(req, () => { log.Debug($"Peer connection is forwarding a local {req.Request.Operation} request to the remote"); return(Task.Run(async() => { ConnectionResponse res; try { var p = await connection.SendAndWaitAsync <Protocol.Request, Protocol.Response>(req.Request); if (targetKey == null) { // Record the target key targetKey = p.Self.Key; if (remote == null || remote.Key == null) { remote = new PeerInfo(p.Self.Key, remote.Address); } // Write a registration request to the broker await Channels.ConnectionBrokerRegistrations.Get().WriteAsync( new ConnectionRegistrationRequest() { IsTerminate = false, UpdateRouting = true, Peer = remote } ); log.Debug($"Registering peer in routing table: {remote.Key} {remote.Address} ..."); await routingrequests.AddPeerAsync(remote.Key, remote); } if (p.Peers != null) { log.Debug($"Registering {p.Peers.Count} peers with the routing table ..."); foreach (var peer in p.Peers) { await routingrequests.AddPeerAsync(peer.Key, peer); } log.Debug($"Registered {p.Peers.Count} peers with the routing table"); } res = new ConnectionResponse() { Key = p.Self.Key, Response = p }; } catch (Exception ex) { log.Warn($"Failed to get result, sending error response", ex); res = new ConnectionResponse() { Key = targetKey, Exception = ex }; log.Warn($"Killing peer due to the previous exception"); await input.RetireAsync(); } if (req.Response != null) { try { await req.Response.WriteAsync(res); } catch (Exception ex) { log.Warn("Failed to send response", ex); } } })); }); } } finally { await remotehandler.RetireAsync(); if (connection != null) { try { await connection.ShutdownAsync(); } catch (Exception ex) { log.Warn("Failed to shut down IPC Peer", ex); } } // Write a registration request to the broker await Channels.ConnectionBrokerRegistrations.Get().WriteAsync( new ConnectionRegistrationRequest() { IsTerminate = false, UpdateRouting = false, Peer = remote } ); } }
/// <summary> /// Runs the peer process /// </summary> /// <returns>An awaitable task.</returns> /// <param name="selfinfo">The information describing the peer.</param> /// <param name="k">The redundancy parameter.</param> /// <param name="storesize">The maximum size of the local store</param> /// <param name="maxage">The maximum age of items in the cache</param> /// <param name="initialContactlist">Initial list of peers to contact.</param> /// <param name="requests">The request channel for local management</param> public static async Task RunPeer(PeerInfo selfinfo, int k, int storesize, TimeSpan maxage, EndPoint[] initialContactlist, IReadChannel <PeerRequest> requests) { try { if (selfinfo == null) { throw new ArgumentNullException(nameof(selfinfo)); } if (initialContactlist == null) { throw new ArgumentNullException(nameof(initialContactlist)); } var ip = selfinfo.Address as IPEndPoint; if (ip == null) { throw new ArgumentException($"Unable to convert {nameof(selfinfo.Address)} to a {nameof(IPEndPoint)}", nameof(selfinfo)); } log.Debug($"Starting a peer with key {selfinfo.Key} and address {selfinfo.Address}"); using (var scope = new IsolatedChannelScope()) { var sock = new TcpListener(ip); sock.Start(); // Set up the helper processes var router = RoutingProcess.RunAsync(selfinfo, k); var broker = ConnectionBroker.RunAsync(selfinfo); var values = MRUProcess.RunAsync(selfinfo, storesize, maxage); var remoter = RemoteProcess.RunAsync(selfinfo); log.Debug("Started router, broker, and value store"); // Handle new connections var listener = ListenAsync(selfinfo, sock); log.Debug("Started listener"); // Start discovery of peers var discovery = DiscoveryProcess.RunAsync(selfinfo, initialContactlist); log.Debug("Started discovery"); // The process handling requests to the local node var proc = AutomationExtensions.RunTask( new { Requests = requests, Refresh = Channels.PeerRequests.ForRead, // Add these, so this process terminates the others as well BrokerReg = Channels.ConnectionBrokerRegistrations.ForWrite, BrokerReq = Channels.ConnectionBrokerRequests.ForWrite, BrokerStat = Channels.ConnectionBrokerStats.ForWrite, MRUReq = Channels.MRURequests.ForWrite, MRUStat = Channels.MRUStats.ForWrite, RouteReq = Channels.RoutingTableRequests.ForWrite, RouteStat = Channels.RoutingTableStats.ForWrite, }, async self => { log.Debug("Running peer main loop"); try { while (true) { var req = (await MultiChannelAccess.ReadFromAnyAsync(self.Requests, self.Refresh)).Value; log.Debug($"Peer {selfinfo.Key} got message: {req.Operation}"); switch (req.Operation) { case PeerOperation.Add: await HandleAddOperation(selfinfo, req, k); break; case PeerOperation.Find: await HandleFindOperation(selfinfo, req, k); break; case PeerOperation.Stats: await HandleStatsOperation(req); break; case PeerOperation.Refresh: await HandleRefreshOperation(selfinfo, req, k); break; default: await req.Response.WriteAsync(new PeerResponse() { SuccessCount = -1 }); break; } log.Debug($"Peer {selfinfo.Key} handled message: {req.Operation}"); } } catch (Exception ex) { if (!ex.IsRetiredException()) { log.Warn($"Terminating peer {selfinfo.Key} due to error", ex); } throw; } finally { log.Debug($"Terminating peer {selfinfo.Key}"); } } ); log.Debug("Started main handler"); // Set up a process that periodically emits refresh operations var refresher = AutomationExtensions.RunTask( new { Control = Channels.PeerRequests.ForWrite }, async self => { var respchan = Channel.Create <PeerResponse>(); while (true) { // Sleep, but exit if the parent does if (await Task.WhenAny(Task.Delay(TimeSpan.FromMinutes(10)), proc) == proc) { return; } await self.Control.WriteAsync(new PeerRequest() { Operation = PeerOperation.Refresh, Response = respchan }); await respchan.ReadAsync(); } } ); log.Debug("Started refresh process, peer is now live"); await proc; await router; await broker; await values; await remoter; await discovery; await refresher; await Task.WhenAll(router, broker, values, remoter, discovery, refresher); } } catch (Exception ex) { log.Warn("Failed to start peer", ex); try { await requests.RetireAsync(); } catch (Exception ex2) { log.Warn("Failed to stop the input channel", ex2); } log.Debug($"Peer with key {selfinfo.Key} and address {selfinfo.Address} stopped..."); throw; } }