github.com/amp-space/amp-sdk-go@v0.7.6/stdlib/task/pool.go (about) 1 package task 2 3 import ( 4 "context" 5 "fmt" 6 "sync" 7 "time" 8 9 "golang.org/x/sync/semaphore" 10 11 "github.com/amp-space/amp-sdk-go/stdlib/utils" 12 ) 13 14 type Pool struct { 15 Context 16 itemsAvailable *utils.Mailbox 17 chItems chan PoolUniqueID 18 sem *semaphore.Weighted 19 retryInterval time.Duration 20 poolItems map[PoolUniqueID]poolItem 21 poolItemsMu sync.RWMutex 22 } 23 24 func StartNewPool(name string, concurrency int, retryInterval time.Duration) *Pool { 25 p := &Pool{ 26 itemsAvailable: utils.NewMailbox(1000), 27 chItems: make(chan PoolUniqueID), 28 sem: semaphore.NewWeighted(int64(concurrency)), 29 retryInterval: retryInterval, 30 poolItems: make(map[PoolUniqueID]poolItem), 31 } 32 33 p.Context, _ = Start(&Task{ 34 Label: "pool", 35 OnStart: p.OnContextStarted, 36 }) 37 return p 38 } 39 40 type PoolUniqueID interface{} 41 42 type poolItem struct { 43 item PoolUniqueIDer 44 state poolItemState 45 retryWhen time.Time 46 } 47 48 type PoolUniqueIDer interface { 49 ID() PoolUniqueID 50 } 51 52 type poolItemState int 53 54 const ( 55 poolItemState_Available poolItemState = iota 56 poolItemState_InUse 57 poolItemState_Done 58 poolItemState_InRetryPool 59 ) 60 61 func (p *Pool) OnContextStarted(ctx Context) error { 62 p.Context.Go("deliverAvailableItems", p.deliverAvailableItems) 63 p.Context.Go("handleItemsAwaitingRetry", p.handleItemsAwaitingRetry) 64 return nil 65 } 66 67 func (p *Pool) NumItemsPending() int { 68 p.poolItemsMu.RLock() 69 defer p.poolItemsMu.RUnlock() 70 71 var n int 72 for _, item := range p.poolItems { 73 if item.state == poolItemState_Available || item.state == poolItemState_InRetryPool { 74 n++ 75 } 76 } 77 return n 78 } 79 80 func (p *Pool) Add(item PoolUniqueIDer) { 81 p.poolItemsMu.Lock() 82 defer p.poolItemsMu.Unlock() 83 84 _, exists := p.poolItems[item.ID()] 85 if exists { 86 return 87 } 88 p.poolItems[item.ID()] = poolItem{item, poolItemState_Available, time.Time{}} 89 p.itemsAvailable.Deliver(item.ID()) 90 } 91 92 func (p *Pool) Get(ctx context.Context) (item interface{}, err error) { 93 err = p.sem.Acquire(ctx, 1) 94 if err != nil { 95 return nil, err 96 } 97 defer func() { 98 if err != nil { 99 p.sem.Release(1) 100 } 101 }() 102 103 select { 104 case <-ctx.Done(): 105 return nil, ctx.Err() 106 107 case id := <-p.chItems: 108 p.poolItemsMu.RLock() 109 defer p.poolItemsMu.RUnlock() 110 entry, exists := p.poolItems[id] 111 if !exists { 112 panic(fmt.Sprintf("(%T) %v", id, id)) 113 } else if entry.state != poolItemState_Available { 114 panic(fmt.Sprintf("(%T) %v", id, id)) 115 } 116 return entry.item, nil 117 } 118 } 119 120 func (p *Pool) deliverAvailableItems(ctx Context) { 121 for { 122 select { 123 case <-ctx.Done(): 124 return 125 case <-p.itemsAvailable.Notify(): 126 for _, id := range p.itemsAvailable.RetrieveAll() { 127 var entry poolItem 128 var exists bool 129 func() { 130 p.poolItemsMu.RLock() 131 defer p.poolItemsMu.RUnlock() 132 entry, exists = p.poolItems[id] 133 }() 134 if !exists { 135 continue 136 } else if entry.state != poolItemState_Available { 137 panic("no") 138 } 139 140 select { 141 case <-ctx.Done(): 142 return 143 case p.chItems <- id: 144 } 145 } 146 } 147 } 148 } 149 150 func (p *Pool) setState(id PoolUniqueID, state poolItemState, retryWhen time.Time) { 151 entry, exists := p.poolItems[id] 152 if !exists { 153 panic(fmt.Sprintf("(%T) %v", id, id)) 154 } 155 entry.state = state 156 entry.retryWhen = retryWhen 157 p.poolItems[id] = entry 158 } 159 160 func (p *Pool) RetryLater(id PoolUniqueID, when time.Time) { 161 p.poolItemsMu.Lock() 162 defer p.poolItemsMu.Unlock() 163 p.setState(id, poolItemState_InRetryPool, when) 164 p.sem.Release(1) 165 } 166 167 func (p *Pool) ForceRetry(id PoolUniqueID) { 168 p.poolItemsMu.Lock() 169 defer p.poolItemsMu.Unlock() 170 171 entry, exists := p.poolItems[id] 172 if !exists { 173 panic(fmt.Sprintf("(%T) %v", id, id)) 174 } 175 176 switch entry.state { 177 case poolItemState_Available: 178 case poolItemState_InUse: 179 case poolItemState_InRetryPool: 180 p.setState(id, poolItemState_Available, time.Time{}) 181 p.itemsAvailable.Deliver(id) 182 case poolItemState_Done: 183 } 184 } 185 186 func (p *Pool) Complete(id PoolUniqueID) { 187 p.poolItemsMu.Lock() 188 defer p.poolItemsMu.Unlock() 189 p.setState(id, poolItemState_Done, time.Time{}) 190 p.sem.Release(1) 191 } 192 193 func (p *Pool) handleItemsAwaitingRetry(ctx Context) { 194 ticker := time.NewTicker(p.retryInterval) 195 for { 196 select { 197 case <-p.Context.Done(): 198 return 199 case <-ctx.Done(): 200 return 201 202 case <-ticker.C: 203 func() { 204 p.poolItemsMu.Lock() 205 defer p.poolItemsMu.Unlock() 206 207 now := time.Now() 208 209 for id, entry := range p.poolItems { 210 if entry.state != poolItemState_InRetryPool { 211 continue 212 } else if !entry.retryWhen.Before(now) { 213 continue 214 } 215 p.setState(id, poolItemState_Available, time.Time{}) 216 p.itemsAvailable.Deliver(id) 217 } 218 }() 219 } 220 } 221 }