feat(executor): add worker pool and concurrency limit #187

Merged
fuzzy merged 2 commits from feature/add-resource-management into main 2026-07-06 08:37:56 +00:00
Owner

What

Add resource management to the command executor:

  • Semaphore-based worker pool using buffered channel (#76)
  • Default concurrency limit of 20 concurrent commands (#77)
  • max_concurrency config option in config.yaml (#78)
  • Non-blocking webhook response preserved — actions run in goroutines (#79)
  • Default applied in config loader

Why

Prevents unbounded goroutine and process spawning under high webhook load. Ensures the server stays responsive even during burst events.

Testing

  • Unit tests pass — 6 tests including concurrency limit test (10 rules, 2 concurrent)
  • Integration tests pass — N/A
  • Manual testing performed — go test ./... and pre-commit pass
  • New tests added for new functionality — TestRunCommandConcurrencyLimit

Breaking Changes

  • RunActions signature changed: added maxConcurrency int parameter (pass 0 for default)

Notes

  • max_concurrency: 0 in config means "use default" (20)
  • Semaphore is per RunActions call, not global across the application
  • Each action slot in the pool runs subcommands sequentially within a rule

Closes #80
Closes #76
Closes #77
Closes #78
Closes #79

## What Add resource management to the command executor: - Semaphore-based worker pool using buffered channel (#76) - Default concurrency limit of 20 concurrent commands (#77) - `max_concurrency` config option in `config.yaml` (#78) - Non-blocking webhook response preserved — actions run in goroutines (#79) - Default applied in config loader ## Why Prevents unbounded goroutine and process spawning under high webhook load. Ensures the server stays responsive even during burst events. ## Testing - [x] Unit tests pass — 6 tests including concurrency limit test (10 rules, 2 concurrent) - [ ] Integration tests pass — N/A - [x] Manual testing performed — `go test ./...` and pre-commit pass - [x] New tests added for new functionality — `TestRunCommandConcurrencyLimit` ## Breaking Changes - `RunActions` signature changed: added `maxConcurrency int` parameter (pass 0 for default) ## Notes - `max_concurrency: 0` in config means "use default" (20) - Semaphore is per `RunActions` call, not global across the application - Each action slot in the pool runs subcommands sequentially within a rule Closes #80 Closes #76 Closes #77 Closes #78 Closes #79
Add semaphore-based worker pool to RunActions:
- max_concurrency config option (default 20)
- Buffered channel limits concurrent command executions
- Prevents unbounded goroutine spawning
- Non-blocking webhook response preserved

Added MaxConcurrency to Config model with default in config loader.

Closes #76
Closes #77
Closes #78
Closes #79
docs: mark Add resource management tasks as complete
All checks were successful
CI / Pre-commit Hooks (pull_request) Successful in 2m39s
CI / Lint (golangci-lint) (pull_request) Successful in 1m34s
CI / Test (pull_request) Successful in 1m23s
CI / Lint (golangci-lint) (push) Successful in 1m35s
CI / Pre-commit Hooks (push) Successful in 2m43s
CI / Test (push) Successful in 1m22s
497c5e9b93
Closes #80
fuzzy scheduled this pull request to auto merge when all checks succeed 2026-07-06 08:32:42 +00:00
the.auditor left a comment

Review

Clean worker pool implementation. Full project lint, vet, tests pass.

Design highlights:

  • Buffered channel semaphore (make(chan struct{}, maxConcurrency)) — standard Go concurrency pattern
  • Semaphore acquired right before runCommand, released after retry loop — slot held for full retry sequence
  • Template interpolation failures skip the semaphore entirely (no acquire → no release needed ✓)
  • Config default applied in applyDefaults (0 or negative → 20)
  • defaultRetry constant cleaned up (was unused)

Models: Config.MaxConcurrency int with max_concurrency YAML tag. Zero-value = use default.

Breaking change: RunActions signature extended with maxConcurrency int. All callers (tests) updated.

Non-blocking: Goroutines are still spawned for every rule even when pool is full. They block on the semaphore before executing. This is by design per #79 (non-blocking webhook response). A full worker pool (limiting goroutines too) could be a future enhancement.

No blocking issues.

APPROVED

## Review Clean worker pool implementation. Full project lint, vet, tests pass. **Design highlights**: - Buffered channel semaphore (`make(chan struct{}, maxConcurrency)`) — standard Go concurrency pattern - Semaphore acquired right before `runCommand`, released after retry loop — slot held for full retry sequence - Template interpolation failures skip the semaphore entirely (no acquire → no release needed ✓) - Config default applied in `applyDefaults` (0 or negative → 20) - `defaultRetry` constant cleaned up (was unused) **Models**: `Config.MaxConcurrency int` with `max_concurrency` YAML tag. Zero-value = use default. **Breaking change**: `RunActions` signature extended with `maxConcurrency int`. All callers (tests) updated. **Non-blocking**: Goroutines are still spawned for every rule even when pool is full. They block on the semaphore before executing. This is by design per #79 (non-blocking webhook response). A full worker pool (limiting goroutines too) could be a future enhancement. **No blocking issues.** **APPROVED**
fuzzy merged commit 497c5e9b93 into main 2026-07-06 08:37:56 +00:00
fuzzy deleted branch feature/add-resource-management 2026-07-06 08:37:57 +00:00
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
2 participants
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set

Reference
thwap/ttheart!187
No description provided.