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  }