feat: Required dependencies with auto-registration (fixes #2164)#2165
feat: Required dependencies with auto-registration (fixes #2164)#2165
Conversation
- Add new Optional property with default value of true, making dependencies optional by default (module runs even if dependency is not registered) - Mark IgnoreIfNotRegistered as obsolete, mapping it to Optional for backwards compatibility - Update existing tests to use Optional = false where required dependency behavior is needed - Add new test for Optional default behavior BREAKING CHANGE: Dependencies are now optional by default. Use [DependsOn<T>(Optional = false)] for required dependencies.
Update ModuleDependencyValidator to use the 'optional' naming consistently. The error message now references the new Optional property and RequiresDependency attribute instead of the deprecated IgnoreIfNotRegistered. Also update ModuleNotRegisteredExceptionTests to test both optional and required dependency validation scenarios.
- Rename ignoreIfNotRegistered variable to optional for consistency - Update error message to reference Optional and [RequiresDependency<T>] - Add test for GetModuleIfRegistered returning null for unregistered optional dependency
Update ModuleDependencyResolver, DeclaredDependency, and related components to use the new Optional terminology instead of IgnoreIfNotRegistered for consistency with the optional-by-default dependency model. - Update return tuple types from IgnoreIfNotRegistered to Optional - Rename DeclaredDependency.IgnoreIfNotRegistered to IsOptional - Update DependencyGraphValidator and UnusedModuleDetector to use Optional - Update IModuleContext documentation examples - Update related unit tests
When modules are filtered by category or skipped, they now signal completion immediately so dependent modules with optional dependencies don't hang. This adds: - CompletionSourceSetterCache: compiled delegate factory to efficiently set the TaskCompletionSource on ignored modules - CategoryFilterDependencyTests: tests for issue #2164 verifying optional dependencies work correctly when filtered by category Fixes #2164
SummaryThis PR makes [DependsOn] dependencies optional by default, adds [RequiresDependency] for strict dependencies, and fixes issue #2164 where filtered/skipped modules caused dependent modules to hang indefinitely. Critical Issues1. Breaking change without proper backwards compatibility handling The PR changes the default behavior of [DependsOn] from required to optional (Optional = true). While it provides IgnoreIfNotRegistered for backwards compatibility (marked obsolete), this is a breaking API change that will silently change behavior for existing pipelines. This could hide bugs in existing pipelines where dependencies are accidentally not registered. Users upgrading will not get compile-time or runtime warnings that their pipeline behavior has changed. Recommendation: This should be a major version bump (v3.0), or the default should remain Optional = false with a new [DependsOnOptional] attribute instead. 2. Potential reflection-related AOT compatibility issues The new CompletionSourceSetterCache uses reflection to access the internal CompletionSource property (line 218). This may cause issues with AOT/trimming if not properly annotated. While the code uses compiled expressions, it lacks [DynamicallyAccessedMembers] annotations to ensure the reflection targets are preserved during trimming. Recommendation: Add appropriate annotations or avoid reflection by making CompletionSource accessible via an internal interface method. Suggestions1. Consider explicit API design - The new [RequiresDependency] vs [DependsOn] distinction may be confusing since DependsOn usually implies a requirement. A clearer API might be [DependsOn] (required by default) and [DependsOnOptional] (new). 2. Documentation for the fix - The fix for issue #2164 is well-implemented but could explain why it's necessary. Verdict |
This changes the behavior of [DependsOn<T>] to be required by default: - Required (default): Dependencies are automatically registered if not explicitly added to the pipeline. Use GetModule<T>() to access. - Optional = true: Dependencies are not auto-registered. Use GetModuleIfRegistered<T>() to safely check if present. Auto-registration handles transitive dependencies, so if Module2 depends on Module1 which depends on Module0, all will be auto-registered when only Module2 is explicitly added. This addresses issue #2164 where using category filters with dependencies in different categories would cause unexpected failures. Now: - Required deps are auto-registered, ensuring they're always present - Optional deps should be used when dependencies may be filtered out Also: - Removed RequiresDependencyAttribute (no longer needed) - Fixed IgnoredModuleResultRegistrar type mismatch for value types - Updated documentation with clear examples
SummaryThis PR adds auto-registration for required dependencies and fixes issue 2164 where filtered modules caused dependent modules to hang. Critical Issues
Suggestions
VerdictREQUEST CHANGES |
- Fix FailedModule.cs: Change IgnoreIfNotRegistered to Optional = true
- Remove unused TypeExtensions.MakeNullableIfValueType method
- Add null check with exception for GetProperty("CompletionSource")
- Rename tuple parameter from IgnoreIfNotRegistered to Optional
- Update exception documentation to reference Optional instead
SummaryThis PR makes [DependsOn] dependencies required by default with auto-registration, addressing issue #2164 where category filters with dependencies in different categories would cause failures. Critical IssuesNone found ✅ Suggestions1. CompletionSourceSetterCache type safety (IgnoredModuleResultRegistrar.cs:466)The expression-compiled setter in CompletionSourceSetterCache.CreateSetter accesses the CompletionSource property using reflection and makes assumptions about the generic type structure. While this appears to work correctly, consider adding validation after line 465 to catch potential type system issues early. 2. ModuleAutoRegistrar infinite loop protectionThe AutoRegisterMissingDependencies method uses a do-while loop with an addedAny flag to handle transitive dependencies. While the logic looks correct, in a pathological case (e.g., a circular dependency that somehow bypasses validation), this could theoretically loop indefinitely. Consider adding a safety counter with a reasonable upper bound (e.g., 1000 iterations). 3. Error message clarity (DependencyWaiter.cs:360-368)The new error message mentions 'could not be auto-registered' but does not explain why it could not be auto-registered. This could confuse users when a required dependency fails validation (e.g., abstract class, interface, generic type definition). Consider adding diagnostic info about why auto-registration failed (is abstract, is interface, is generic type definition, does not implement IModule, etc). Verdict✅ APPROVE - No critical issues. The implementation is solid with comprehensive test coverage (128/128 dependency tests passing). The suggestions above are optional improvements for robustness and diagnostics. |
Required dependencies are now auto-registered rather than causing validation errors, so the test must expect no dependency error.
SummaryThis PR makes [DependsOn] dependencies required by default with auto-registration of missing dependencies, addressing issue #2164. Critical IssuesNone found ✅ Previous review concerns have been addressed:
Suggestions
Previous Review StatusAll three previous review comments have been addressed successfully. Verdict✅ APPROVE - No critical issues. Implementation correctly addresses #2164 with required dependencies by default, auto-registration of missing deps, comprehensive test coverage (128/128 passing), and clear documentation. |
Summary
Makes
[DependsOn<T>]dependencies required by default with auto-registration of missing dependencies.This addresses issue #2164 where using category filters with dependencies in different categories would cause unexpected failures.
New Behavior
Required Dependencies (Default)
Module1is not explicitly registered, it will be automatically registeredGetModule<T>()to access - guaranteed to be presentOptional Dependencies
GetModuleIfRegistered<T>()to safely check if presentKey Changes
DependsOnAttribute.csOptionaldefaults tofalse, added comprehensive XML docsModuleAutoRegistrar.csPipelineBuilder.csIgnoredModuleResultRegistrar.csDependencyWaiter.csModuleDependencyValidator.csRequiresDependencyAttribute.csexecution-and-dependencies.mdTest Plan
DependsOnTests- 11/11 passingCategoryFilterDependencyTests- 3/3 passingModuleNotRegisteredExceptionTests- 3/3 passingSingleTypeParameterGetModuleTests- 6/6 passingFixes #2164