Skip to content

Commit 5621f01

Browse files
committed
feat: Added RedisJobLock to help handle jobs taking "too long", so that when scheduled again they will just exit silently.
1 parent d67cadd commit 5621f01

File tree

4 files changed

+223
-109
lines changed

4 files changed

+223
-109
lines changed

CFLookup/Jobs/GetLatestUpdatedModPerGame.cs

Lines changed: 127 additions & 106 deletions
Original file line numberDiff line numberDiff line change
@@ -25,152 +25,173 @@ public async static Task RunAsync(PerformContext context)
2525

2626
var _db = _redis.GetDatabase(5);
2727

28-
Console.WriteLine("Fetching all games");
28+
using (var jobLock = new RedisJobLock(_redis, "GetLatestUpdatedModPerGame"))
29+
{
30+
if (!await jobLock.TryTakeLockAsync())
31+
{
32+
return;
33+
}
34+
35+
Console.WriteLine("Fetching all games");
2936

30-
var allGames = new List<Game>();
37+
var allGames = new List<Game>();
3138

32-
DateTimeOffset lastUpdatedMod = DateTimeOffset.MinValue;
33-
Mod? latestUpdatedModData = null;
34-
CurseForge.APIClient.Models.Files.File? latestUpdatedFileData = null;
39+
DateTimeOffset lastUpdatedMod = DateTimeOffset.MinValue;
40+
Mod? latestUpdatedModData = null;
41+
CurseForge.APIClient.Models.Files.File? latestUpdatedFileData = null;
3542

36-
var privateGames = await db.ExecuteListAsync<ProcessingGames>(
37-
"SELECT * FROM ProcessingGames WHERE Disabled = 0 AND ModCount > 0"
38-
);
43+
var privateGames = await db.ExecuteListAsync<ProcessingGames>(
44+
"SELECT * FROM ProcessingGames WHERE Disabled = 0 AND ModCount > 0"
45+
);
3946

40-
foreach (var privateGame in privateGames)
41-
{
42-
if (!allGames.Any(g => g.Id == privateGame.GameId))
47+
foreach (var privateGame in privateGames)
4348
{
44-
var game = await cfClient.GetGameAsync(privateGame.GameId);
45-
if (game != null && game.Data != null)
49+
if (!allGames.Any(g => g.Id == privateGame.GameId))
4650
{
47-
allGames.Add(game.Data);
48-
}
51+
var game = await cfClient.GetGameAsync(privateGame.GameId);
52+
if (game != null && game.Data != null)
53+
{
54+
allGames.Add(game.Data);
55+
}
4956

50-
if (game != null && game.Error != null && game.Error.ErrorCode != 404)
51-
{
52-
Console.WriteLine($"Error fetching game info for {privateGame.Id}: {game.Error.ErrorMessage}");
53-
continue;
57+
if (game != null && game.Error != null && game.Error.ErrorCode != 404)
58+
{
59+
Console.WriteLine(
60+
$"Error fetching game info for {privateGame.Id}: {game.Error.ErrorMessage}");
61+
continue;
62+
}
63+
64+
await Task.Delay(100);
5465
}
55-
await Task.Delay(100);
5666
}
57-
}
58-
59-
foreach (var game in allGames)
60-
{
61-
Console.WriteLine($"Starting to check for latest updated mod for {game.Name} (GameId: {game.Id})");
62-
await _db.StringSetAsync($"cf-game-{game.Id}", JsonSerializer.Serialize(game), TimeSpan.FromDays(1));
6367

64-
await Task.Delay(100);
68+
foreach (var game in allGames)
69+
{
70+
Console.WriteLine(
71+
$"Starting to check for latest updated mod for {game.Name} (GameId: {game.Id})");
72+
await _db.StringSetAsync($"cf-game-{game.Id}", JsonSerializer.Serialize(game),
73+
TimeSpan.FromDays(1));
6574

66-
var latestUpdatedMod = await cfClient.SearchModsAsync(game.Id, sortField: ModsSearchSortField.LastUpdated, sortOrder: ModsSearchSortOrder.Descending, pageSize: 1);
75+
await Task.Delay(100);
6776

68-
if (latestUpdatedMod != null && latestUpdatedMod.Error != null)
69-
{
70-
Console.WriteLine($"Error fetching latest updated mod for {game.Name} (GameId: {game.Id}): {latestUpdatedMod.Error.ErrorMessage}");
71-
continue;
72-
}
77+
var latestUpdatedMod = await cfClient.SearchModsAsync(game.Id,
78+
sortField: ModsSearchSortField.LastUpdated, sortOrder: ModsSearchSortOrder.Descending,
79+
pageSize: 1);
7380

74-
if (latestUpdatedMod != null && latestUpdatedMod.Pagination != null && latestUpdatedMod.Pagination.ResultCount > 0)
75-
{
76-
await db.ExecuteNonQueryAsync("UPDATE ProcessingGames SET LastUpdate = GETUTCDATE(), ModCount = @modCount WHERE GameId = @gameId",
77-
new SqlParameter("@modCount", latestUpdatedMod.Pagination.TotalCount),
78-
new SqlParameter("@gameId", game.Id)
79-
);
80-
81-
var mod = latestUpdatedMod.Data.First();
82-
var latestUpdatedFile = mod.LatestFiles.OrderByDescending(f => f.FileDate).FirstOrDefault();
83-
if (latestUpdatedFile != null)
81+
if (latestUpdatedMod != null && latestUpdatedMod.Error != null)
8482
{
85-
Console.WriteLine($"Latest updated mod for {game.Name} (GameId: {game.Id}) is {mod.Name} (ModId: {mod.Id}) with {mod.DownloadCount} downloads and the latest file was updated {latestUpdatedFile.FileDate}");
86-
if (lastUpdatedMod < latestUpdatedFile.FileDate)
87-
{
88-
lastUpdatedMod = latestUpdatedFile.FileDate;
89-
latestUpdatedModData = mod;
90-
latestUpdatedFileData = latestUpdatedFile;
91-
}
92-
93-
await _db.StringSetAsync($"cf-mod-{mod.Id}", JsonSerializer.Serialize(mod), TimeSpan.FromDays(1));
94-
await _db.StringSetAsync($"cf-file-{latestUpdatedFile.Id}", JsonSerializer.Serialize(latestUpdatedFile), TimeSpan.FromDays(1));
83+
Console.WriteLine(
84+
$"Error fetching latest updated mod for {game.Name} (GameId: {game.Id}): {latestUpdatedMod.Error.ErrorMessage}");
85+
continue;
86+
}
9587

96-
var existingGame = await db.ExecuteSingleRowAsync<FileProcessingStatus>(
97-
"SELECT * FROM fileProcessingStatus WHERE gameId = @gameId",
88+
if (latestUpdatedMod != null && latestUpdatedMod.Pagination != null &&
89+
latestUpdatedMod.Pagination.ResultCount > 0)
90+
{
91+
await db.ExecuteNonQueryAsync(
92+
"UPDATE ProcessingGames SET LastUpdate = GETUTCDATE(), ModCount = @modCount WHERE GameId = @gameId",
93+
new SqlParameter("@modCount", latestUpdatedMod.Pagination.TotalCount),
9894
new SqlParameter("@gameId", game.Id)
9995
);
10096

101-
if (existingGame == null)
97+
var mod = latestUpdatedMod.Data.First();
98+
var latestUpdatedFile = mod.LatestFiles.OrderByDescending(f => f.FileDate).FirstOrDefault();
99+
if (latestUpdatedFile != null)
102100
{
103-
// New game, insert it
104-
await db.ExecuteNonQueryAsync(
105-
"INSERT INTO fileProcessingStatus (last_updated_utc, gameId, modId, fileId) VALUES (@last_updated_utc, @gameId, @modId, @fileId)",
106-
new SqlParameter("@last_updated_utc", latestUpdatedFile.FileDate),
107-
new SqlParameter("@gameId", game.Id),
108-
new SqlParameter("@modId", mod.Id),
109-
new SqlParameter("@fileId", latestUpdatedFile.Id)
101+
Console.WriteLine(
102+
$"Latest updated mod for {game.Name} (GameId: {game.Id}) is {mod.Name} (ModId: {mod.Id}) with {mod.DownloadCount} downloads and the latest file was updated {latestUpdatedFile.FileDate}");
103+
if (lastUpdatedMod < latestUpdatedFile.FileDate)
104+
{
105+
lastUpdatedMod = latestUpdatedFile.FileDate;
106+
latestUpdatedModData = mod;
107+
latestUpdatedFileData = latestUpdatedFile;
108+
}
109+
110+
await _db.StringSetAsync($"cf-mod-{mod.Id}", JsonSerializer.Serialize(mod),
111+
TimeSpan.FromDays(1));
112+
await _db.StringSetAsync($"cf-file-{latestUpdatedFile.Id}",
113+
JsonSerializer.Serialize(latestUpdatedFile), TimeSpan.FromDays(1));
114+
115+
var existingGame = await db.ExecuteSingleRowAsync<FileProcessingStatus>(
116+
"SELECT * FROM fileProcessingStatus WHERE gameId = @gameId",
117+
new SqlParameter("@gameId", game.Id)
110118
);
119+
120+
if (existingGame == null)
121+
{
122+
// New game, insert it
123+
await db.ExecuteNonQueryAsync(
124+
"INSERT INTO fileProcessingStatus (last_updated_utc, gameId, modId, fileId) VALUES (@last_updated_utc, @gameId, @modId, @fileId)",
125+
new SqlParameter("@last_updated_utc", latestUpdatedFile.FileDate),
126+
new SqlParameter("@gameId", game.Id),
127+
new SqlParameter("@modId", mod.Id),
128+
new SqlParameter("@fileId", latestUpdatedFile.Id)
129+
);
130+
}
131+
else
132+
{
133+
// Existing game, update it
134+
await db.ExecuteNonQueryAsync(
135+
"UPDATE fileProcessingStatus SET last_updated_utc = @last_updated_utc, modId = @modId, fileId = @fileId WHERE gameId = @gameId",
136+
new SqlParameter("@last_updated_utc", latestUpdatedFile.FileDate),
137+
new SqlParameter("@modId", mod.Id),
138+
new SqlParameter("@fileId", latestUpdatedFile.Id),
139+
new SqlParameter("@gameId", game.Id)
140+
);
141+
}
111142
}
112143
else
113144
{
114-
// Existing game, update it
115-
await db.ExecuteNonQueryAsync(
116-
"UPDATE fileProcessingStatus SET last_updated_utc = @last_updated_utc, modId = @modId, fileId = @fileId WHERE gameId = @gameId",
117-
new SqlParameter("@last_updated_utc", latestUpdatedFile.FileDate),
118-
new SqlParameter("@modId", mod.Id),
119-
new SqlParameter("@fileId", latestUpdatedFile.Id),
120-
new SqlParameter("@gameId", game.Id)
121-
);
145+
Console.WriteLine($"No updated files found for {game.Name} and mod {mod.Name}");
122146
}
123147
}
124148
else
125149
{
126-
Console.WriteLine($"No updated files found for {game.Name} and mod {mod.Name}");
150+
Console.WriteLine($"No mods found for {game.Name}");
127151
}
128152
}
129-
else
130-
{
131-
Console.WriteLine($"No mods found for {game.Name}");
132-
}
133-
}
134153

135-
Console.WriteLine($"Last updated mod was updated {lastUpdatedMod}");
154+
Console.WriteLine($"Last updated mod was updated {lastUpdatedMod}");
136155

137-
if (lastUpdatedMod < DateTimeOffset.UtcNow.AddHours(-3) && latestUpdatedModData != null && latestUpdatedFileData != null)
138-
{
139-
Console.WriteLine("No mods were updated in the last 3 hours, file processing might be down.");
156+
if (lastUpdatedMod < DateTimeOffset.UtcNow.AddHours(-3) && latestUpdatedModData != null &&
157+
latestUpdatedFileData != null)
158+
{
159+
Console.WriteLine("No mods were updated in the last 3 hours, file processing might be down.");
140160

141-
var warned = await _db.StringGetAsync("cf-file-processing-warning");
161+
var warned = await _db.StringGetAsync("cf-file-processing-warning");
142162

143-
if (warned.HasValue && warned == "true")
144-
{
145-
Console.WriteLine("Already warned about this, skipping.");
146-
return;
147-
}
163+
if (warned.HasValue && warned == "true")
164+
{
165+
Console.WriteLine("Already warned about this, skipping.");
166+
return;
167+
}
148168

149-
var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient();
150-
var discordWebhook = Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.Machine) ??
151-
Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.User) ??
152-
Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.Process) ??
153-
string.Empty;
169+
var httpClient = scope.ServiceProvider.GetRequiredService<IHttpClientFactory>().CreateClient();
170+
var discordWebhook =
171+
Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.Machine) ??
172+
Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.User) ??
173+
Environment.GetEnvironmentVariable("DISCORD_WEBHOOK", EnvironmentVariableTarget.Process) ??
174+
string.Empty;
154175

155-
if (!string.IsNullOrWhiteSpace(discordWebhook))
156-
{
157-
var message = @$"No mods were updated in the last 3 hours, file processing might be down.
176+
if (!string.IsNullOrWhiteSpace(discordWebhook))
177+
{
178+
var message = @$"No mods were updated in the last 3 hours, file processing might be down.
158179
Last updated mod was updated {lastUpdatedMod}, and it was {latestUpdatedModData.Name}
159180
(ProjectID: {latestUpdatedModData.Id}, FileId: {latestUpdatedFileData.Id})
160181
https://cflookup.com/{latestUpdatedModData.Id}";
161-
var payload = new
162-
{
163-
content = message,
164-
flags = 4
165-
};
182+
var payload = new
183+
{
184+
content = message,
185+
flags = 4
186+
};
166187

167-
var json = JsonSerializer.Serialize(payload);
168-
var content = new StringContent(json, Encoding.UTF8, "application/json");
169-
await httpClient.PostAsync(discordWebhook, content);
170-
}
188+
var json = JsonSerializer.Serialize(payload);
189+
var content = new StringContent(json, Encoding.UTF8, "application/json");
190+
await httpClient.PostAsync(discordWebhook, content);
191+
}
171192

172-
await _db.StringSetAsync("cf-file-processing-warning", "true", TimeSpan.FromHours(1));
173-
return;
193+
await _db.StringSetAsync("cf-file-processing-warning", "true", TimeSpan.FromHours(1));
194+
}
174195
}
175196
}
176197
}

CFLookup/Program.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -188,7 +188,7 @@ private static async Task Main(string[] args)
188188
app.MapDefaultControllerRoute();
189189

190190
#if !DEBUG
191-
RecurringJob.AddOrUpdate("cflookup:GetLatestUpdatedModPerGame", () => GetLatestUpdatedModPerGame.RunAsync(null), "*/5 * * * *");
191+
RecurringJob.AddOrUpdate("cflookup:GetLatestUpdatedModPerGame", () => GetLatestUpdatedModPerGame.RunAsync(null), "*/15 * * * *");
192192
RecurringJob.AddOrUpdate("cflookup:SaveMinecraftModStats", () => SaveMinecraftModStats.RunAsync(null), Cron.Hourly());
193193
RecurringJob.AddOrUpdate("cflookup:CacheMCStatsOvertime", () => CacheMCOverTime.RunAsync(null), "*/30 * * * *");
194194

0 commit comments

Comments
 (0)