/* * Copyright 2026 Safronov Grigorii * * Licensed under the CDDL, Version 1.0 (the "License"); * you may not use this file except in compliance with the License. * * You may obtain a copy of the License at * https://opensource.org/licenses/CDDL-1.0 */ // Файл: internal/cluster/worker_pool.go // Назначение: Пул воркеров для ограничения количества горутин package cluster import ( "fmt" "runtime/debug" "sync" "sync/atomic" "time" ) // LoggerInterface определяет интерфейс для логирования type LoggerInterface interface { Debug(msg string) Info(msg string) Error(msg string) Warn(msg string) } // WorkerTask представляет задачу для выполнения в пуле type WorkerTask struct { ID string Handler func() error CreatedAt int64 } // WorkerPool управляет пулом воркеров type WorkerPool struct { maxWorkers int taskQueue chan *WorkerTask wg sync.WaitGroup stopChan chan struct{} activeTasks atomic.Int32 totalTasks atomic.Uint64 failedTasks atomic.Uint64 logger LoggerInterface panicHandler func(interface{}) } // NewWorkerPool создаёт новый пул воркеров func NewWorkerPool(maxWorkers int, logger LoggerInterface) *WorkerPool { if maxWorkers <= 0 { maxWorkers = 100 // ограничение по умолчанию } pool := &WorkerPool{ maxWorkers: maxWorkers, taskQueue: make(chan *WorkerTask, maxWorkers*10), stopChan: make(chan struct{}), logger: logger, panicHandler: defaultPanicHandler, } for i := 0; i < maxWorkers; i++ { pool.wg.Add(1) go pool.worker(i) } return pool } // defaultPanicHandler обрабатывает панику в горутине func defaultPanicHandler(r interface{}) { fmt.Printf("PANIC recovered: %v\n%s\n", r, debug.Stack()) } // SetPanicHandler устанавливает обработчик паник func (wp *WorkerPool) SetPanicHandler(handler func(interface{})) { if handler != nil { wp.panicHandler = handler } } // worker запускает воркер func (wp *WorkerPool) worker(id int) { defer func() { if r := recover(); r != nil { if wp.panicHandler != nil { wp.panicHandler(r) } if wp.logger != nil { wp.logger.Error(fmt.Sprintf("Worker %d panicked: %v", id, r)) } // Перезапускаем воркер wp.wg.Add(1) go wp.worker(id) } wp.wg.Done() }() for { select { case <-wp.stopChan: return case task, ok := <-wp.taskQueue: if !ok { return } wp.activeTasks.Add(1) wp.totalTasks.Add(1) func() { defer func() { if r := recover(); r != nil { wp.failedTasks.Add(1) if wp.panicHandler != nil { wp.panicHandler(r) } if wp.logger != nil { wp.logger.Error(fmt.Sprintf("Task %s panicked: %v", task.ID, r)) } } wp.activeTasks.Add(-1) }() if err := task.Handler(); err != nil { wp.failedTasks.Add(1) if wp.logger != nil { wp.logger.Error(fmt.Sprintf("Task %s failed: %v", task.ID, err)) } } }() } } } // Submit отправляет задачу в пул func (wp *WorkerPool) Submit(task *WorkerTask) error { select { case wp.taskQueue <- task: return nil case <-time.After(5 * time.Second): return fmt.Errorf("task queue full, task rejected: %s", task.ID) } } // SubmitFunc отправляет функцию как задачу func (wp *WorkerPool) SubmitFunc(id string, handler func() error) error { return wp.Submit(&WorkerTask{ ID: id, Handler: handler, CreatedAt: time.Now().UnixMilli(), }) } // GetStats возвращает статистику пула func (wp *WorkerPool) GetStats() map[string]interface{} { return map[string]interface{}{ "max_workers": wp.maxWorkers, "active_tasks": wp.activeTasks.Load(), "total_tasks": wp.totalTasks.Load(), "failed_tasks": wp.failedTasks.Load(), "queue_size": len(wp.taskQueue), "queue_capacity": cap(wp.taskQueue), } } // GetWorkerPoolStats возвращает статистику в структурированном виде func (wp *WorkerPool) GetWorkerPoolStats() *WorkerPoolStats { return &WorkerPoolStats{ MaxWorkers: wp.maxWorkers, ActiveTasks: wp.activeTasks.Load(), TotalTasks: wp.totalTasks.Load(), FailedTasks: wp.failedTasks.Load(), QueueSize: len(wp.taskQueue), QueueCapacity: cap(wp.taskQueue), } } // Stop останавливает пул воркеров func (wp *WorkerPool) Stop() { close(wp.stopChan) close(wp.taskQueue) wp.wg.Wait() } // SafeGo безопасно запускает горутину с обработкой паники func SafeGo(fn func(), panicHandler func(interface{})) { go func() { defer func() { if r := recover(); r != nil { if panicHandler != nil { panicHandler(r) } } }() fn() }() } // SafeGoWithLogger безопасно запускает горутину с логгером func SafeGoWithLogger(fn func(), logger LoggerInterface, name string) { go func() { defer func() { if r := recover(); r != nil { if logger != nil { logger.Error(fmt.Sprintf("Goroutine %s panicked: %v\n%s", name, r, debug.Stack())) } } }() fn() }() }