Skip to content

Commit 592d607

Browse files
committed
RE1-T118 TTS worker fix
1 parent 3354bc0 commit 592d607

3 files changed

Lines changed: 82 additions & 4 deletions

File tree

Core/Resgrid.Services/TtsAudioService.cs

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,9 @@ public async Task RegenerateStaticPromptsAsync(IEnumerable<string> prompts, Canc
7474

7575
private static Exception CreateRequestFailure(string operation, RestResponse response)
7676
{
77+
if (response.ErrorException is OperationCanceledException or TaskCanceledException)
78+
return new OperationCanceledException($"The TTS service {operation} was canceled.", response.ErrorException);
79+
7780
var status = response.StatusCode == 0 ? "no-response" : response.StatusCode.ToString();
7881
var detail = response.ErrorException?.Message;
7982

Tests/Resgrid.Tests/Workers/Console/Tasks/TtsStaticPromptRefreshTaskTests.cs

Lines changed: 43 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,16 +27,19 @@ public class TtsStaticPromptRefreshTaskTests
2727
private IContainer _testWorkerContainer;
2828
private string _originalServiceBaseUrl;
2929
private string _originalStaticPromptAdminKey;
30+
private TimeSpan _originalRetryDelay;
3031

3132
[SetUp]
3233
public void SetUp()
3334
{
3435
_originalWorkerContainer = WorkerBootstrapperContainerField.GetValue(null) as IContainer;
3536
_originalServiceBaseUrl = TtsConfig.ServiceBaseUrl;
3637
_originalStaticPromptAdminKey = TtsConfig.StaticPromptAdminKey;
38+
_originalRetryDelay = TtsStaticPromptRefreshTask.RetryDelay;
3739

3840
TtsConfig.ServiceBaseUrl = "https://tts.example.com";
3941
TtsConfig.StaticPromptAdminKey = "prompt-admin-key";
42+
TtsStaticPromptRefreshTask.RetryDelay = TimeSpan.FromMilliseconds(1);
4043
}
4144

4245
[TearDown]
@@ -46,10 +49,11 @@ public void TearDown()
4649
_testWorkerContainer?.Dispose();
4750
TtsConfig.ServiceBaseUrl = _originalServiceBaseUrl;
4851
TtsConfig.StaticPromptAdminKey = _originalStaticPromptAdminKey;
52+
TtsStaticPromptRefreshTask.RetryDelay = _originalRetryDelay;
4953
}
5054

5155
[Test]
52-
public async Task process_async_should_rethrow_refresh_failures()
56+
public async Task process_async_should_throw_after_all_retries_exhausted()
5357
{
5458
var failure = new InvalidOperationException("refresh failed");
5559
var ttsAudioService = new Mock<ITtsAudioService>(MockBehavior.Strict);
@@ -66,6 +70,10 @@ await FluentActions
6670
.Should()
6771
.ThrowAsync<InvalidOperationException>()
6872
.WithMessage("refresh failed");
73+
74+
ttsAudioService.Verify(
75+
x => x.RegenerateStaticPromptsAsync(It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()),
76+
Times.Exactly(3));
6977
}
7078

7179
[Test]
@@ -85,6 +93,40 @@ await FluentActions
8593
.Awaiting(() => task.ProcessAsync(new TtsStaticPromptRefreshCommand(1), progress.Object, cancellationTokenSource.Token))
8694
.Should()
8795
.ThrowAsync<OperationCanceledException>();
96+
97+
ttsAudioService.Verify(
98+
x => x.RegenerateStaticPromptsAsync(It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()),
99+
Times.Once);
100+
}
101+
102+
[Test]
103+
public async Task process_async_should_succeed_on_retry_after_initial_failure()
104+
{
105+
var ttsAudioService = new Mock<ITtsAudioService>(MockBehavior.Strict);
106+
var callCount = 0;
107+
ttsAudioService
108+
.Setup(x => x.RegenerateStaticPromptsAsync(It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()))
109+
.Returns(() =>
110+
{
111+
callCount++;
112+
if (callCount == 1)
113+
throw new InvalidOperationException("transient failure");
114+
115+
return Task.CompletedTask;
116+
});
117+
SetWorkerContainer(ttsAudioService.Object);
118+
119+
var task = new TtsStaticPromptRefreshTask(Mock.Of<ILogger>());
120+
var progress = new Mock<IQuidjiboProgress>(MockBehavior.Loose);
121+
122+
await FluentActions
123+
.Awaiting(() => task.ProcessAsync(new TtsStaticPromptRefreshCommand(1), progress.Object, CancellationToken.None))
124+
.Should()
125+
.NotThrowAsync();
126+
127+
ttsAudioService.Verify(
128+
x => x.RegenerateStaticPromptsAsync(It.IsAny<IEnumerable<string>>(), It.IsAny<CancellationToken>()),
129+
Times.Exactly(2));
88130
}
89131

90132
private void SetWorkerContainer(ITtsAudioService ttsAudioService)

Workers/Resgrid.Workers.Console/Tasks/TtsStaticPromptRefreshTask.cs

Lines changed: 36 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,9 @@ namespace Resgrid.Workers.Console.Tasks
1515
{
1616
public class TtsStaticPromptRefreshTask : IQuidjiboHandler<TtsStaticPromptRefreshCommand>
1717
{
18+
public const int MaxRetries = 3;
19+
public static TimeSpan RetryDelay = TimeSpan.FromSeconds(30);
20+
1821
public string Name => "TTS Static Prompt Refresh";
1922
public int Priority => 1;
2023
private readonly ILogger _logger;
@@ -38,11 +41,41 @@ public async Task ProcessAsync(TtsStaticPromptRefreshCommand command, IQuidjiboP
3841
}
3942

4043
var ttsAudioService = Bootstrapper.GetKernel().Resolve<ITtsAudioService>();
44+
var prompts = TwilioVoicePromptCatalog.GetStaticPrompts();
45+
Exception lastException = null;
46+
47+
for (int attempt = 1; attempt <= MaxRetries; attempt++)
48+
{
49+
cancellationToken.ThrowIfCancellationRequested();
4150

42-
_logger.LogInformation("TtsStaticPromptRefresh::Refreshing static prompts");
43-
await ttsAudioService.RegenerateStaticPromptsAsync(TwilioVoicePromptCatalog.GetStaticPrompts(), cancellationToken);
51+
try
52+
{
53+
_logger.LogInformation("TtsStaticPromptRefresh::Refreshing static prompts (attempt {Attempt}/{MaxRetries})", attempt, MaxRetries);
54+
await ttsAudioService.RegenerateStaticPromptsAsync(prompts, cancellationToken);
55+
56+
_logger.LogInformation("TtsStaticPromptRefresh::Successfully refreshed static prompts on attempt {Attempt}", attempt);
57+
progress.Report(100, $"Finishing the {Name} Task");
58+
return;
59+
}
60+
catch (OperationCanceledException)
61+
{
62+
throw;
63+
}
64+
catch (Exception ex)
65+
{
66+
lastException = ex;
67+
68+
if (attempt < MaxRetries)
69+
{
70+
_logger.LogWarning(ex, "TtsStaticPromptRefresh::Attempt {Attempt}/{MaxRetries} failed, retrying in {DelaySeconds}s", attempt, MaxRetries, (int)RetryDelay.TotalSeconds);
71+
await Task.Delay(RetryDelay, cancellationToken);
72+
}
73+
}
74+
}
4475

45-
progress.Report(100, $"Finishing the {Name} Task");
76+
Resgrid.Framework.Logging.LogException(lastException);
77+
_logger.LogError(lastException, "TtsStaticPromptRefresh::Failed to refresh static prompts after {MaxRetries} attempts", MaxRetries);
78+
throw lastException!;
4679
}
4780
catch (OperationCanceledException)
4881
{

0 commit comments

Comments
 (0)