yao/agent/robot/pool/pool.go
Max 41e0544aba Refactor Executor Architecture and Update Documentation
- Introduced multiple executor modes (Standard, DryRun, Sandbox) to accommodate various use cases, enhancing flexibility in execution strategies.
- Updated DESIGN.md to reflect the new executor modes and their respective use cases, including detailed descriptions and configuration examples.
- Revised TECHNICAL.md to outline the new executor package structure, emphasizing the modular design for future enhancements.
- Enhanced the TODO.md to track the progress of executor mode implementations and related tasks.
- Removed outdated executor stub files and tests, streamlining the codebase for improved maintainability.
- Updated integration tests to utilize the new DryRun executor, ensuring comprehensive coverage of execution scenarios without real agent calls.
2026-01-16 15:27:46 +08:00

224 lines
5.7 KiB
Go

package pool
import (
"fmt"
"sync"
"sync/atomic"
"github.com/yaoapp/yao/agent/robot/types"
)
// Default configuration values
const (
DefaultWorkerSize = 10 // default number of workers
DefaultQueueSize = 100 // default global queue size
)
// Config holds pool configuration
type Config struct {
WorkerSize int // number of workers (default: 10)
QueueSize int // global queue size (default: 100)
}
// DefaultConfig returns default pool configuration
func DefaultConfig() *Config {
return &Config{
WorkerSize: DefaultWorkerSize,
QueueSize: DefaultQueueSize,
}
}
// ExecutorFactory creates an executor based on the mode
type ExecutorFactory func(mode types.ExecutorMode) types.Executor
// Pool implements types.Pool interface
// Manages a pool of workers that execute robot jobs from a priority queue
type Pool struct {
size int // number of workers
queue *PriorityQueue // priority queue for pending jobs
executor types.Executor // default executor for running jobs
executorFactory ExecutorFactory // optional: factory for mode-specific executors
workers []*Worker // worker goroutines
running atomic.Int32 // number of currently running jobs
wg sync.WaitGroup // wait group for graceful shutdown
started bool // whether pool has been started
mu sync.RWMutex // protects started flag
}
// New creates a new pool instance with default configuration
func New() *Pool {
return NewWithConfig(nil)
}
// NewWithConfig creates a new pool instance with custom configuration
func NewWithConfig(config *Config) *Pool {
if config == nil {
config = DefaultConfig()
}
// Apply defaults for zero values
workerSize := config.WorkerSize
if workerSize <= 0 {
workerSize = DefaultWorkerSize
}
queueSize := config.QueueSize
if queueSize <= 0 {
queueSize = DefaultQueueSize
}
return &Pool{
size: workerSize,
queue: NewPriorityQueue(queueSize),
}
}
// SetExecutor sets the default executor for the pool
// Must be called before Start()
func (p *Pool) SetExecutor(executor types.Executor) {
p.executor = executor
}
// SetExecutorFactory sets the executor factory for mode-specific executors
// If set, the factory is used to create executors based on ExecutorMode
func (p *Pool) SetExecutorFactory(factory ExecutorFactory) {
p.executorFactory = factory
}
// GetExecutor returns the appropriate executor for the given mode
// If factory is set and mode is specified, uses factory; otherwise uses default
func (p *Pool) GetExecutor(mode types.ExecutorMode) types.Executor {
// If factory is set and mode is specified, use factory
if p.executorFactory != nil && mode != "" {
return p.executorFactory(mode)
}
// Otherwise use default executor
return p.executor
}
// Start starts the worker pool
func (p *Pool) Start() error {
p.mu.Lock()
defer p.mu.Unlock()
if p.started {
return fmt.Errorf("pool already started")
}
if p.executor == nil {
return fmt.Errorf("executor not set, call SetExecutor() first")
}
// Create and start workers
p.workers = make([]*Worker, p.size)
for i := 0; i < p.size; i++ {
worker := newWorker(i+1, p, &p.wg)
p.workers[i] = worker
worker.start()
}
p.started = true
return nil
}
// Stop stops the worker pool gracefully
// Waits for all running jobs to complete
func (p *Pool) Stop() error {
p.mu.Lock()
if !p.started {
p.mu.Unlock()
return nil // already stopped or never started
}
p.started = false
p.mu.Unlock()
// Stop all workers
for _, worker := range p.workers {
worker.stop()
}
// Wait for all workers to finish
p.wg.Wait()
return nil
}
// Submit submits a robot execution to the pool
// Returns execution ID if successfully queued, error otherwise
func (p *Pool) Submit(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}) (string, error) {
return p.SubmitWithMode(ctx, robot, trigger, data, "")
}
// SubmitWithMode submits a robot execution with specified executor mode
// executorMode: optional, overrides robot's config if provided
// Returns execution ID if successfully queued, error otherwise
func (p *Pool) SubmitWithMode(ctx *types.Context, robot *types.Robot, trigger types.TriggerType, data interface{}, executorMode types.ExecutorMode) (string, error) {
p.mu.RLock()
if !p.started {
p.mu.RUnlock()
return "", fmt.Errorf("pool not started")
}
p.mu.RUnlock()
if robot == nil {
return "", fmt.Errorf("robot cannot be nil")
}
// Create queue item
item := &QueueItem{
Robot: robot,
Ctx: ctx,
Trigger: trigger,
Data: data,
ExecutorMode: executorMode,
}
// Try to add to queue
if !p.queue.Enqueue(item) {
return "", fmt.Errorf("queue full (max %d items)", p.queue.maxSize)
}
// Generate execution ID for tracking
// Note: Actual execution ID will be generated by Executor
// This is just a placeholder for the Submit return value
execID := fmt.Sprintf("queued_%s_%d", robot.MemberID, item.EnqueueTime.Unix())
return execID, nil
}
// Running returns number of currently running jobs
func (p *Pool) Running() int {
return int(p.running.Load())
}
// Queued returns number of queued jobs
func (p *Pool) Queued() int {
return p.queue.Size()
}
// incrementRunning increments the running counter
func (p *Pool) incrementRunning() {
p.running.Add(1)
}
// decrementRunning decrements the running counter
func (p *Pool) decrementRunning() {
p.running.Add(-1)
}
// Size returns the configured pool size
func (p *Pool) Size() int {
return p.size
}
// QueueSize returns the configured queue size
func (p *Pool) QueueSize() int {
return p.queue.maxSize
}
// IsStarted returns true if the pool has been started
func (p *Pool) IsStarted() bool {
p.mu.RLock()
defer p.mu.RUnlock()
return p.started
}