package dispatch import ( "context" "fmt" "sync" "time" ) // Task represents a unit of work that can be executed type Task interface { ID() string Execute(ctx context.Context) (interface{}, error) } // TaskResult holds the result of a task execution type TaskResult struct { TaskID string Result interface{} Error error Duration time.Duration StartTime time.Time EndTime time.Time } // Dispatcher manages parallel task execution type Dispatcher struct { mu sync.RWMutex maxConcurrency int results map[string]*TaskResult inProgress map[string]bool completed map[string]bool semaphore chan struct{} taskOrder []string } // NewDispatcher creates a new task dispatcher func NewDispatcher(maxConcurrency int) *Dispatcher { if maxConcurrency <= 0 { maxConcurrency = 10 } return &Dispatcher{ maxConcurrency: maxConcurrency, results: make(map[string]*TaskResult), inProgress: make(map[string]bool), completed: make(map[string]bool), semaphore: make(chan struct{}, maxConcurrency), taskOrder: make([]string, 0), } } // DispatchAll dispatches all tasks concurrently and waits for completion func (d *Dispatcher) DispatchAll(ctx context.Context, tasks []Task) (map[string]*TaskResult, error) { if len(tasks) == 0 { return make(map[string]*TaskResult), nil } d.mu.Lock() d.taskOrder = make([]string, len(tasks)) for i, task := range tasks { d.taskOrder[i] = task.ID() } d.mu.Unlock() var wg sync.WaitGroup errChan := make(chan error, len(tasks)) // Launch all tasks concurrently with concurrency limit for _, task := range tasks { wg.Add(1) go func(t Task) { defer wg.Done() // Acquire semaphore slot select { case d.semaphore <- struct{}{}: defer func() { <-d.semaphore }() case <-ctx.Done(): errChan <- ctx.Err() return } err := d.executeTask(ctx, t) if err != nil { errChan <- err } }(task) } // Wait for all tasks to complete wg.Wait() close(errChan) // Collect errors var errors []error for err := range errChan { if err != nil { errors = append(errors, err) } } d.mu.RLock() resultsCopy := make(map[string]*TaskResult) for id, result := range d.results { resultsCopy[id] = result } d.mu.RUnlock() if len(errors) > 0 { return resultsCopy, fmt.Errorf("tasks completed with %d errors", len(errors)) } return resultsCopy, nil } // executeTask executes a single task and stores the result func (d *Dispatcher) executeTask(ctx context.Context, task Task) error { taskID := task.ID() d.mu.Lock() d.inProgress[taskID] = true d.mu.Unlock() result := &TaskResult{ TaskID: taskID, StartTime: time.Now(), } // Execute task with context timeout taskCtx, cancel := context.WithCancel(ctx) defer cancel() taskResult, err := task.Execute(taskCtx) result.EndTime = time.Now() result.Duration = result.EndTime.Sub(result.StartTime) result.Result = taskResult result.Error = err d.mu.Lock() d.results[taskID] = result d.inProgress[taskID] = false d.completed[taskID] = true d.mu.Unlock() return nil } // GetResult retrieves the result of a task func (d *Dispatcher) GetResult(taskID string) (*TaskResult, bool) { d.mu.RLock() defer d.mu.RUnlock() result, exists := d.results[taskID] return result, exists } // GetResults retrieves all results func (d *Dispatcher) GetResults() map[string]*TaskResult { d.mu.RLock() defer d.mu.RUnlock() resultsCopy := make(map[string]*TaskResult) for id, result := range d.results { resultsCopy[id] = result } return resultsCopy } // GetStats returns dispatcher statistics func (d *Dispatcher) GetStats() map[string]interface{} { d.mu.RLock() defer d.mu.RUnlock() completed := len(d.completed) totalDuration := time.Duration(0) maxDuration := time.Duration(0) minDuration := time.Duration(0) for _, result := range d.results { totalDuration += result.Duration if result.Duration > maxDuration { maxDuration = result.Duration } if minDuration == 0 || result.Duration < minDuration { minDuration = result.Duration } } avgDuration := time.Duration(0) if completed > 0 { avgDuration = totalDuration / time.Duration(completed) } return map[string]interface{}{ "total_tasks": len(d.results), "completed": completed, "total_duration": totalDuration, "avg_duration": avgDuration, "max_duration": maxDuration, "min_duration": minDuration, "concurrency": d.maxConcurrency, } } // GetExecutionTime returns the total execution time (wallclock) func (d *Dispatcher) GetExecutionTime() time.Duration { d.mu.RLock() defer d.mu.RUnlock() if len(d.results) == 0 { return 0 } var minStart time.Time var maxEnd time.Time for _, result := range d.results { if minStart.IsZero() || result.StartTime.Before(minStart) { minStart = result.StartTime } if result.EndTime.After(maxEnd) { maxEnd = result.EndTime } } return maxEnd.Sub(minStart) } // GetTotalTaskDuration returns the sum of all task durations func (d *Dispatcher) GetTotalTaskDuration() time.Duration { d.mu.RLock() defer d.mu.RUnlock() total := time.Duration(0) for _, result := range d.results { total += result.Duration } return total } // GetSpeedup returns the speedup factor (sum of task durations / wallclock time) func (d *Dispatcher) GetSpeedup() float64 { totalDuration := d.GetTotalTaskDuration() executionTime := d.GetExecutionTime() if executionTime == 0 { return 0 } return float64(totalDuration) / float64(executionTime) } // IsComplete checks if a task is complete func (d *Dispatcher) IsComplete(taskID string) bool { d.mu.RLock() defer d.mu.RUnlock() return d.completed[taskID] } // AreAllComplete checks if all tasks are complete func (d *Dispatcher) AreAllComplete() bool { d.mu.RLock() defer d.mu.RUnlock() return len(d.completed) == len(d.results) } // GetCompletedCount returns the number of completed tasks func (d *Dispatcher) GetCompletedCount() int { d.mu.RLock() defer d.mu.RUnlock() return len(d.completed) } // WaitForCompletion waits for all tasks to complete or context to be cancelled func (d *Dispatcher) WaitForCompletion(ctx context.Context) error { ticker := time.NewTicker(10 * time.Millisecond) defer ticker.Stop() for { select { case <-ctx.Done(): return ctx.Err() case <-ticker.C: if d.AreAllComplete() { return nil } } } }