Redo heartbeats to save heart attack events

This commit is contained in:
jetsparrow 2021-04-22 17:41:35 +03:00
parent b0e1957ec8
commit 4131d2f22c
3 changed files with 47 additions and 32 deletions

View File

@ -56,18 +56,25 @@ namespace JetHerald.Controllers
} }
} }
[Route("api/heartbeat")]
[HttpGet]
public Task<IActionResult> HeartbeatGet(HeartbeatArgs args) => DoHeartbeat(args);
private async Task<IActionResult> DoHeartbeat(HeartbeatArgs args) private async Task<IActionResult> DoHeartbeat(HeartbeatArgs args)
{ {
var heart = args.Heart ?? "General";
var t = await Db.GetTopic(args.Topic); var t = await Db.GetTopic(args.Topic);
if (t == null) if (t == null)
return new NotFoundResult(); return new NotFoundResult();
else if (!t.WriteToken.Equals(args.WriteToken, StringComparison.OrdinalIgnoreCase)) else if (!t.WriteToken.Equals(args.WriteToken, StringComparison.Ordinal))
return StatusCode(403); return StatusCode(403);
if (t.ExpiryMessageSent)
await Herald.HeartbeatSent(t);
await Db.AddExpiry(t.Name, args.ExpiryTimeout); var affected = await Db.ReportHeartbeat(t.TopicId, heart, args.ExpiryTimeout);
if (affected == 1)
await Herald.HeartbeatSent(t);
return new OkResult(); return new OkResult();
} }
@ -75,6 +82,7 @@ namespace JetHerald.Controllers
public class HeartbeatArgs public class HeartbeatArgs
{ {
[JsonPropertyName("Topic")] public string Topic; [JsonPropertyName("Topic")] public string Topic;
[JsonPropertyName("Heart")] public string Heart;
[JsonPropertyName("ExpiryTimeout")] public int ExpiryTimeout; [JsonPropertyName("ExpiryTimeout")] public int ExpiryTimeout;
[JsonPropertyName("WriteToken")] public string WriteToken; [JsonPropertyName("WriteToken")] public string WriteToken;
} }

View File

@ -28,11 +28,14 @@ namespace JetHerald
=> Name == Description ? Name : $"{Name}: {Description}"; => Name == Description ? Name : $"{Name}: {Description}";
} }
public class ExpiredTopicChat public class HeartAttack
{ {
public NamespacedId Chat; public ulong HeartattackId { get; set; }
public string Description; public uint TopicId { get; set; }
public string Heart { get; set; }
public DateTime ExpiryTime { get; set; } public DateTime ExpiryTime { get; set; }
public string Description { get; set; }
} }
public async Task<int> DeleteTopic(string name, string adminToken) public async Task<int> DeleteTopic(string name, string adminToken)
@ -131,38 +134,37 @@ namespace JetHerald
new { topicName, chat }); new { topicName, chat });
} }
public Task AddExpiry(string topicName, int addedTime)
public async Task<int> ReportHeartbeat(uint topicId, string heart, int timeoutSeconds)
{ {
using var c = GetConnection(); using var c = GetConnection();
return c.ExecuteAsync( return await c.ExecuteAsync(
" UPDATE topic" + @"
" SET ExpiryTime = CURRENT_TIMESTAMP() + INTERVAL @addedTime SECOND," + INSERT INTO heartbeat
" ExpiryMessageSent = 0" + (TopicId, Heart, ExpiryTime)
" WHERE Name = @topicName", VALUES
new { topicName, addedTime }); (@topicId, @heart, @expiry)
ON DUPLICATE KEY UPDATE
ExpiryTime = @expiry;
",
new { topicId, heart, expiry = DateTime.UtcNow.AddSeconds(timeoutSeconds)});
} }
public Task<IEnumerable<ExpiredTopicChat>> GetExpiredTopics(CancellationToken token = default) public async Task<IEnumerable<HeartAttack>> ProcessHeartAttacks()
{ {
using var c = GetConnection(); using var c = GetConnection();
return c.QueryAsync<ExpiredTopicChat>( return await c.QueryAsync<HeartAttack>("CALL process_heartattacks();");
" SELECT tc.Chat, t.Description, t.ExpiryTime" +
" FROM topic_chat tc" +
" INNER JOIN topic t ON t.TopicId = tc.TopicId" +
" WHERE t.ExpiryTime < CURRENT_TIMESTAMP() AND NOT t.ExpiryMessageSent",
token);
} }
public Task MarkExpiredTopics(CancellationToken token = default) public async Task MarkHeartAttackReported(ulong id)
{ {
using var c = GetConnection(); using var c = GetConnection();
return c.ExecuteAsync( await c.ExecuteAsync("UPDATE heartattack SET Reported = 1 WHERE HeartattackId = @id", new {id});
" UPDATE topic t" +
" SET t.ExpiryMessageSent = 1" +
" WHERE t.ExpiryTime < CURRENT_TIMESTAMP()",
token);
} }
public Db(IOptions<Options.ConnectionStrings> cfg) public Db(IOptions<Options.ConnectionStrings> cfg)
{ {
Config = cfg.Value; Config = cfg.Value;

View File

@ -88,13 +88,18 @@ namespace JetHerald
while (!token.IsCancellationRequested) while (!token.IsCancellationRequested)
{ {
await Task.Delay(1000 * 10, token); await Task.Delay(1000 * 10, token);
try try
{ {
foreach (var chatSent in await Db.GetExpiredTopics(token)) var attacks = await Db.ProcessHeartAttacks();
await SendMessageImpl(chatSent.Chat, $"!{chatSent.Description}!:\nTimeout expired at {chatSent.ExpiryTime}"); foreach (var attack in attacks)
{
await Db.MarkExpiredTopics(token); var chats = await Db.GetChatsForTopic(attack.TopicId);
foreach (var chat in chats)
await SendMessageImpl(chat, $"!{attack.Description}!:\nTimeout expired at {attack.ExpiryTime}");
await Db.MarkHeartAttackReported(attack.HeartattackId);
if (token.IsCancellationRequested)
return;
}
} }
catch (Exception e) catch (Exception e)
{ {