github.com/makyo/juju@v0.0.0-20160425123129-2608902037e9/apiserver/common/resource.go (about)

     1  // Copyright 2013 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package common
     5  
     6  import (
     7  	"fmt"
     8  	"strconv"
     9  	"sync"
    10  )
    11  
    12  // Resource represents any resource that should be cleaned up when an
    13  // API connection terminates. The Stop method will be called when
    14  // that happens.
    15  type Resource interface {
    16  	Stop() error
    17  }
    18  
    19  // Resources holds all the resources for a connection.
    20  // It allows the registration of resources that will be cleaned
    21  // up when a connection terminates.
    22  type Resources struct {
    23  	mu        sync.Mutex
    24  	maxId     uint64
    25  	resources map[string]Resource
    26  	// The stack is used to control the order of destruction.
    27  	// last registered, first stopped.
    28  	stack []string
    29  }
    30  
    31  func NewResources() *Resources {
    32  	return &Resources{
    33  		resources: make(map[string]Resource),
    34  	}
    35  }
    36  
    37  // Get returns the resource for the given id, or
    38  // nil if there is no such resource.
    39  func (rs *Resources) Get(id string) Resource {
    40  	rs.mu.Lock()
    41  	defer rs.mu.Unlock()
    42  	return rs.resources[id]
    43  }
    44  
    45  // Register registers the given resource. It returns a unique
    46  // identifier for the resource which can then be used in
    47  // subsequent API requests to refer to the resource.
    48  func (rs *Resources) Register(r Resource) string {
    49  	rs.mu.Lock()
    50  	defer rs.mu.Unlock()
    51  	rs.maxId++
    52  	id := strconv.FormatUint(rs.maxId, 10)
    53  	rs.resources[id] = r
    54  	rs.stack = append(rs.stack, id)
    55  	logger.Tracef("registered unnamed resource: %s", id)
    56  	return id
    57  }
    58  
    59  // RegisterNamed registers the given resource. Callers must supply a unique
    60  // name for the given resource. It is an error to try to register another
    61  // resource with the same name as an already registered name. (This could be
    62  // softened that you can overwrite an existing one and it will be Stopped and
    63  // replaced, but we don't have a need for that yet.)
    64  // It is also an error to supply a name that is an integer string, since that
    65  // collides with the auto-naming from Register.
    66  func (rs *Resources) RegisterNamed(name string, r Resource) error {
    67  	rs.mu.Lock()
    68  	defer rs.mu.Unlock()
    69  	if _, err := strconv.Atoi(name); err == nil {
    70  		return fmt.Errorf("RegisterNamed does not allow integer names: %q", name)
    71  	}
    72  	if _, ok := rs.resources[name]; ok {
    73  		return fmt.Errorf("resource %q already registered", name)
    74  	}
    75  	rs.resources[name] = r
    76  	rs.stack = append(rs.stack, name)
    77  	logger.Tracef("registered named resource: %s", name)
    78  	return nil
    79  }
    80  
    81  // Stop stops the resource with the given id and unregisters it.
    82  // It returns any error from the underlying Stop call.
    83  // It does not return an error if the resource has already
    84  // been unregistered.
    85  func (rs *Resources) Stop(id string) error {
    86  	// We don't hold the mutex while calling Stop, because
    87  	// that might take a while and we don't want to
    88  	// stop all other resource manipulation while we do so.
    89  	// If resources.Stop is called concurrently, we'll get
    90  	// two concurrent calls to Stop, but that should fit
    91  	// well with the way we invariably implement Stop.
    92  	logger.Tracef("stopping resource: %s", id)
    93  	r := rs.Get(id)
    94  	if r == nil {
    95  		return nil
    96  	}
    97  	err := r.Stop()
    98  	rs.mu.Lock()
    99  	defer rs.mu.Unlock()
   100  	delete(rs.resources, id)
   101  	for pos := 0; pos < len(rs.stack); pos++ {
   102  		if rs.stack[pos] == id {
   103  			rs.stack = append(rs.stack[0:pos], rs.stack[pos+1:]...)
   104  			break
   105  		}
   106  	}
   107  	return err
   108  }
   109  
   110  // StopAll stops all the resources.
   111  func (rs *Resources) StopAll() {
   112  	rs.mu.Lock()
   113  	defer rs.mu.Unlock()
   114  	for i := len(rs.stack); i > 0; i-- {
   115  		id := rs.stack[i-1]
   116  		r := rs.resources[id]
   117  		logger.Tracef("stopping resource: %s", id)
   118  		if err := r.Stop(); err != nil {
   119  			logger.Errorf("error stopping %T resource: %v", r, err)
   120  		}
   121  	}
   122  	rs.resources = make(map[string]Resource)
   123  	rs.stack = nil
   124  }
   125  
   126  // Count returns the number of resources currently held.
   127  func (rs *Resources) Count() int {
   128  	rs.mu.Lock()
   129  	defer rs.mu.Unlock()
   130  	return len(rs.resources)
   131  }
   132  
   133  // StringResource is just a regular 'string' that matches the Resource
   134  // interface.
   135  type StringResource string
   136  
   137  func (StringResource) Stop() error {
   138  	return nil
   139  }
   140  
   141  func (s StringResource) String() string {
   142  	return string(s)
   143  }
   144  
   145  // ValueResource is a Resource with a no-op Stop method, containing an
   146  // interface{} value.
   147  type ValueResource struct {
   148  	Value interface{}
   149  }
   150  
   151  func (r ValueResource) Stop() error {
   152  	return nil
   153  }