Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 73 additions & 0 deletions src/ModularPipelines/Engine/ModuleBehaviorMetadata.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
using ModularPipelines.Modules.Behaviors;

namespace ModularPipelines.Engine;

/// <summary>
/// Cached metadata for module behavior interfaces.
/// </summary>
/// <remarks>
/// This caches the result of runtime type checks for behavior interfaces
/// to avoid repeated <c>is</c> checks during module execution.
///
/// Behavior interfaces:
/// - ISkippable: Module can be skipped based on conditions
/// - IHookable: Module has before/after execution hooks
/// - ITimeoutable: Module has a timeout configuration
/// - IRetryable: Module has a retry policy
/// - IIgnoreFailures: Module failures are non-fatal
/// - IAlwaysRun: Module runs regardless of pipeline cancellation
/// </remarks>
internal sealed record ModuleBehaviorMetadata
{
/// <summary>
/// Gets a value indicating whether the module implements ISkippable.
/// </summary>
public bool IsSkippable { get; init; }

/// <summary>
/// Gets a value indicating whether the module implements IHookable.
/// </summary>
public bool IsHookable { get; init; }

/// <summary>
/// Gets a value indicating whether the module implements ITimeoutable.
/// </summary>
public bool IsTimeoutable { get; init; }

/// <summary>
/// Gets a value indicating whether the module implements IRetryable.
/// </summary>
public bool IsRetryable { get; init; }

/// <summary>
/// Gets a value indicating whether the module implements IIgnoreFailures.
/// </summary>
public bool IsIgnoreFailures { get; init; }

/// <summary>
/// Gets a value indicating whether the module implements IAlwaysRun.
/// </summary>
public bool IsAlwaysRun { get; init; }

/// <summary>
/// Creates behavior metadata from a module type by checking implemented interfaces.
/// Uses type-safe IsAssignableFrom checks to avoid namespace collision issues.
/// </summary>
/// <param name="moduleType">The module type to analyze.</param>
/// <returns>Metadata containing cached behavior flags.</returns>
public static ModuleBehaviorMetadata FromType(Type moduleType)
{
// Use type-safe IsAssignableFrom instead of string-based name matching
// to avoid namespace collision issues
return new ModuleBehaviorMetadata
{
IsSkippable = typeof(ISkippable).IsAssignableFrom(moduleType),
IsHookable = typeof(IHookable).IsAssignableFrom(moduleType),
IsTimeoutable = typeof(ITimeoutable).IsAssignableFrom(moduleType),
IsRetryable = moduleType.GetInterfaces().Any(i =>
i.IsGenericType && i.GetGenericTypeDefinition() == typeof(IRetryable<>)),
IsIgnoreFailures = typeof(IIgnoreFailures).IsAssignableFrom(moduleType),
IsAlwaysRun = typeof(IAlwaysRun).IsAssignableFrom(moduleType),
};
}
}
4 changes: 2 additions & 2 deletions src/ModularPipelines/Engine/ModuleExecutionPipeline.cs
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,8 @@ private void SetupCancellation(
CancellationToken engineCancellationToken)
{
// AlwaysRun modules don't get cancelled when the engine cancels
// Check both the interface (composition) and the property (inheritance) for backwards compatibility
var isAlwaysRun = module is IAlwaysRun || module.ModuleRunType == ModuleRunType.AlwaysRun;
// ModuleRunType property already checks for IAlwaysRun interface (see IModule.cs)
var isAlwaysRun = module.ModuleRunType == ModuleRunType.AlwaysRun;
if (!isAlwaysRun)
{
// Create a linked token source that cancels when:
Expand Down
26 changes: 22 additions & 4 deletions src/ModularPipelines/Engine/ModuleMetadataCache.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,24 +5,42 @@
namespace ModularPipelines.Engine;

/// <summary>
/// Caches module scheduling metadata to avoid repeated reflection lookups.
/// Caches module metadata to avoid repeated reflection lookups.
/// </summary>
/// <remarks>
/// This cache provides unified access to both:
/// - Scheduling metadata (attributes like NotInParallel, Priority)
/// - Behavior metadata (interfaces like ISkippable, IHookable)
///
/// Using cached metadata avoids repeated reflection and runtime type checks.
/// </remarks>
internal static class ModuleMetadataCache
{
private static readonly ConcurrentDictionary<Type, ModuleSchedulingMetadata> Cache = new();
private static readonly ConcurrentDictionary<Type, ModuleSchedulingMetadata> SchedulingCache = new();
private static readonly ConcurrentDictionary<Type, ModuleBehaviorMetadata> BehaviorCache = new();

/// <summary>
/// Gets the cached scheduling metadata for a module type.
/// </summary>
/// <param name="moduleType">The module type to get metadata for.</param>
/// <returns>The cached metadata containing scheduling attributes.</returns>
public static ModuleSchedulingMetadata GetMetadata(Type moduleType)
public static ModuleSchedulingMetadata GetSchedulingMetadata(Type moduleType)
{
return Cache.GetOrAdd(moduleType, static t => new ModuleSchedulingMetadata
return SchedulingCache.GetOrAdd(moduleType, static t => new ModuleSchedulingMetadata
{
NotInParallelAttribute = t.GetCustomAttribute<NotInParallelAttribute>(),
PriorityAttribute = t.GetCustomAttribute<PriorityAttribute>(),
ExecutionHintAttribute = t.GetCustomAttribute<ExecutionHintAttribute>(),
});
}

/// <summary>
/// Gets the cached behavior metadata for a module type.
/// </summary>
/// <param name="moduleType">The module type to get behavior metadata for.</param>
/// <returns>The cached metadata containing behavior interface flags.</returns>
public static ModuleBehaviorMetadata GetBehaviorMetadata(Type moduleType)
{
return BehaviorCache.GetOrAdd(moduleType, ModuleBehaviorMetadata.FromType);
}
Comment on lines +37 to +45
Copy link

Copilot AI Jan 2, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The ModuleBehaviorMetadata class and GetBehaviorMetadata method are not being used anywhere in the codebase. While the caching infrastructure is in place, no code is actually calling GetBehaviorMetadata to leverage these cached behavior flags. This creates unused code that adds maintenance burden without providing any performance benefit. Either integrate this metadata into the module execution pipeline to replace runtime type checks, or remove it until it's ready to be used.

Copilot uses AI. Check for mistakes.
}
2 changes: 1 addition & 1 deletion src/ModularPipelines/Engine/ModuleScheduler.cs
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,7 @@ public void InitializeModules(IEnumerable<IModule> modules)
var moduleType = state.ModuleType;

// Use cached metadata to avoid repeated reflection lookups
var metadata = ModuleMetadataCache.GetMetadata(moduleType);
var metadata = ModuleMetadataCache.GetSchedulingMetadata(moduleType);

if (metadata.NotInParallelAttribute != null)
{
Expand Down
Loading