github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/state/pool.go (about) 1 // Copyright 2015 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package state 5 6 import ( 7 "sync" 8 9 "github.com/juju/errors" 10 "gopkg.in/juju/names.v2" 11 ) 12 13 // NewStatePool returns a new StatePool instance. It takes a State 14 // connected to the system (controller model). 15 func NewStatePool(systemState *State) *StatePool { 16 return &StatePool{ 17 systemState: systemState, 18 pool: make(map[string]*PoolItem), 19 } 20 } 21 22 // PoolItem holds a State and tracks how many requests are using it 23 // and whether it's been marked for removal. 24 type PoolItem struct { 25 state *State 26 references uint 27 remove bool 28 } 29 30 // StatePool is a cache of State instances for multiple 31 // models. Clients should call Release when they have finished with any 32 // state. 33 type StatePool struct { 34 systemState *State 35 // mu protects pool 36 mu sync.Mutex 37 pool map[string]*PoolItem 38 } 39 40 // Get returns a State for a given model from the pool, creating one 41 // if required. If the State has been marked for removal because there 42 // are outstanding uses, an error will be returned. 43 func (p *StatePool) Get(modelUUID string) (*State, error) { 44 if modelUUID == p.systemState.ModelUUID() { 45 return p.systemState, nil 46 } 47 48 p.mu.Lock() 49 defer p.mu.Unlock() 50 51 item, ok := p.pool[modelUUID] 52 if ok && item.remove { 53 // We don't want to allow increasing the refcount of a model 54 // that's been removed. 55 return nil, errors.Errorf("model %v has been removed", modelUUID) 56 } 57 if ok { 58 item.references++ 59 return item.state, nil 60 } 61 62 st, err := p.systemState.ForModel(names.NewModelTag(modelUUID)) 63 if err != nil { 64 return nil, errors.Annotatef(err, "failed to create state for model %v", modelUUID) 65 } 66 p.pool[modelUUID] = &PoolItem{state: st, references: 1} 67 return st, nil 68 } 69 70 // Release indicates that the client has finished using the State. If the 71 // state has been marked for removal, it will be closed and removed 72 // when the final Release is done. 73 func (p *StatePool) Release(modelUUID string) error { 74 if modelUUID == p.systemState.ModelUUID() { 75 // We don't maintain a refcount for the controller. 76 return nil 77 } 78 79 p.mu.Lock() 80 defer p.mu.Unlock() 81 82 item, ok := p.pool[modelUUID] 83 if !ok { 84 return errors.Errorf("unable to return unknown model %v to the pool", modelUUID) 85 } 86 if item.references == 0 { 87 return errors.Errorf("state pool refcount for model %v is already 0", modelUUID) 88 } 89 item.references-- 90 return p.maybeRemoveItem(modelUUID, item) 91 } 92 93 // Remove takes the state out of the pool and closes it, or marks it 94 // for removal if it's currently being used (indicated by Gets without 95 // corresponding Releases). 96 func (p *StatePool) Remove(modelUUID string) error { 97 if modelUUID == p.systemState.ModelUUID() { 98 // We don't manage the controller state. 99 return nil 100 } 101 102 p.mu.Lock() 103 defer p.mu.Unlock() 104 105 item, ok := p.pool[modelUUID] 106 if !ok { 107 // Don't require the client to keep track of what we've seen - 108 // ignore unknown model uuids. 109 return nil 110 } 111 item.remove = true 112 return p.maybeRemoveItem(modelUUID, item) 113 } 114 115 func (p *StatePool) maybeRemoveItem(modelUUID string, item *PoolItem) error { 116 if item.remove && item.references == 0 { 117 delete(p.pool, modelUUID) 118 return item.state.Close() 119 } 120 return nil 121 } 122 123 // SystemState returns the State passed in to NewStatePool. 124 func (p *StatePool) SystemState() *State { 125 return p.systemState 126 } 127 128 // KillWorkers tells the internal worker for all cached State 129 // instances in the pool to die. 130 func (p *StatePool) KillWorkers() { 131 p.mu.Lock() 132 defer p.mu.Unlock() 133 for _, item := range p.pool { 134 item.state.KillWorkers() 135 } 136 } 137 138 // Close closes all State instances in the pool. 139 func (p *StatePool) Close() error { 140 p.mu.Lock() 141 defer p.mu.Unlock() 142 143 var lastErr error 144 for _, item := range p.pool { 145 if item.references != 0 || item.remove { 146 logger.Warningf( 147 "state for %v leaked from pool - references: %v, removed: %v", 148 item.state.ModelUUID(), 149 item.references, 150 item.remove, 151 ) 152 } 153 err := item.state.Close() 154 if err != nil { 155 lastErr = err 156 } 157 } 158 p.pool = make(map[string]*PoolItem) 159 return errors.Annotate(lastErr, "at least one error closing a state") 160 }