Skip to content

Commit 9598f7b

Browse files
Fix startup crash when external services are unavailable (#27)
* Initial plan * Fix startup crash by adding retry logic to TelegramConnector Co-authored-by: thomasneuberger <23504477+thomasneuberger@users.noreply.github.com> * Address code review feedback: add constants and improve error handling Co-authored-by: thomasneuberger <23504477+thomasneuberger@users.noreply.github.com> * Improve thread-safety and use bit shifting for exponential backoff Co-authored-by: thomasneuberger <23504477+thomasneuberger@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: thomasneuberger <23504477+thomasneuberger@users.noreply.github.com>
1 parent 05b7982 commit 9598f7b

1 file changed

Lines changed: 99 additions & 18 deletions

File tree

TgHomeBot.Notifications.Telegram/TelegramConnector.cs

Lines changed: 99 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -19,29 +19,98 @@ internal class TelegramConnector(
1919
private readonly TelegramBotClient _botClient = new(options.Value.Token);
2020
private readonly CancellationTokenSource _cancellationTokenSource = new();
2121

22+
private const int MaxRetries = 5;
23+
private const int MaxRetryDelaySeconds = 30;
24+
2225
private string? _botName;
26+
private volatile bool _isConnected;
2327

2428
public async Task Connect()
2529
{
2630
await registeredChatService.LoadRegisteredChats();
2731

28-
var botCommands = commands
29-
.Where(c => !c.HideFromMenu)
30-
.Select(c => new BotCommand { Command = c.Name, Description = c.Description })
31-
.ToList();
32-
33-
foreach (var chat in registeredChatService.RegisteredChats)
34-
{
35-
await _botClient.SetMyCommandsAsync(botCommands, BotCommandScope.Chat(new ChatId(chat.ChatId)), cancellationToken: _cancellationTokenSource.Token);
36-
}
37-
38-
_botClient.StartReceiving(ReceiveUpdate, HandleError, cancellationToken: _cancellationTokenSource.Token);
39-
40-
var bot = await _botClient.GetMeAsync();
32+
// Start connection in background to avoid blocking application startup
33+
_ = Task.Run(async () =>
34+
{
35+
try
36+
{
37+
await ConnectAsync(_cancellationTokenSource.Token);
38+
}
39+
catch (Exception ex)
40+
{
41+
logger.LogError(ex, "Unhandled exception in Telegram connection task: {Exception}", ex.Message);
42+
}
43+
});
44+
}
4145

42-
_botName = bot.Username;
46+
private async Task ConnectAsync(CancellationToken cancellationToken)
47+
{
48+
var retryCount = 0;
4349

44-
logger.LogInformation("Telegram bot connected: {Bot}", bot);
50+
while (!cancellationToken.IsCancellationRequested)
51+
{
52+
try
53+
{
54+
logger.LogInformation("Attempting to connect to Telegram bot (attempt {Attempt}/{MaxRetries})...", retryCount + 1, MaxRetries);
55+
56+
var bot = await _botClient.GetMeAsync(cancellationToken);
57+
_botName = bot.Username;
58+
59+
logger.LogInformation("Telegram bot connected: {Bot}", bot);
60+
61+
var botCommands = commands
62+
.Where(c => !c.HideFromMenu)
63+
.Select(c => new BotCommand { Command = c.Name, Description = c.Description })
64+
.ToList();
65+
66+
foreach (var chat in registeredChatService.RegisteredChats)
67+
{
68+
try
69+
{
70+
await _botClient.SetMyCommandsAsync(botCommands, BotCommandScope.Chat(new ChatId(chat.ChatId)), cancellationToken: cancellationToken);
71+
}
72+
catch (Exception ex)
73+
{
74+
logger.LogWarning(ex, "Failed to set commands for chat {ChatId}", chat.ChatId);
75+
}
76+
}
77+
78+
_botClient.StartReceiving(ReceiveUpdate, HandleError, cancellationToken: cancellationToken);
79+
80+
_isConnected = true;
81+
82+
// Connection successful, exit retry loop
83+
return;
84+
}
85+
catch (OperationCanceledException)
86+
{
87+
logger.LogInformation("Telegram bot connection cancelled during shutdown.");
88+
return;
89+
}
90+
catch (Exception ex)
91+
{
92+
retryCount++;
93+
logger.LogError(ex, "Error connecting to Telegram bot (attempt {Attempt}/{MaxRetries}): {Exception}", retryCount, MaxRetries, ex.Message);
94+
95+
if (retryCount >= MaxRetries)
96+
{
97+
logger.LogError("Failed to connect to Telegram bot after {MaxRetries} attempts. Telegram functionality will be unavailable.", MaxRetries);
98+
return;
99+
}
100+
101+
try
102+
{
103+
var delaySeconds = Math.Min(MaxRetryDelaySeconds, 1 << retryCount);
104+
logger.LogInformation("Retrying Telegram connection in {Delay} seconds...", delaySeconds);
105+
await Task.Delay(TimeSpan.FromSeconds(delaySeconds), cancellationToken);
106+
}
107+
catch (OperationCanceledException)
108+
{
109+
logger.LogInformation("Telegram bot connection retry cancelled during shutdown.");
110+
return;
111+
}
112+
}
113+
}
45114
}
46115

47116
public async Task DisconnectAsync()
@@ -98,11 +167,23 @@ private async Task ReceiveUpdateAsync(ITelegramBotClient client, Update update,
98167

99168
public async Task SendAsync(string message)
100169
{
101-
foreach (var registeredChat in registeredChatService.RegisteredChats)
170+
if (!_isConnected)
102171
{
103-
await _botClient.SendTextMessageAsync(registeredChat.ChatId, message, parseMode: ParseMode.Html);
172+
logger.LogWarning("Cannot send message - Telegram bot is not connected yet.");
173+
return;
174+
}
104175

105-
logger.LogInformation("Message sent to chat {ChatId} with user {User}: {Message}", registeredChat.ChatId, registeredChat.Username, message);
176+
foreach (var registeredChat in registeredChatService.RegisteredChats)
177+
{
178+
try
179+
{
180+
await _botClient.SendTextMessageAsync(registeredChat.ChatId, message, parseMode: ParseMode.Html);
181+
logger.LogInformation("Message sent to chat {ChatId} with user {User}: {Message}", registeredChat.ChatId, registeredChat.Username, message);
182+
}
183+
catch (Exception ex)
184+
{
185+
logger.LogError(ex, "Failed to send message to chat {ChatId}: {Exception}", registeredChat.ChatId, ex.Message);
186+
}
106187
}
107188
}
108189
}

0 commit comments

Comments
 (0)