Skip to content

Commit 5453c1a

Browse files
Handle TimeoutException During Agent Replacement in agent configuration (#5210)
* adding timeout check in update agent async call during agent configuration * adding string for timeout error * adding timeout exection handling * minor fixes * removing unused string * added string for logs * using common function for update agent async call and retry mechanism * Restrict retries in UpdateAgentWithRetryAsync to only timeout-related exceptions
1 parent c01ad6a commit 5453c1a

File tree

2 files changed

+71
-20
lines changed

2 files changed

+71
-20
lines changed

src/Agent.Listener/Configuration/ConfigurationManager.cs

Lines changed: 70 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@
2323
using Newtonsoft.Json;
2424
using Microsoft.Win32;
2525
using Microsoft.VisualStudio.Services.Agent.Listener.Telemetry;
26+
using Microsoft.TeamFoundation.Test.WebApi;
2627

2728
namespace Microsoft.VisualStudio.Services.Agent.Listener.Configuration
2829
{
@@ -45,6 +46,8 @@ public sealed class ConfigurationManager : AgentService, IConfigurationManager
4546

4647
private const string VsTelemetryRegPath = @"SOFTWARE\Microsoft\VisualStudio\Telemetry\PersistentPropertyBag\c57a9efce9b74de382d905a89852db71";
4748
private const string VsTelemetryRegKey = "IsPipelineAgent";
49+
private const int _maxRetries = 3;
50+
private const int _delaySeconds = 2;
4851

4952
public override void Initialize(IHostContext hostContext)
5053
{
@@ -252,18 +255,15 @@ public async Task ConfigureAsync(CommandSettings command)
252255
{
253256
// Update existing agent with new PublicKey, agent version and SystemCapabilities.
254257
agent = UpdateExistingAgent(agent, publicKey, systemCapabilities);
255-
256-
try
258+
agent = await UpdateAgentWithRetryAsync<TaskAgent>(
259+
() => agentProvider.UpdateAgentAsync(agentSettings, agent, command),
260+
command
261+
);
262+
if (agent != null)
257263
{
258-
agent = await agentProvider.UpdateAgentAsync(agentSettings, agent, command);
259264
_term.WriteLine(StringUtil.Loc("AgentReplaced"));
260265
break;
261266
}
262-
catch (Exception e) when (!command.Unattended())
263-
{
264-
_term.WriteError(e);
265-
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
266-
}
267267
}
268268
else if (command.Unattended())
269269
{
@@ -455,6 +455,58 @@ public async Task ConfigureAsync(CommandSettings command)
455455
}
456456
}
457457

458+
private async Task<T> UpdateAgentWithRetryAsync<T>(
459+
Func<Task<T>> operation,
460+
CommandSettings command)
461+
{
462+
int attempt = 0;
463+
while (true)
464+
{
465+
try
466+
{
467+
return await operation();
468+
}
469+
catch (Exception e) when (
470+
e is TimeoutException ||
471+
e is TaskCanceledException ||
472+
(e is OperationCanceledException && !(e is TaskCanceledException))
473+
)
474+
{
475+
attempt++;
476+
if (command.Unattended())
477+
{
478+
if (attempt >= _maxRetries)
479+
{
480+
_term.WriteError(e);
481+
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
482+
Trace.Error($"{operation.Method.Name} failed after maximum retries. Exception: {e}");
483+
throw new InvalidOperationException(StringUtil.Loc("FailedToReplaceAgent"), e);
484+
}
485+
else
486+
{
487+
Trace.Info($"Retrying operation, Attempt: '{attempt}'.");
488+
int backoff = _delaySeconds * (int)Math.Pow(2, attempt - 1);
489+
_term.WriteLine(StringUtil.Loc("RetryingReplaceAgent", attempt, _maxRetries, backoff));
490+
await Task.Delay(TimeSpan.FromSeconds(backoff));
491+
}
492+
}
493+
else
494+
{
495+
_term.WriteError(e);
496+
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
497+
break;
498+
}
499+
}
500+
catch (Exception e)
501+
{
502+
_term.WriteError(e);
503+
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
504+
break;
505+
}
506+
}
507+
return default(T);
508+
}
509+
458510
public async Task UnconfigureAsync(CommandSettings command)
459511
{
460512
ArgUtil.NotNull(command, nameof(command));
@@ -695,19 +747,18 @@ public async Task ReAuthAsync(CommandSettings command)
695747
}
696748
else
697749
{
698-
try
750+
agent.Authorization = new TaskAgentAuthorization
699751
{
700-
agent.Authorization = new TaskAgentAuthorization
701-
{
702-
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
703-
};
704-
agent = await agentProvider.UpdateAgentAsync(agentSettings, agent, command);
705-
_term.WriteLine(StringUtil.Loc("AgentReplaced"));
706-
}
707-
catch (Exception e) when (!command.Unattended())
752+
PublicKey = new TaskAgentPublicKey(publicKey.Exponent, publicKey.Modulus),
753+
};
754+
agent = await UpdateAgentWithRetryAsync<TaskAgent>(
755+
() => agentProvider.UpdateAgentAsync(agentSettings, agent, command),
756+
command
757+
);
758+
if (agent != null)
708759
{
709-
_term.WriteError(e);
710-
_term.WriteError(StringUtil.Loc("FailedToReplaceAgent"));
760+
_term.WriteLine(StringUtil.Loc("AgentReplaced"));
761+
break;
711762
}
712763
}
713764

@@ -838,7 +889,6 @@ private TaskAgent UpdateExistingAgent(TaskAgent agent, RSAParameters publicKey,
838889
{
839890
agent.SystemCapabilities[capability.Key] = capability.Value ?? string.Empty;
840891
}
841-
842892
return agent;
843893
}
844894

src/Misc/layoutbin/en-US/strings.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,7 @@
511511
"RestartMessage": "Restart the machine to launch agent and for autologon settings to take effect.",
512512
"ReStreamLogsToFilesError": "You cannot use --disableloguploads and --reStreamLogsToFiles at the same time!",
513513
"RetryCountLimitExceeded": "The maximum allowed number of attempts is {0} but got {1}. Retry attempts count will be decreased to {0}.",
514+
"RetryingReplaceAgent": "Retrying to replace agent (attempt {0} of {1}). Waiting {2} seconds before next attempt...",
514515
"RMApiFailure": "Api {0} failed with an error code {1}",
515516
"RMArtifactContainerDetailsInvalidError": "The artifact does not have valid container details: {0}",
516517
"RMArtifactContainerDetailsNotFoundError": "The artifact does not contain container details: {0}",

0 commit comments

Comments
 (0)