Skip to content

Commit

Permalink
AoT compatibility
Browse files Browse the repository at this point in the history
This replaces `IsTrimmable` with `IsAotCompatible` which contains better analyzers. As a result, the registrations logic was updated to react to errors.
  • Loading branch information
mburumaxwell committed Jun 5, 2024
1 parent ff0b2af commit 0308cd2
Show file tree
Hide file tree
Showing 8 changed files with 30 additions and 23 deletions.
2 changes: 1 addition & 1 deletion src/Directory.Build.props
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
<Company>Tingle Software</Company>
<Authors>Tingle Software</Authors>
<PackageTags>$(PackageTags);periodic;tasks;cron;cronjob;scheduled</PackageTags>
<IsTrimmable>true</IsTrimmable>
<IsAotCompatible>true</IsAotCompatible>
</PropertyGroup>

<PropertyGroup>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,16 @@ namespace Microsoft.AspNetCore.Builder;
/// </summary>
public static class PeriodicTasksEndpointRouteBuilderExtensions
{
internal const string MapEndpointTrimmerWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may be trimmed if not directly referenced.";
private const string MapEndpointUnreferencedCodeWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may be trimmed if not directly referenced.";
private const string MapEndpointDynamicCodeWarning = "This API may perform reflection on the supplied delegate and its parameters. These types may require generated code and aren't compatible with native AOT applications.";

/// <summary>
/// Maps incoming requests to the paths for periodic tasks.
/// </summary>
/// <param name="endpoints">The <see cref="IEndpointRouteBuilder"/> to add the routes to.</param>
/// <returns>A <see cref="IEndpointConventionBuilder"/> for endpoints associated with periodic tasks.</returns>
[RequiresUnreferencedCode(MapEndpointTrimmerWarning)]
[RequiresUnreferencedCode(MapEndpointUnreferencedCodeWarning)]
[RequiresDynamicCode(MapEndpointDynamicCodeWarning)]
public static IEndpointConventionBuilder MapPeriodicTasks(this IEndpointRouteBuilder endpoints)
{
ArgumentNullException.ThrowIfNull(endpoints);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public List<PeriodicTaskRegistration> GetRegistrations()
{
var registrations = hostOptions.Registrations;
var results = new List<PeriodicTaskRegistration>();
foreach (var (name, type) in registrations)
foreach (var (name, (type, _)) in registrations)
{
var options = optionsMonitor.Get(name);
results.Add(new()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ public void Configure(string? name, PeriodicTaskOptions options)
ArgumentException.ThrowIfNullOrEmpty(name);

// set schedule and timezone from Attribute
var type = tasksHostOptions.Registrations[name];
var (type, _) = tasksHostOptions.Registrations[name];
var attrs = type.GetCustomAttributes(false);
if (attrs.OfType<PeriodicTaskScheduleAttribute>().SingleOrDefault() is PeriodicTaskScheduleAttribute attrSchedule)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,16 +85,14 @@ public PeriodicTasksBuilder Configure(Action<PeriodicTasksHostOptions> configure
{
ArgumentNullException.ThrowIfNull(configure);

var tt = typeof(TTask);

Configure(opt =>
{
if (opt.Registrations.TryGetValue(name, out var r))
{
throw new InvalidOperationException($"A task with the name '{name}' has already been registered. Names are case insensitive.");
}

opt.AddRegistration(name, tt);
opt.AddRegistration<TTask>(name);
});

Services.Configure(name, configure);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ public class PeriodicTasksHostOptions
/// Names must be case-insensitive because they are used to form lock names and the underlying distributed
/// lock provider may not support case sensitivity.
/// For example, using files for locks may fail because file names on Windows are case insensitive.
private readonly Dictionary<string, Type> registrations = new(StringComparer.OrdinalIgnoreCase);
private readonly Dictionary<string, PeriodicTaskTypeRegistration> registrations = new(StringComparer.OrdinalIgnoreCase);

/// <summary>
/// Gets or sets the prefix value for the lock names.
Expand Down Expand Up @@ -64,7 +64,21 @@ public class PeriodicTasksHostOptions
public PeriodicTaskIdFormat DefaultExecutionIdFormat { get; set; } = PeriodicTaskIdFormat.GuidNoDashes;

/// <summary>The periodic tasks registered.</summary>
public IReadOnlyDictionary<string, Type> Registrations => registrations;
public IReadOnlyDictionary<string, PeriodicTaskTypeRegistration> Registrations => registrations;

internal void AddRegistration(string name, [DynamicallyAccessedMembers(TrimmingHelper.Task)] Type type) => registrations.Add(name, type);
internal void AddRegistration<[DynamicallyAccessedMembers(TrimmingHelper.Task)] TTask>(string name)
=> registrations.Add(name, new(typeof(TTask), typeof(IPeriodicTaskRunner<TTask>)));
}

/// <summary>Registration for a periodic task.</summary>
public readonly record struct PeriodicTaskTypeRegistration([DynamicallyAccessedMembers(TrimmingHelper.Task)] Type Type, [DynamicallyAccessedMembers(TrimmingHelper.Task)] Type RunnerType)
{
/// <summary>Deconstructs the registration into its parts.</summary>
/// <param name="type">The type of the periodic task.</param>
/// <param name="runnerType">The type of the runner for the periodic task.</param>
public void Deconstruct(out Type type, out Type runnerType)
{
type = Type;
runnerType = RunnerType;
}
}
12 changes: 3 additions & 9 deletions src/Tingle.PeriodicTasks/Internal/PeriodicTaskRunnerCreator.cs
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Options;
using System.Diagnostics.CodeAnalysis;

namespace Tingle.PeriodicTasks.Internal;

Expand All @@ -11,19 +10,14 @@ internal class PeriodicTaskRunnerCreator(IServiceProvider provider, IOptions<Per
public IPeriodicTaskRunner Create(string name)
{
name = PeriodicTasksBuilder.TrimCommonSuffixes(name, true);
if (!options.Registrations.TryGetValue(name, out var type))
if (!options.Registrations.TryGetValue(name, out var registration))
{
throw new InvalidOperationException($"A periodic task with the name '{name}' does not exist."
+ $" Ensure you call services.AddPeriodicTasks(builder => builder.AddTask(...)) when configuring your host.");
}

return Create(type);
return Create(registration);
}

public IPeriodicTaskRunner Create([DynamicallyAccessedMembers(TrimmingHelper.Task)] Type type)
{
var genericRunnerType = typeof(IPeriodicTaskRunner<>);
var runnerType = genericRunnerType.MakeGenericType(type);
return (IPeriodicTaskRunner)provider.GetRequiredService(runnerType);
}
public IPeriodicTaskRunner Create(PeriodicTaskTypeRegistration registration) => (IPeriodicTaskRunner)provider.GetRequiredService(registration.RunnerType);
}
5 changes: 2 additions & 3 deletions src/Tingle.PeriodicTasks/Internal/PeriodicTasksHost.cs
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken)

// create the tasks
var tasks = new List<Task>();
foreach (var registration in options.Registrations)
foreach (var (name, registration) in options.Registrations)
{
var (name, type) = registration;
var runner = creator.Create(type);
var runner = creator.Create(registration);
tasks.Add(runner.RunAsync(name, stoppingToken));
}

Expand Down

0 comments on commit 0308cd2

Please sign in to comment.