Last Updated: February 20, 2026 (v1.7.0)
This document lists known limitations, platform-specific constraints, and measured performance characteristics. Transparency is a feature.
| Constraint | Details | Impact | Workaround |
|---|---|---|---|
| Go Version | Requires Go 1.20+ for full Job Objects support (zombie prevention) | Pre-1.20: Child processes may become zombies on hard crash | Upgrade Go or accept zombie risk |
| Console Input | CONIN$ requires explicit opt-in for non-blocking reliable I/O | Default behavior may block on Ctrl+C in some terminals |
Use lifecycle.NewInteractiveRouter or manual setup |
| SIGTERM Behavior | Not natively supported; mapped to graceful exit | Works but less native than Unix | No alternative; design accounts for this |
| Constraint | Details | Impact | Workaround |
|---|---|---|---|
| PDeathSig | Not supported by Go os/signal on macOS |
Hard crashes can leave orphan processes | Use external process monitor or heartbeat mechanism |
| Zombie Detection | Cannot automatically detect zombie child processes | May accumulate if parent crashes during wait() |
Monitor with ps aux \| grep <defunct> |
| Constraint | Details | Impact | Workaround |
|---|---|---|---|
| SIGCHLD Handling | Default handler may interfere with custom signal handlers | Rare, but affects some specialized use cases | Document custom handlers explicitly |
| Feature | Limitation | Details | Example |
|---|---|---|---|
| Pattern Syntax | Glob-only (not full regex) | Uses Go’s path.Match internally: *, ?, [...] only |
✅ signal/*/handler ❌ signal/(int\|term) |
| Performance | Linear search (no indexing) | O(n) route lookup for n routes | See benchmark results below |
| Ambiguity | First-match wins (no priority weights) | Overlapping patterns use definition order | Define more-specific patterns first |
Pattern Examples:
// ✅ Valid glob patterns
router.HandleFunc("signal/*/handler", fn) // Matches any single segment
router.HandleFunc("signal/[it]*", fn) // Matches interrupt, terminate
router.HandleFunc("event/*/", fn) // Prefix matching
// ❌ Invalid (not supported - use exact routes instead)
router.HandleFunc("signal/(int|term)", fn) // Regex alternation not supported
router.HandleFunc("signal/\\d+", fn) // Regex character classes not supported
Benchmark Results (See pkg/events/router_benchmark_test.go):
| Routes | Exact Match | Glob Match (avg) | Worst Case |
|---|---|---|---|
| 1 (exact) | ~77ns | N/A | Fast map lookup |
| 10 (glob) | ~77ns | ~984ns (~1µs) | Linear scan |
| 100 (glob) | ~77ns | ~7µs | Linear scan |
| 1000 (glob) | ~77ns | ~81µs | Linear scan |
Recommendation:
Affected Code: pkg/events/router.go — See inline comments for optimization notes.
| Feature | Status | Caveat |
|---|---|---|
| OnGoroutinePanicked | Stable (v1.6.0) | Stack capture is optional; auto-detect uses slog.LevelDebug |
| Stack Bytes Format | Stable | Uses runtime/debug.Stack() (text format, not parsed) |
| Observer Ordering | Not guaranteed | Multiple observers called serially; exception stops chain (TBD) |
| Production Overhead | ~0.5-1µs per panic | Only if observer is installed; no overhead if nil |
Documented In: TECHNICAL.md §14 - Observability
| Mode | Behavior | Overhead (per panic) | Memory | Use Case |
|---|---|---|---|---|
Enabled WithStackCapture(true) |
Always capture stack bytes | +1-2µs | ~4-8 KB | Critical tasks, debugging |
Disabled WithStackCapture(false) |
Never capture (even if debug on) | Baseline (~2µs) | ~0 bytes | Performance-sensitive code |
| Auto-Detect (default) | Capture only if slog.LevelDebug enabled |
Conditional | Conditional | Development (recommended) |
Recommendation: Leave unset (auto-detect) in most cases. Only use explicit true for critical worker lifecycle tracking.
Implementation: pkg/core/runtime/task.go — Conditional stack capture logic.
| Feature | Limitation | OS | Impact | Workaround |
|---|---|---|---|---|
Recursive Watching WithRecursive(true) |
Limited by inotify max instances |
Linux | fsnotify fails to add directories, silent drops, or supervisor failure |
Increase fs.inotify.max_user_watches or aggressively use WithFilter |
| Recursive Discovery | New directories are discovered and attached dynamically | All | Small race window between dir creation and watch attachment | Ignore micro-races outside of your control |
Recommendation: Never use WithRecursive(true) on root repository directories (like mono-repos) without also providing a WithFilter that ignores massive dependencies like node_modules/, vendor/, or .git/.
Example Benchmarking Environment (yours will vary):
- Hardware: 11th Gen Intel i9-11900H @ 2.50GHz, 16GB RAM
- OS: Windows 11, Go 1.22
- Benchmarks run with
-benchtime=2s -benchmemImportant: These are reference numbers from one machine. Performance varies significantly across hardware, OS, and workload. Always benchmark on your target environment.
| Operation | Baseline | lifecycle Overhead | Notes |
|---|---|---|---|
go func() + wg.Wait() |
~440ns | — | Raw goroutine creation |
lifecycle.Go(ctx, fn) |
~1.4µs | ~3x | Tracking + metrics + observer setup |
lifecycle.Do(ctx, fn) |
~800ns-1.2µs | ~2x | Recovery + metrics |
Interpretation: The overhead is acceptable for I/O-bound tasks (network, disk) but may matter for tight CPU loops spawning thousands of goroutines per second.
| Observer Status | Overhead per Go() |
Notes |
|---|---|---|
No Observer (nil) |
Baseline | Check is ~5ns |
| Observer Installed | +0.5-1µs | Only on panic; normal execution unaffected |
See “Router Pattern Matching” section above for detailed route count scaling.
Middleware Overhead (per middleware, per event):
| Middleware Count | Overhead | Example |
|---|---|---|
| 0 | Baseline (~100ns) | Direct handler invocation |
| 1 | +50-100ns | Logging |
| 5 | +200-400ns | Logging + Recovery + Metrics + Custom |
| 10 | +500-800ns | Complex chains |
| Tree Size | State() Call |
Memory Footprint | Notes |
|---|---|---|---|
| 10 workers | ~5-10µs | ~5 KB | Fast; suitable for live dashboards |
| 100 workers | ~50-100µs | ~50 KB | Acceptable for periodic polling |
| 1000 workers | ~500-800µs | ~500 KB | Consider caching; avoid hot loops |
Recommendation: For large trees, cache State() results or poll at intervals (e.g., 1s) instead of per-request.
# Full benchmark suite (runtime + router)
make benchmark
# Individual packages
go test -bench=. -benchmem -benchtime=5s ./pkg/core/runtime/
go test -bench=. -benchmem -benchtime=5s ./pkg/events/
# Specific benchmark with profiling
go test -bench=BenchmarkGoVsRawGoroutine -benchmem -cpuprofile=cpu.prof ./pkg/core/runtime/
go tool pprof cpu.prof
BenchmarkGoVsRawGoroutine/LifecycleGo-8 500000 5234 ns/op 256 B/op 4 allocs/op
^^^^^^ ^^^^^^^^^^^^ ^^^^^^^^^ ^^^^^^^^^^^^^
iterations time/op bytes/op allocations
_ = 1 + 1). Real workloads (I/O, DB queries) will mask overhead.lifecycle.Go vs raw goroutinesState() calls)lifecycle.Run, lifecycle.Go, lifecycle.Dolifecycle.NewRouter, lifecycle.Handlelifecycle.NewSupervisor, pkg/core/supervisor/SupervisorSpeclifecycle.NewSignalContext (aliased to lifecycle.SignalContext)lifecycle.NewInteractiveRouterlifecycle.Context() — Manual context setup for gradual migrationlifecycle.WithStackCapture(bool) — Stack capture controlObserver.OnGoroutinePanicked(recovered any, stack []byte) — Panic hooklifecycle.StopAndWait(ctx, worker) — Generalized utility for robust worker terminationpkg/events/filewatch.FileWatchSource — Event-based file watchingpkg/events/webhook.WebhookSource — HTTP trigger source (1MB default payload limit to prevent OOM)pkg/events/health.HealthCheckSource — Health status sourcepkg/events.Notify(chan<- Event) — Pub/Sub channel bridgingpkg/events.DebounceHandler — High-frequency event dampeningpkg/events.WithRecursive & WithFilter — Advanced FileWatchSource capabilitiespkg/core/worker/suspend.Suspend — Context-aware worker pausing mechanismDeprecation Policy: See DEPRECATION.md for the active 3-phase lifecycle.
github.com/aretw0/lifecycle 85%
github.com/aretw0/lifecycle/pkg/core/signal 92%
github.com/aretw0/lifecycle/pkg/core/supervisor 88%
github.com/aretw0/lifecycle/pkg/core/runtime 87%
github.com/aretw0/lifecycle/pkg/core/worker 84%
github.com/aretw0/lifecycle/pkg/events 80%
| Package | Coverage | Reason | Strategy |
|---|---|---|---|
pkg/core/metrics |
~40% | Interface definitions + no-op stubs | Compile check; tested in consuming packages |
pkg/core/log |
~30% | Wrapper around slog |
Compile check; assumes slog stability |
procio (external) |
Tested in procio repo |
OS-dependent syscalls | Extracted to procio library |
Philosophy: See TESTING.md for “Honest Coverage” rationale.
| File | Line | Issue | Priority |
|---|---|---|---|
| pkg/events/router.go | 192 | Optimize route matching if many routes | 🟢 Low (future: batch indexing) |
| Scenario | Reason | Impact | Workaround |
|---|---|---|---|
| Windows CONIN$ on SSH | Interactive I/O unavailable in SSH | Cannot use NewInteractiveRouter |
Use non-interactive mode |
| Docker Alpine + musl | musl libc has signal handling quirks | Rare issues with suspend/resume | Test before production |
| Kubernetes graceful shutdown <5s | Default SIGTERM timeout may be insufficient | May force-kill graceful tasks | Increase terminationGracePeriodSeconds |
| Large supervision trees (>1000 workers) | Performance characteristics unknown | May hit memory/latency limits | Monitor and benchmark |
| Component | Status | Versions Tested |
|---|---|---|
| Go | ✅ Stable | 1.20, 1.21, 1.22 |
| Windows | ✅ Stable | 10, 11, Server 2022 |
| Linux | ✅ Stable | Ubuntu 20.04+, Alpine 3.16+ |
| macOS | ⚠️ Partial (no PDeathSig) | 12+, both Intel & Apple Silicon |
| Platform | Reason |
|---|---|
| BSD / FreeBSD | No CI; contributions welcome |
| Plan 9 / WASI | Out of scope (niche platforms) |
| Android / iOS | Not intended for mobile |
Found a limitation not listed here? Please open an issue with:
See DECISIONS.md for architectural trade-offs that explain some limitations.
Context: As of v1.6.3, all critical workers have migrated to the
withLock/withLockResultpattern to ensure concurrency safety and state consistency. However, some exceptions and limitations are important for ongoing maintenance and project evolution.
withLock/withLockResult directly, as it is a generic base and does not have a polymorphic lock wrapper. Manual locking (mu.Lock/mu.Unlock) remains to ensure compatibility and flexibility for custom workers.pkg/core/worker (e.g., event workers, custom handlers) may use their own locks or different patterns, and are not directly affected by this standardization.withLockAny/withLockResultAny helpers were created to allow safe locking on any struct with a mu sync.(R)WMutex field, without requiring specific type dependencies.withLock/withLockResult pattern is recommended for all new workers and future maintenance, but is not mandatory for legacy code or cases where manual locking is more appropriate.