217 lines
6.3 KiB
Go
217 lines
6.3 KiB
Go
/*
|
||
* 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()
|
||
}()
|
||
}
|