本文へ移動
cccskills
無料GitHub で公開

agilepool

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.

日本語の概要は準備中です。原文の説明を表示しています。

Yiming1997/agilePoolスター 306(GitHub 306 + cccskills 0)

実行スコア 未検証

インストール方法を見る

含まれるファイル(1)

  • SKILL.md9.9 KB

SKILL.md(原文)

インストールする前に、エージェントに与えられる指示の中身を確認できます。

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

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

pool := agilepool.NewPool(config *Config)

NewPool(nil) uses defaults (unlimited workers, LinkedListType, MutexLock).

Task Interface and Types

// 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

MethodBehavior
pool.Submit(task)Enqueues task; no-op on nil. Returns void.
pool.TrySubmit(task) boolReturns 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

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

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

pool.SetLogger(l Logger)  // interface { Printf(string, ...interface{}); Println(...interface{}) }
// Pass *log.Logger, zap.SugaredLogger, etc.

Configuration Options

All via functional options pattern:

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

TypeAddPopBest for
LinkedListTypeO(1)O(1)General purpose (default)
MinHeapTypeO(log n)O(log n)Ordered by lastActiveAt
SliceTypeO(1)O(n)Simple, small pools
RingQueueTypeO(1)O(1)High-frequency Pop; works well with SpinLock

Common Patterns

Graceful Shutdown

pool := agilepool.NewPool(config)
defer pool.Close()        // 1. Stop accepting new work

// ... submit tasks ...

pool.Wait()               // 2. Wait for completion

Producer-Consumer

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

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:

    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:

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.

レビュー

まだレビューはありません。使ってみた感想をお寄せください。

コメント

ログインなしで書き込めます。使ってみた感想、質問、動かなかった環境などを気軽にどうぞ。

まだコメントはありません。最初のコメントを書いてみませんか。

送信するとすぐに公開されます。メールアドレスや電話番号などの個人情報は書かないでください。URL は 2 つまでです。

0 / 1000 文字

このフォームは reCAPTCHA で保護されており、Google のプライバシーポリシーと利用規約が適用されます。

このスキルの問題を報告する