High-performance adaptive goroutine pool for Go with auto-scaling via Little's Law, sliding-window rate statistics, chunked overflow buffer, multiple idle container backends (LinkedList/MinHeap/Slice/RingQueue), customizable spinlock, retry with exponential backoff, and context-aware submission. Use when working with the github.com/Yiming1997/agilePool/v2 codebase — adding features, fixing bugs, writing tests, or answering questions about pool internals and API usage.
Scanned 9/10/2026
Install to Claude Code
npx -y skills add Yiming1997/agilePool --skill skills --agent claude-codeInstalls into .claude/skills of the current project.
Are you the author of agilepool?
Add the live security badge to your README — it updates automatically with every re-scan.
[](https://www.skillsdirectory.com/skills/yiming1997-agilepool)More formats (shields.io, HTML) on the badges page.
---
name: agilepool
description: High-performance adaptive goroutine pool for Go with auto-scaling via Little's Law, sliding-window rate statistics, chunked overflow buffer, multiple idle container backends (LinkedList/MinHeap/Slice/RingQueue), customizable spinlock, retry with exponential backoff, and context-aware submission. Use when working with the github.com/Yiming1997/agilePool/v2 codebase — adding features, fixing bugs, writing tests, or answering questions about pool internals and API usage.
---
# agilePool — Adaptive Goroutine Pool
## Overview
Package `agilepool` (`github.com/Yiming1997/agilePool/v2`, Go 1.23.4) provides an adaptive goroutine pool that dynamically scales worker count using Little's Law. It features:
- **Adaptive scaling**: Median rate sampling over a sliding window → `target = submitMedian × runningWorkers / consumeMedian`, with backlog-weighted boosting and exit-rate compensation.
- **Chunked overflow buffer**: When the handoff channel is full, tasks spool into a linked-list of fixed-size chunks (4096 slots each). Soft cap at 100k total tasks; exceeding it blocks on the channel instead of growing unboundedly.
- **Multiple idle containers**: `LinkedListType` (default FIFO), `MinHeapType`, `SliceType`, `RingQueueType` — pluggable via config.
- **Configurable locking**: `muIdle` lock can be `sync.Mutex` (default) or a custom `spinLock` (three-phase: CAS → exponential backoff → Gosched).
- **Task variants**: `Task` interface, `TaskFunc` (bare func), `TaskWithRetry` (exponential backoff), context-aware via `SubmitCtx`, timeout via `SubmitBefore`.
- **Panic-safe**: Workers recover from panics and continue processing.
- **Graceful shutdown**: `Close()` + `Wait()` pattern.
## Quick Start
```go
import agilepool "github.com/Yiming1997/agilePool/v2"
pool := agilepool.NewPool(agilepool.NewConfig(
agilepool.WithWorkerNumCapacity(1000),
agilepool.WithTaskQueueSize(10000),
))
defer pool.Close()
pool.Submit(agilepool.TaskFunc(func() error {
// your work here
return nil
}))
pool.Wait()
```
## API Reference
### Pool Creation
```go
pool := agilepool.NewPool(config *Config)
```
`NewPool(nil)` uses defaults (unlimited workers, LinkedListType, MutexLock).
### Task Interface and Types
```go
// Core interface
type Task interface {
process()
}
// Simple function adapter
// func() error → NewTask → Task
pool.Submit(agilepool.NewTask(func() error { return nil }))
type TaskFunc func() error // func() error → TaskFunc → Task
pool.Submit(agilepool.TaskFunc(func() error { return nil }))
// Retry with backoff
pool.Submit(&agilepool.TaskWithRetry{
MinBackOff: 1 * time.Second,
MaxBackOff: 200 * time.Second,
RetryNum: 3, // retries on error, so total attempts = RetryNum + 1
BackOffStrategy: nil, // nil → default exponential: min * 2^retryNum, capped at max
Task: func() error { return errors.New("fail") },
})
```
### Submission Methods
| Method | Behavior |
|--------|----------|
| `pool.Submit(task)` | Enqueues task; no-op on nil. Returns void. |
| `pool.TrySubmit(task) bool` | Returns `false` if rejected (pool closed, nil task, or NONBLOCK mode queue full). |
| `pool.SubmitCtx(ctx, task)` | Wraps task with context; skips execution if `ctx.Err() != nil` at dequeue time. |
| `pool.SubmitBefore(task, timeout)` | Skips task if not started within `timeout`. |
### Shutdown
```go
pool.Close() // Idempotent. Stops cleaner/scaler. New submits become no-ops.
pool.Wait() // Blocks until all in-flight tasks complete.
// Always: Close() first, then Wait().
```
**Critical**: Call `Close()` before `Wait()`. Calling `Wait()` alone blocks forever because the background goroutines never exit.
### Query Methods
```go
pool.GetRunningWorkersNum() int64 // currently active goroutines
pool.GetWorkerCreateCount() int64 // lifetime sync.Pool allocations
pool.GetIdleWorkerCount() int64 // parked in idle container
pool.GetTaskQueueLen() int // len(taskQueue) — does NOT include chunked buffer
pool.GetCapacity() int64 // max worker limit
agilepool.GetDefaultPool() *Pool // most recently created pool (nil after Close)
```
### Logger
```go
pool.SetLogger(l Logger) // interface { Printf(string, ...interface{}); Println(...interface{}) }
// Pass *log.Logger, zap.SugaredLogger, etc.
```
## Configuration Options
All via functional options pattern:
```go
agilepool.NewConfig(
agilepool.WithCleanPeriod(500 * time.Millisecond), // expired worker cleanup interval
agilepool.WithTaskQueueSize(10000), // handoff channel capacity
agilepool.WithWorkerNumCapacity(20000), // max concurrent workers
agilepool.WithBlockMode(agilepool.BLOCK), // BLOCK (default) or NONBLOCK
agilepool.WithIdleContainerType(agilepool.LinkedListType), // LinkedList/MinHeap/Slice/RingQueue
agilepool.WithLockType(agilepool.SpinLock), // MutexLock (default) or SpinLock
agilepool.WithStatsSamplePeriod(100 * time.Millisecond), // rate sampling interval
agilepool.WithStatsWindowSize(10), // sliding window count for median
agilepool.WithScalerPeriod(10 * time.Millisecond), // scaler tick interval
agilepool.WithBacklogDecayFactor(0.3), // queue backlog weight in scaler (0-1)
)
```
### WorkMode
- **BLOCK** (default): `Submit` blocks until the task is accepted (channel or chunked buffer).
- **NONBLOCK**: `Submit` drops the task when the handoff channel is full. Use `TrySubmit` to detect rejection.
### IdleContainerType
| Type | Add | Pop | Best for |
|------|-----|-----|----------|
| `LinkedListType` | O(1) | O(1) | General purpose (default) |
| `MinHeapType` | O(log n) | O(log n) | Ordered by lastActiveAt |
| `SliceType` | O(1) | O(n) | Simple, small pools |
| `RingQueueType` | O(1) | O(1) | High-frequency Pop; works well with SpinLock |
## Common Patterns
### Graceful Shutdown
```go
pool := agilepool.NewPool(config)
defer pool.Close() // 1. Stop accepting new work
// ... submit tasks ...
pool.Wait() // 2. Wait for completion
```
### Producer-Consumer
```go
pool := agilepool.NewPool(agilepool.NewConfig(
agilepool.WithWorkerNumCapacity(runtime.NumCPU() * 2),
))
defer pool.Close()
var wg sync.WaitGroup
for _, item := range items {
wg.Add(1)
it := item
pool.Submit(agilepool.TaskFunc(func() error {
defer wg.Done()
process(it)
return nil
}))
}
wg.Wait()
pool.Wait()
```
### Context-Aware with Cancellation
```go
ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
defer cancel()
pool.SubmitCtx(ctx, agilepool.TaskFunc(func() error {
select {
case <-ctx.Done():
return ctx.Err()
default:
return doWork()
}
}))
```
## Anti-Patterns & Pitfalls
1. **Don't call `Wait()` before `Close()`** — the pool has background goroutines (cleaner, scaler, sampler). `Wait()` without `Close()` blocks forever.
2. **Don't `Close()` from within a task without defer** — `Close()` stops the cleaner goroutine, causing idle workers to accumulate. Closing inside a task is safe but use `defer pool.Close()` only at the outer scope.
3. **Nil `*TaskWithRetry` panics** — `isNilTask` checks `*TaskWithRetry == nil` but not its `Task` field. Ensure `Task` is non-nil.
4. **`SubmitCtx` with `nil` context** — automatically uses `context.Background()`. An already-canceled context causes the task to be silently dropped.
5. **`GetTaskQueueLen()` is incomplete** — it only reports `len(taskQueue)`, not the chunked overflow buffer. For true backlog, check your own metrics.
6. **Task closure capture** — Go loop variable capture applies. Always copy:
```go
for _, v := range items {
v := v
pool.Submit(agilepool.TaskFunc(func() error { use(v); return nil }))
}
```
## Testing
Tests use package `agilepool_test` (external test package) — import `github.com/Yiming1997/agilePool/v2` and use the public API. Use `testing.Short()` guards for heavy benchmarks:
```go
if testing.Short() {
taskCount = 20000 // reduced
}
```
## Internal Architecture (when modifying code)
- **`pool.go`**: Core Pool struct, NewPool, Submit path (channel → chunked buffer → backpressure), scaler with `scaleIfNeeded()`, Close/Wait.
- **`config.go`**: `Config` struct and `With*` functional options.
- **`worker.go`**: `worker.run()` — three-phase task acquisition loop: (1) channel receive, (2) chunked buffer batch (up to 8 per lock), (3) second channel check before parking.
- **`task.go`**: `Task` interface, `TaskFunc`, `TaskWithRetry` (exponential backoff default: `min × 2^retryNum`, capped at `max`).
- **`idle_container.go`**: `IdleWorkerContainer` interface + `IdleContainerType` enum.
- **`idle_linkedlist.go` / `idle_heap.go` / `idle_slice.go` / `idle_ring_queue.go`**: Concrete implementations.
- **`histogram.go`**: Fixed-bucket sliding-window histogram for rate sampling. Used by scaler to compute median submit/consume/exit rates.
- **`spinlock.go`**: Three-phase spin lock implementing `sync.Locker`. Used for `muIdle` when `WithLockType(SpinLock)`.
- **`stack_trace.go`**: `Stack(skip)` for panic recovery — reads source files, so not for production hot paths.
### Code Modification Notes
- `pendingTasks` is atomic — decremented in `pool.done()`, incremented on submit. It covers submitters blocked on the channel, giving the scaler full visibility.
- Workers are pooled via `sync.Pool`. **Do NOT** `workerPool.Put(w)` when the worker is also in `idleWorks` — that causes dual ownership and data races.
- The scaler runs every `scalerPeriod` (default 10ms). It reads median rates from histograms, computes target via Little's Law with backlog-weighted boosting.
- `taskMu` protects the chunked buffer. Workers batch-pop up to 8 tasks per lock acquisition to amortize contention.
- The spinlock does NOT call into the kernel — optimal when hold times are <1µs (e.g., RingQueue index swaps). Use `MutexLock` for longer or unpredictable hold times.
Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.
No comments yet. Be the first to comment!