/// <summary> /// Class constructor /// </summary> public BondWorkerClient( LoggingContext loggingContext, string workerName, string ipAddress, int port, DistributionServices services, EventHandler onActivateConnection, EventHandler onDeactivateConnection, EventHandler onConnectionTimeOut) { m_loggingContext = loggingContext; m_workerName = workerName; m_proxyManager = new BondProxyConnectionManager <WorkerProxyAdapter>( client: new BondTcpClient <WorkerProxyAdapter>( new BondHostLog(loggingContext, ipAddress, port), new BondConnectOptions(true, (uint)EngineEnvironmentSettings.DistributionConnectTimeout.Value.TotalMilliseconds)), loggingContext: loggingContext, services: services, createProxyCallback: client => new WorkerProxyAdapter( ResiliencyTestBondTransportClient.TryWrapClientForTest(DistributedBuildRoles.Master, client))); m_proxyManager.OnActivateConnection += onActivateConnection; m_proxyManager.OnDeactivateConnection += onDeactivateConnection; m_proxyManager.OnConnectionTimeOut += onConnectionTimeOut; m_proxyManager.Start(ipAddress, port, this); }
/// <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>(); }
/// <nodoc/> public void Start(DistributionServices services, EventHandler onConnectionTimeOut) { m_proxyManager = new BondProxyConnectionManager <MasterProxyAdapter>( client: m_bondTcpClient, loggingContext: m_loggingContext, services: services, createProxyCallback: client => new BondMasterClient.MasterProxyAdapter( ResiliencyTestBondTransportClient.TryWrapClientForTest(DistributedBuildRoles.Worker, client))); m_proxyManager.OnConnectionTimeOut += onConnectionTimeOut; m_proxyManager.Start(m_ipAddress, m_port, this); }
/// <summary> /// Class constructor /// </summary> public TracingBondService(int port, BondService innerService, DistributionServices services, LoggingContext loggingContext, params ServiceFunctionRegistration[] registrations) : base(innerService.ServiceName) { Contract.Requires(innerService != null); Contract.Requires(loggingContext != null); Contract.Requires(services != null); Analysis.IgnoreArgument(port); m_services = services; m_innerService = innerService; m_loggingContext = loggingContext; m_stopwatch = Stopwatch.StartNew(); foreach (var registration in registrations) { registration.Register(this); } }
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)); }
public BondWorkerServer(LoggingContext loggingContext, WorkerService workerService, int port, DistributionServices services) { m_loggingContext = loggingContext; m_port = port; m_workerService = workerService; m_tracingBondService = new TracingBondService(port, this, services, loggingContext, TracingBondService.GenerateRegistrationVoid <BuildStartData>("Attach"), TracingBondService.GenerateRegistrationVoid <PipBuildRequest>("ExecutePips"), TracingBondService.GenerateRegistrationVoidVoid("Exit"), TracingBondService.GenerateRegistrationVoidVoid("Heartbeat")); }