package main import ( "fmt" "log" "time" ) type GrainPool interface { GetOrSpawn(id string) (Grain, error) Get(id string) (Grain, error) } type Ttl struct { Expires time.Time Item Grain } type GrainLocalPool struct { grains map[string]Grain expiry []Ttl Ttl time.Duration PoolSize int } func NewGrainLocalPool(size int, ttl time.Duration) *GrainLocalPool { ret := &GrainLocalPool{ grains: make(map[string]Grain), expiry: make([]Ttl, 0), Ttl: ttl, PoolSize: size, } cartPurge := time.NewTicker(time.Minute) go func() { <-cartPurge.C ret.Purge() }() return ret } func (p *GrainLocalPool) Purge() { lastChangeTime := time.Now().Add(-p.Ttl) keepChanged := lastChangeTime.Unix() for i := 0; i < len(p.expiry); i++ { item := p.expiry[i] if item.Expires.Before(time.Now()) { if item.Item.GetLastChange() > keepChanged { log.Printf("Changed item %s expired, keeping", item.Item.GetId()) p.expiry = append(p.expiry[:i], p.expiry[i+1:]...) p.expiry = append(p.expiry, item) } else { log.Printf("Item %s expired", item.Item.GetId()) delete(p.grains, item.Item.GetId()) p.expiry = append(p.expiry[:i], p.expiry[i+1:]...) } } else { break } } } func (p *GrainLocalPool) GetGrains() map[string]Grain { return p.grains } func (p *GrainLocalPool) GetOrSpawn(id string, generator func(id string) Grain) (Grain, error) { grain, ok := p.grains[id] if !ok { if len(p.grains) >= p.PoolSize { if p.expiry[0].Expires.Before(time.Now()) { delete(p.grains, p.expiry[0].Item.GetId()) p.expiry = p.expiry[1:] } else { return nil, fmt.Errorf("pool is full") } } grain = generator(id) p.grains[id] = grain } return grain, nil } func (p *GrainLocalPool) Get(id string) (Grain, error) { grain, ok := p.grains[id] if !ok { return nil, fmt.Errorf("grain not found") } return grain, nil }