|
| 1 | +# Circular Dependency Detection |
| 2 | + |
| 3 | +## Overview |
| 4 | + |
| 5 | +The porch configuration system now includes robust circular dependency detection to prevent infinite loops in command group references. This feature helps catch configuration errors early and provides clear error messages to help debug issues. |
| 6 | + |
| 7 | +## Features |
| 8 | + |
| 9 | +### 1. Circular Dependency Detection |
| 10 | + |
| 11 | +The system detects several types of circular dependencies: |
| 12 | + |
| 13 | +- **Simple circular dependencies**: Group A references Group B, which references Group A |
| 14 | +- **Multi-way circular dependencies**: Group A → Group B → Group C → Group A |
| 15 | +- **Self-referencing groups**: A group that references itself |
| 16 | +- **Deep nested cycles**: Circular dependencies buried deep in nested command structures |
| 17 | + |
| 18 | +### 2. Maximum Recursion Depth Protection |
| 19 | + |
| 20 | +To prevent stack overflow and excessive processing, the system enforces a maximum recursion depth of 100 levels when resolving command groups. |
| 21 | + |
| 22 | +### 3. Enhanced Error Messages |
| 23 | + |
| 24 | +When a circular dependency is detected, the system provides clear error messages showing: |
| 25 | + |
| 26 | +- The exact circular path (e.g., "group_a → group_b → group_a") |
| 27 | +- Which command within a group caused the issue |
| 28 | +- The command index for easier debugging |
| 29 | + |
| 30 | +### 4. Context Cancellation Support |
| 31 | + |
| 32 | +Configuration building now respects context cancellation, allowing for: |
| 33 | + |
| 34 | +- Graceful interruption with Ctrl+C during config parsing |
| 35 | +- Timeout protection (30-second default for config building) |
| 36 | +- Responsive signal handling during long configuration processes |
| 37 | + |
| 38 | +## Examples |
| 39 | + |
| 40 | +### Detecting Circular Dependencies |
| 41 | + |
| 42 | +**Example 1: Simple Circular Dependency** |
| 43 | + |
| 44 | +```yaml |
| 45 | +name: infinite loop example |
| 46 | +command_groups: |
| 47 | + - name: group_one |
| 48 | + commands: |
| 49 | + - type: serial |
| 50 | + name: reference to two |
| 51 | + command_group: group_two |
| 52 | + - name: group_two |
| 53 | + commands: |
| 54 | + - type: serial |
| 55 | + name: reference to one |
| 56 | + command_group: group_one |
| 57 | +commands: |
| 58 | + - type: parallel |
| 59 | + name: start |
| 60 | + command_group: group_one |
| 61 | +``` |
| 62 | +
|
| 63 | +**Error Output:** |
| 64 | +
|
| 65 | +``` |
| 66 | +failed to build config: invalid command group 'group_one': |
| 67 | +in command 0 of group group_one: circular dependency detected: group_one → group_two → group_one |
| 68 | +``` |
| 69 | +
|
| 70 | +**Example 2: Self-Referencing Group** |
| 71 | +
|
| 72 | +```yaml |
| 73 | +name: self reference example |
| 74 | +command_groups: |
| 75 | + - name: recursive_group |
| 76 | + commands: |
| 77 | + - type: shell |
| 78 | + name: first command |
| 79 | + command_line: echo "first" |
| 80 | + - type: serial |
| 81 | + name: self reference |
| 82 | + command_group: recursive_group |
| 83 | +commands: |
| 84 | + - type: serial |
| 85 | + name: start |
| 86 | + command_group: recursive_group |
| 87 | +``` |
| 88 | +
|
| 89 | +**Error Output:** |
| 90 | +
|
| 91 | +``` |
| 92 | +failed to build config: invalid command group 'recursive_group': |
| 93 | +in command 1 of group recursive_group: circular dependency detected: recursive_group → recursive_group |
| 94 | +``` |
| 95 | +
|
| 96 | +### Valid Nested Groups |
| 97 | +
|
| 98 | +This configuration is valid and will work correctly: |
| 99 | +
|
| 100 | +```yaml |
| 101 | +name: valid nested example |
| 102 | +command_groups: |
| 103 | + - name: setup |
| 104 | + commands: |
| 105 | + - type: shell |
| 106 | + name: prepare |
| 107 | + command_line: echo "preparing" |
| 108 | + - type: serial |
| 109 | + name: run tests |
| 110 | + command_group: test_suite |
| 111 | + - name: test_suite |
| 112 | + commands: |
| 113 | + - type: shell |
| 114 | + name: unit tests |
| 115 | + command_line: echo "running unit tests" |
| 116 | + - type: serial |
| 117 | + name: cleanup |
| 118 | + command_group: cleanup |
| 119 | + - name: cleanup |
| 120 | + commands: |
| 121 | + - type: shell |
| 122 | + name: clean up |
| 123 | + command_line: echo "cleaning up" |
| 124 | +commands: |
| 125 | + - type: serial |
| 126 | + name: main workflow |
| 127 | + command_group: setup |
| 128 | +``` |
| 129 | +
|
| 130 | +## Signal Handling Improvements |
| 131 | +
|
| 132 | +### Configuration Building Timeout |
| 133 | +
|
| 134 | +Configuration building now has a 30-second timeout to prevent hanging on malformed configurations: |
| 135 | +
|
| 136 | +```go |
| 137 | +// In cmd/porch/run/run.go |
| 138 | +configCtx, configCancel := context.WithTimeout(ctx, 30*time.Second) |
| 139 | +defer configCancel() |
| 140 | + |
| 141 | +rb, err := config.BuildFromYAML(configCtx, factory, bytes) |
| 142 | +``` |
| 143 | + |
| 144 | +### Context Cancellation Checks |
| 145 | + |
| 146 | +The system now checks for context cancellation at key points: |
| 147 | + |
| 148 | +- Before starting configuration parsing |
| 149 | +- After command group validation |
| 150 | +- During individual command processing |
| 151 | + |
| 152 | +### Graceful Interruption |
| 153 | + |
| 154 | +When you press Ctrl+C during configuration building, you'll see: |
| 155 | + |
| 156 | +``` |
| 157 | +failed to build config: configuration building timed out: context canceled |
| 158 | +``` |
| 159 | + |
| 160 | +## Implementation Details |
| 161 | + |
| 162 | +### Registry-Level Detection |
| 163 | + |
| 164 | +The `commandregistry.Registry` now includes: |
| 165 | + |
| 166 | +- `resolveCommandGroupWithDepth()`: Tracks recursion depth and visiting state |
| 167 | +- `validateCommandForCircularDeps()`: Validates individual commands for group references |
| 168 | +- `formatCircularDependencyPath()`: Creates human-readable circular dependency paths |
| 169 | + |
| 170 | +### Configuration-Level Validation |
| 171 | + |
| 172 | +The `config.BuildFromYAML()` function: |
| 173 | + |
| 174 | +- Validates all command groups before proceeding with command creation |
| 175 | +- Includes context cancellation checks throughout the process |
| 176 | +- Provides detailed error messages with command indices |
| 177 | + |
| 178 | +### Error Types |
| 179 | + |
| 180 | +New error types have been added: |
| 181 | + |
| 182 | +- `ErrCircularDependency`: For circular dependency detection |
| 183 | +- `ErrConfigurationTimeout`: For configuration timeout handling |
| 184 | +- `ErrMaxRecursionDepth`: For recursion depth protection |
| 185 | + |
| 186 | +## Testing |
| 187 | + |
| 188 | +Comprehensive tests have been added covering: |
| 189 | + |
| 190 | +- Simple circular dependencies |
| 191 | +- Multi-way circular dependencies |
| 192 | +- Self-referencing groups |
| 193 | +- Maximum recursion depth |
| 194 | +- Context cancellation |
| 195 | +- Configuration timeouts |
| 196 | + |
| 197 | +Run tests with: |
| 198 | + |
| 199 | +```bash |
| 200 | +go test ./internal/config -v -run TestCircularDependencyDetection |
| 201 | +go test ./internal/config -v -run TestConfigurationTimeout |
| 202 | +go test ./internal/commandregistry -v |
| 203 | +``` |
0 commit comments