Skip to content

Commit

Permalink
refactor(cronjob): use attribute to get expressions and add job
Browse files Browse the repository at this point in the history
  • Loading branch information
GZTimeWalker committed Dec 19, 2024
1 parent 1a822ae commit 7f3cc2e
Show file tree
Hide file tree
Showing 4 changed files with 93 additions and 65 deletions.
1 change: 1 addition & 0 deletions src/GZCTF/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
using GZCTF.Services.Cache;
using GZCTF.Services.Config;
using GZCTF.Services.Container;
using GZCTF.Services.CronJob;
using GZCTF.Services.Mail;
using Microsoft.AspNetCore.DataProtection;
using Microsoft.AspNetCore.HttpOverrides;
Expand Down
22 changes: 22 additions & 0 deletions src/GZCTF/Services/CronJob/CronJobAttribute.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
using System.Reflection;
using Cronos;

namespace GZCTF.Services.CronJob;

[AttributeUsage(AttributeTargets.Method)]
public class CronJobAttribute(string expression) : Attribute
{
public CronExpression Expression { get; } = CronExpression.Parse(expression);
}

public class CronJobNotFoundException(string message) : Exception(message);

public static class CronJobExtensions
{
public static (string, CronJobEntry) ToEntry(this CronJob job)
{
var method = job.Method;
var attr = method.GetCustomAttribute<CronJobAttribute>() ?? throw new CronJobNotFoundException(method.Name);
return (method.Name, new CronJobEntry(job, attr.Expression));
}
}
Original file line number Diff line number Diff line change
@@ -1,25 +1,13 @@
using System.Threading.Channels;
using System.Reflection;
using Cronos;
using GZCTF.Repositories;
using GZCTF.Repositories.Interface;
using GZCTF.Services.Cache;
using Microsoft.Extensions.Caching.Distributed;

namespace GZCTF.Services;
namespace GZCTF.Services.CronJob;

public delegate Task CronJob(AsyncServiceScope scope, ILogger<CronJobService> logger);

public record CronJobEntry(CronJob Job, CronExpression Expression)
{
/// <summary>
/// Create a cron job entry
/// </summary>
/// <param name="job"></param>
/// <param name="expression"></param>
/// <returns></returns>
public static CronJobEntry Create(CronJob job, string expression) =>
new(job, CronExpression.Parse(expression));
}
public record CronJobEntry(CronJob Job, CronExpression Expression);

public class CronJobService(IDistributedCache cache, IServiceScopeFactory provider, ILogger<CronJobService> logger)
: IHostedService, IDisposable
Expand All @@ -37,11 +25,12 @@ public void Dispose()
/// <summary>
/// Add a job to the cron job service
/// </summary>
public bool AddJob(string job, CronJobEntry task)
public bool AddJob(CronJob job)
{
lock (_jobs)
{
if (!_jobs.TryAdd(job, task))
(string name, CronJobEntry entry) = job.ToEntry();
if (!_jobs.TryAdd(name, entry))
return false;
}

Expand All @@ -65,13 +54,15 @@ public bool RemoveJob(string job)

void LaunchCronJob()
{
// container checker, every 3min
AddJob(nameof(CronJobs.ContainerChecker),
CronJobEntry.Create(CronJobs.ContainerChecker, "* * * * *"));
var methods = typeof(DefaultCronJobs).GetMethods(BindingFlags.Static | BindingFlags.Public);
foreach (var method in methods)
{
var attr = method.GetCustomAttribute<CronJobAttribute>();
if (attr is null)
continue;

// bootstrap cache, every 10min
AddJob(nameof(CronJobs.BootstrapCache),
CronJobEntry.Create(CronJobs.BootstrapCache, "*/10 * * * *"));
AddJob(method.CreateDelegate<CronJob>());
}

_timer = new Timer(_ => Task.Run(Execute),
null, TimeSpan.FromSeconds(60 - DateTime.UtcNow.Second), TimeSpan.FromMinutes(1));
Expand Down Expand Up @@ -196,45 +187,3 @@ async Task Execute()
await Task.WhenAll(handles);
}
}

public static class CronJobs
{
public static async Task ContainerChecker(AsyncServiceScope scope, ILogger<CronJobService> logger)
{
var containerRepo = scope.ServiceProvider.GetRequiredService<IContainerRepository>();

foreach (Models.Data.Container container in await containerRepo.GetDyingContainers())
{
await containerRepo.DestroyContainer(container);
logger.SystemLog(
Program.StaticLocalizer[nameof(Resources.Program.CronJob_RemoveExpiredContainer),
container.ContainerId],
TaskStatus.Success, LogLevel.Debug);
}
}

public static async Task BootstrapCache(AsyncServiceScope scope, ILogger<CronJobService> logger)
{
var gameRepo = scope.ServiceProvider.GetRequiredService<IGameRepository>();
var upcoming = await gameRepo.GetUpcomingGames();

if (upcoming.Length <= 0)
return;

var channelWriter = scope.ServiceProvider.GetRequiredService<ChannelWriter<CacheRequest>>();
var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();

foreach (var game in upcoming)
{
var key = CacheKey.ScoreBoard(game);
var value = await cache.GetAsync(key);
if (value is not null)
continue;

await channelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(game));
logger.SystemLog(Program.StaticLocalizer[nameof(Resources.Program.CronJob_BootstrapRankingCache), key],
TaskStatus.Success,
LogLevel.Debug);
}
}
}
56 changes: 56 additions & 0 deletions src/GZCTF/Services/CronJob/DefaultCronJobs.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
using System.Threading.Channels;
using GZCTF.Repositories;
using GZCTF.Repositories.Interface;
using GZCTF.Services.Cache;
using Microsoft.Extensions.Caching.Distributed;
// ReSharper disable UnusedMember.Global

namespace GZCTF.Services.CronJob;

public static class DefaultCronJobs
{
[CronJob("*/3 * * * *")]
public static async Task ContainerChecker(AsyncServiceScope scope, ILogger<CronJobService> logger)
{
logger.SystemLog($"Executing {nameof(ContainerChecker)}", TaskStatus.Pending, LogLevel.Debug);

var containerRepo = scope.ServiceProvider.GetRequiredService<IContainerRepository>();

foreach (Models.Data.Container container in await containerRepo.GetDyingContainers())
{
await containerRepo.DestroyContainer(container);
logger.SystemLog(
Program.StaticLocalizer[nameof(Resources.Program.CronJob_RemoveExpiredContainer),
container.ContainerId],
TaskStatus.Success, LogLevel.Debug);
}
}

[CronJob("*/10 * * * *")]
public static async Task BootstrapCache(AsyncServiceScope scope, ILogger<CronJobService> logger)
{
logger.SystemLog($"Executing {nameof(BootstrapCache)}", TaskStatus.Pending, LogLevel.Debug);

var gameRepo = scope.ServiceProvider.GetRequiredService<IGameRepository>();
var upcoming = await gameRepo.GetUpcomingGames();

if (upcoming.Length <= 0)
return;

var channelWriter = scope.ServiceProvider.GetRequiredService<ChannelWriter<CacheRequest>>();
var cache = scope.ServiceProvider.GetRequiredService<IDistributedCache>();

foreach (var game in upcoming)
{
var key = CacheKey.ScoreBoard(game);
var value = await cache.GetAsync(key);
if (value is not null)
continue;

await channelWriter.WriteAsync(ScoreboardCacheHandler.MakeCacheRequest(game));
logger.SystemLog(Program.StaticLocalizer[nameof(Resources.Program.CronJob_BootstrapRankingCache), key],
TaskStatus.Success,
LogLevel.Debug);
}
}
}

0 comments on commit 7f3cc2e

Please sign in to comment.