github.com/fholzer/ordered-concurrently/v3@v3.0.0-20221001131746-406a6eece748/main.go (about)

     1  package orderedconcurrently
     2  
     3  import (
     4  	"container/heap"
     5  	"context"
     6  	"sync"
     7  )
     8  
     9  // Options options for Process
    10  type Options struct {
    11  	PoolSize         int
    12  	OutChannelBuffer int
    13  }
    14  
    15  // OrderedOutput is the output channel type from Process
    16  type OrderedOutput struct {
    17  	Value     interface{}
    18  	Remaining func() int
    19  }
    20  
    21  // WorkFunction interface
    22  // type WorkFunction func(ctx context.Context) interface{}
    23  type ProcessFunc func(interface{}) interface{}
    24  type processFuncGenerator func(int) (ProcessFunc, error)
    25  
    26  // Process processes work function based on input.
    27  // It Accepts an WorkFunction read channel, work function and concurrent go routine pool size.
    28  // It Returns an interface{} channel.
    29  func Process(ctx context.Context, inputChan <-chan interface{}, processFuncGenerator processFuncGenerator, options *Options) (<-chan OrderedOutput, error) {
    30  
    31  	outputChan := make(chan OrderedOutput, options.OutChannelBuffer)
    32  
    33  	if options.PoolSize < 1 {
    34  		// Set a minimum number of processors
    35  		options.PoolSize = 1
    36  	}
    37  
    38  	processors := make([]ProcessFunc, options.PoolSize)
    39  	for i := 0; i < options.PoolSize; i++ {
    40  		var err error
    41  		processors[i], err = processFuncGenerator(i)
    42  		if err != nil {
    43  			return nil, err
    44  		}
    45  	}
    46  
    47  	go func() {
    48  		processChan := make(chan *processInput, options.PoolSize)
    49  		aggregatorChan := make(chan *processInput, options.PoolSize)
    50  
    51  		// Go routine to print data in order
    52  		go func() {
    53  			var current uint64
    54  			outputHeap := &processInputHeap{}
    55  			defer func() {
    56  				close(outputChan)
    57  			}()
    58  			remaining := func() int {
    59  				return outputHeap.Len()
    60  			}
    61  			for item := range aggregatorChan {
    62  				heap.Push(outputHeap, item)
    63  				for top, ok := outputHeap.Peek(); ok && top.order == current; {
    64  					outputChan <- OrderedOutput{Value: heap.Pop(outputHeap).(*processInput).value, Remaining: remaining}
    65  					current++
    66  				}
    67  			}
    68  
    69  			for outputHeap.Len() > 0 {
    70  				outputChan <- OrderedOutput{Value: heap.Pop(outputHeap).(*processInput).value, Remaining: remaining}
    71  			}
    72  		}()
    73  
    74  		poolWg := sync.WaitGroup{}
    75  		poolWg.Add(options.PoolSize)
    76  		// Create a goroutine pool
    77  		for i := 0; i < options.PoolSize; i++ {
    78  			go func(process ProcessFunc) {
    79  				defer poolWg.Done()
    80  				for input := range processChan {
    81  					input.value = process(input.input)
    82  					aggregatorChan <- input
    83  				}
    84  			}(processors[i])
    85  		}
    86  
    87  		go func() {
    88  			poolWg.Wait()
    89  			close(aggregatorChan)
    90  		}()
    91  
    92  		go func() {
    93  			defer func() {
    94  				close(processChan)
    95  			}()
    96  			var order uint64
    97  			for input := range inputChan {
    98  				processChan <- &processInput{input: input, order: order}
    99  				order++
   100  			}
   101  		}()
   102  	}()
   103  	return outputChan, nil
   104  }