private void DeactivateConnection(TrackedConnection connection) { lock (m_syncLock) { if (m_isActiveCompletionSource.Task.IsCompleted) { if (m_isActiveCompletionSource.Task.Result) { m_isActiveCompletionSource = TaskSourceSlim.Create <bool>(); } } if (m_exceededInactivityTimeout) { m_isActiveCompletionSource.TrySetResult(false); } } OnDeactivateConnection?.Invoke(this, EventArgs.Empty); // TODO: Do nothing to actual connection for now. // Consider recycling connection (ie create new BondTcpConnection) if // connection fails too many times or hasn't had a successful call for a significant // interval. // NOTE: connection may be null Analysis.IgnoreArgument(connection); }
/// <summary> /// Class constructor /// </summary> /// <param name="client">the bond tcp client used to create the proxy</param> /// <param name="createProxyCallback">callback used to create a bond proxy</param> /// <param name="loggingContext">the logging context</param> /// <param name="services">shared services for distribution</param> /// <param name="maxConnectionConcurrency">the maximum number of connections</param> public BondProxyConnectionManager( BondTcpClient <TProxy> client, BondTcpClient <TProxy> .CreateProxyCallback createProxyCallback, LoggingContext loggingContext, DistributionServices services, int maxConnectionConcurrency = 1) { m_client = client; m_createProxyCallback = createProxyCallback; m_loggingContext = loggingContext; m_maxConnectionConcurrency = maxConnectionConcurrency; m_services = services; m_bufferManager = services.BufferManager; m_proxySemaphore = new NullSemaphore(); // m_proxySemaphore = new SemaphoreSlim(m_maxConnectionConcurrency); m_stopwatch = Stopwatch.StartNew(); m_heartbeatTimer = new Timer(HeartbeatTimerCallback); m_connections = new TrackedConnection[m_maxConnectionConcurrency]; for (int i = 0; i < m_maxConnectionConcurrency; i++) { m_connections[i] = new TrackedConnection(this); } m_isActiveCompletionSource = TaskSourceSlim.Create <bool>(); }
/// <summary> /// Creates a task for a bond proxy method call /// </summary> /// <typeparam name="TInput">the input type</typeparam> /// <typeparam name="T">the return type</typeparam> /// <param name="connection">the connection containing the active bond proxy</param> /// <param name="input">the input value</param> /// <param name="callTracker">the call tracker containing data about the call state</param> /// <param name="cancellationToken">cancellation token used to cancel the call</param> /// <returns>a task representing the result of the call</returns> private async Task <T> CreateTaskForProxyCall <TInput, T>( TrackedConnection connection, TInput input, BondCallTracker callTracker, CancellationToken cancellationToken = default(CancellationToken)) where TInput : RpcMessageBase, IBondSerializable, new() where T : IBondSerializable, new() { using (var bufferProvider = m_bufferManager.GetBufferProvider()) { IBondAdaptable adaptable = input as IBondAdaptable; if (adaptable != null) { callTracker.OnStateChanged(BondCallState.Converting); adaptable.Adapt(bufferProvider); callTracker.OnStateChanged(BondCallState.Converted); } // Create a cancellation token source which can be disposed after call completes. // Need to unregister cancellation when call completes but this is difficult to do with // the CancellationTokenRegistration returned by CancellationToken.Register since it // is created inside the begin method delegate using (var cancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken)) { input.SenderName = m_thisMachineName; input.SenderId = m_thisMachineId; input.BuildId = m_services.BuildId; var message = new Message <TInput>(input); // Assign checksum so messages can be verified on receipt m_services.AssignChecksum(message); message.Context.PacketHeaders.m_nettrace.m_callID.SetFromSystemGuid(callTracker.CallId); Func <AsyncCallback, object, IAsyncResult> beginMethod = (callback, state) => { var asyncResult = connection.Proxy.BeginRequest(callTracker.FunctionName, message, callback, bufferProvider.Allocator); callTracker.OnStateChanged(BondCallState.InitiatedRequest); cancellationTokenSource.Token.Register(() => connection.Proxy.CancelRequest(callTracker.FunctionName, asyncResult), useSynchronizationContext: false); return(asyncResult); }; // Use the overload which takes an async callback to circumvent need to wait on wait handle in thread pool var resultMessage = await Task.Factory.FromAsync(beginMethod, asyncResult => connection.Proxy.EndRequest <T>(callTracker.FunctionName, asyncResult), state : null); return(resultMessage.Payload.Value); } } }
protected void AddWatchedConnection(IPEndPoint endPoint, string reason) { if (ignoredConnections.ContainsKey(endPoint)) { return; } TrackedConnection tracked; if (trackedConnections.TryGetValue(endPoint, out tracked)) { tracked.PacketsFrom++; tracked.Accumulator++; } else { tracked = new TrackedConnection(endPoint); if (trackedConnections.TryAdd(endPoint, tracked)) { NetLogger.LogWarning("[WATCH:{0}] {1}", endPoint, reason); } } }
public TrackedConnectionScope(TrackedConnection connection) { m_succeeded = false; Connection = connection; }
private async Task <RpcCallResult <T> > CallCore <T>( Func <TrackedConnection, BondCallTracker, Task <T> > callAsync, CancellationToken cancellationToken, string functionName, BondCallTracker callTracker, bool allowInactive = false, Func <T, bool> shouldRetry = null, uint maxTryCount = 0) { Contract.Requires(functionName != null); callTracker = callTracker ?? CreateLoggingCallTracker(functionName); TimeSpan waitForConnectionDuration = default(TimeSpan); Failure lastFailure = null; m_outstandingCalls.AddOrUpdate(functionName, 1, (k, i) => i + 1); // For heartbeat only try once if (maxTryCount == 0) { maxTryCount = DefaultMaxRetryCount; } for (uint retryCount = 0; retryCount < maxTryCount; retryCount++) { callTracker.TryCount = retryCount; if (retryCount != 0) { // For retries, log a call start with the call tracker's updated retry count callTracker.OnStateChanged(BondCallState.Started); // Yield after first iteration to ensure // we don't overflow the stack with async continuations await Task.Yield(); } TrackedConnection connection = null; try { var startWaitForConnection = m_stopwatch.Elapsed; callTracker.OnStateChanged(BondCallState.WaitingForConnection); // Wait for a connection to become active via the a successful heartbeat using (var connectionScope = await WaitForConnectionAsync(callTracker, allowInactive, cancellationToken)) { // Log wait for connection success var iterationWaitForConnectionDuration = GetElapsed(startWaitForConnection); waitForConnectionDuration += iterationWaitForConnectionDuration; callTracker.OnStateChanged(BondCallState.CompletedWaitForConnection); // connection is not returned in the case that the proxy is shutting down or timed out // other case is that this is a failed heartbeat call. In which case, just continue. if (connectionScope.Connection == null) { if (m_isShuttingDown || m_exceededInactivityTimeout) { // Log the failure lastFailure = new RecoverableExceptionFailure(new BuildXLException(m_isShuttingDown ? "Bond RPC Call failure: Proxy is shutting down" : "Bond RPC Call failure: Proxy timed out")); callTracker.LogMessage("Could not retrieve connection. Failure={0}", lastFailure.DescribeIncludingInnerFailures()); callTracker.OnStateChanged(BondCallState.Failed); return(new RpcCallResult <T>(RpcCallResultState.Failed, retryCount + 1, callTracker.TotalDuration, waitForConnectionDuration, lastFailure)); } continue; } connection = connectionScope.Connection; // Make the actual call var result = await callAsync(connection, callTracker); // Check if call should be retried if (shouldRetry != null && shouldRetry(result)) { continue; } // Log the call completion callTracker.OnStateChanged(BondCallState.Succeeded); m_proxyLogger.LogSuccessfulCall(m_loggingContext, functionName, retryCount); connectionScope.MarkSucceeded(); m_services.Counters.AddToCounter(DistributionCounter.SendPipBuildRequestCallDurationMs, (long)callTracker.TotalDuration.TotalMilliseconds); return(new RpcCallResult <T>(result, retryCount + 1, callTracker.TotalDuration, waitForConnectionDuration)); } } catch (OperationCanceledException) { callTracker.OnStateChanged(BondCallState.Canceled); return(new RpcCallResult <T>(RpcCallResultState.Cancelled, retryCount + 1, callTracker.TotalDuration, waitForConnectionDuration)); } catch (Exception ex) { // If shutting down just return the failed result if (ex is ObjectDisposedException && m_isShuttingDown) { lastFailure = new RecoverableExceptionFailure(new BuildXLException("Bond RPC Call failure: Proxy is shutting down", ex)); callTracker.LogMessage("{0}", lastFailure.DescribeIncludingInnerFailures()); callTracker.OnStateChanged(BondCallState.Failed); return(new RpcCallResult <T>(RpcCallResultState.Failed, retryCount + 1, callTracker.TotalDuration, waitForConnectionDuration)); } if (DistributionServices.IsBuildIdMismatchException(ex)) { m_proxyLogger.LogCallException(m_loggingContext, functionName, retryCount, ex); // If a message with different build is received, it means that the sender has participated in a different distributed build. // Then, we need to lose the connection with the sender. OnConnectionTimeOut?.Invoke(this, EventArgs.Empty); return(new RpcCallResult <T>(RpcCallResultState.Failed, retryCount + 1, callTracker.TotalDuration, waitForConnectionDuration)); } // If not a transient exception, log and throw if (!DistributionHelpers.IsTransientBondException(ex, m_services.Counters) && !m_services.IsChecksumMismatchException(ex)) { m_proxyLogger.LogCallException(m_loggingContext, functionName, retryCount, ex); throw; } // Otherwise, the exception is transient, so log exception and try again lastFailure = new RecoverableExceptionFailure(new BuildXLException("Failed Bond RPC call", ex)); callTracker.LogMessage("{0}", lastFailure.DescribeIncludingInnerFailures()); // Deactivate connection so subsequent calls on the proxy will wait for heartbeat before trying to make call. DeactivateConnection(connection); m_services.Counters.AddToCounter(DistributionCounter.FailedSendPipBuildRequestCallDurationMs, (long)callTracker.TotalDuration.TotalMilliseconds); m_services.Counters.IncrementCounter(DistributionCounter.FailedSendPipBuildRequestCount); m_proxyLogger.LogFailedCall(m_loggingContext, functionName, retryCount, lastFailure); } finally { m_outstandingCalls.AddOrUpdate(functionName, 0, (k, i) => i - 1); } } // Exceeded retry count. callTracker.LogMessage("Call failed and exhausted allowed retries. LastFailure={0}", lastFailure?.DescribeIncludingInnerFailures() ?? string.Empty); callTracker.OnStateChanged(BondCallState.Failed); return(new RpcCallResult <T>(RpcCallResultState.Failed, DefaultMaxRetryCount, callTracker.TotalDuration, waitForConnectionDuration, lastFailure)); }