github.com/wormhole-foundation/wormhole-explorer/common@v0.0.0-20240604151348-09585b5b97c5/pool/pool.go (about)

     1  package pool
     2  
     3  import (
     4  	"sort"
     5  	"time"
     6  
     7  	"golang.org/x/net/context"
     8  	"golang.org/x/time/rate"
     9  )
    10  
    11  // Pool is a pool of items.
    12  type Pool struct {
    13  	items []Item
    14  }
    15  
    16  // Item defines the item of the pool.
    17  type Item struct {
    18  	// Id is the item ID.
    19  	Id string
    20  	// description of the item.
    21  	Description string
    22  	// priority is the priority of the item.
    23  	// The lower the value, the higher the priority.
    24  	priority uint8
    25  	// rateLimit is the rate limiter for the item.
    26  	rateLimit *rate.Limiter
    27  }
    28  
    29  // NewPool creates a new pool.
    30  func NewPool(cfg []Config) *Pool {
    31  	p := &Pool{}
    32  	for _, c := range cfg {
    33  		p.addItem(c)
    34  	}
    35  	return p
    36  }
    37  
    38  // addItem adds a new item to the pool.
    39  func (p *Pool) addItem(cfg Config) {
    40  	i := Item{
    41  		Id:          cfg.Id,
    42  		Description: cfg.Description,
    43  		priority:    cfg.Priority,
    44  		rateLimit: rate.NewLimiter(
    45  			rate.Every(time.Minute/time.Duration(cfg.RequestsPerMinute)), 1),
    46  	}
    47  	p.items = append(p.items, i)
    48  }
    49  
    50  // GetItem returns the next available item of the pool.
    51  func (p *Pool) GetItem() Item {
    52  	// check if there is no item
    53  	if len(p.items) == 0 {
    54  		return Item{}
    55  	}
    56  
    57  	// get the next available item
    58  	itemWithScore := []struct {
    59  		item  Item
    60  		score float64
    61  	}{}
    62  
    63  	now := time.Now()
    64  	for _, i := range p.items {
    65  		tokenAt := i.rateLimit.TokensAt(now)
    66  		itemWithScore = append(itemWithScore, struct {
    67  			item  Item
    68  			score float64
    69  		}{
    70  			item:  i,
    71  			score: tokenAt,
    72  		})
    73  	}
    74  
    75  	// sort by score and priority
    76  	sort.Slice(itemWithScore, func(i, j int) bool {
    77  		if itemWithScore[i].score == itemWithScore[j].score {
    78  			return itemWithScore[i].item.priority < itemWithScore[j].item.priority
    79  		}
    80  		return itemWithScore[i].score > itemWithScore[j].score
    81  	})
    82  
    83  	return itemWithScore[0].item
    84  }
    85  
    86  // GetItems returns the list of items sorted by score and priority.
    87  // Once there is an event on the item, it must be notified using the method NotifyEvent.
    88  func (p *Pool) GetItems() []Item {
    89  	if len(p.items) == 0 {
    90  		return []Item{}
    91  	}
    92  
    93  	itemsWithScore := []struct {
    94  		item  Item
    95  		score float64
    96  	}{}
    97  
    98  	now := time.Now()
    99  	for _, i := range p.items {
   100  		tokenAt := i.rateLimit.TokensAt(now)
   101  		itemsWithScore = append(itemsWithScore, struct {
   102  			item  Item
   103  			score float64
   104  		}{
   105  			item:  i,
   106  			score: tokenAt,
   107  		})
   108  	}
   109  
   110  	// sort by score and priority
   111  	sort.Slice(itemsWithScore, func(i, j int) bool {
   112  		if itemsWithScore[i].score == itemsWithScore[j].score {
   113  			return itemsWithScore[i].item.priority < itemsWithScore[j].item.priority
   114  		}
   115  		return itemsWithScore[i].score > itemsWithScore[j].score
   116  	})
   117  
   118  	// convert itemsWithScore to items
   119  	items := []Item{}
   120  	for _, i := range itemsWithScore {
   121  		items = append(items, i.item)
   122  	}
   123  	return items
   124  }
   125  
   126  // Wait waits for the rate limiter to allow the next item request.
   127  func (i *Item) Wait(ctx context.Context) error {
   128  	return i.rateLimit.Wait(ctx)
   129  }