github.com/mattyw/juju@v0.0.0-20140610034352-732aecd63861/state/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 } 27 28 func NewResources() *Resources { 29 return &Resources{ 30 resources: make(map[string]Resource), 31 } 32 } 33 34 // Get returns the resource for the given id, or 35 // nil if there is no such resource. 36 func (rs *Resources) Get(id string) Resource { 37 rs.mu.Lock() 38 defer rs.mu.Unlock() 39 return rs.resources[id] 40 } 41 42 // Register registers the given resource. It returns a unique 43 // identifier for the resource which can then be used in 44 // subsequent API requests to refer to the resource. 45 func (rs *Resources) Register(r Resource) string { 46 rs.mu.Lock() 47 defer rs.mu.Unlock() 48 rs.maxId++ 49 id := strconv.FormatUint(rs.maxId, 10) 50 rs.resources[id] = r 51 return id 52 } 53 54 // RegisterNamed registers the given resource. Callers must supply a unique 55 // name for the given resource. It is an error to try to register another 56 // resource with the same name as an already registered name. (This could be 57 // softened that you can overwrite an existing one and it will be Stopped and 58 // replaced, but we don't have a need for that yet.) 59 // It is also an error to supply a name that is an integer string, since that 60 // collides with the auto-naming from Register. 61 func (rs *Resources) RegisterNamed(name string, r Resource) error { 62 rs.mu.Lock() 63 defer rs.mu.Unlock() 64 if _, err := strconv.Atoi(name); err == nil { 65 return fmt.Errorf("RegisterNamed does not allow integer names: %q", name) 66 } 67 if _, ok := rs.resources[name]; ok { 68 return fmt.Errorf("resource %q already registered", name) 69 } 70 rs.resources[name] = r 71 return nil 72 } 73 74 // Stop stops the resource with the given id and unregisters it. 75 // It returns any error from the underlying Stop call. 76 // It does not return an error if the resource has already 77 // been unregistered. 78 func (rs *Resources) Stop(id string) error { 79 // We don't hold the mutex while calling Stop, because 80 // that might take a while and we don't want to 81 // stop all other resource manipulation while we do so. 82 // If resources.Stop is called concurrently, we'll get 83 // two concurrent calls to Stop, but that should fit 84 // well with the way we invariably implement Stop. 85 r := rs.Get(id) 86 if r == nil { 87 return nil 88 } 89 err := r.Stop() 90 rs.mu.Lock() 91 defer rs.mu.Unlock() 92 delete(rs.resources, id) 93 return err 94 } 95 96 // StopAll stops all the resources. 97 func (rs *Resources) StopAll() { 98 rs.mu.Lock() 99 defer rs.mu.Unlock() 100 for _, r := range rs.resources { 101 if err := r.Stop(); err != nil { 102 logger.Errorf("error stopping %T resource: %v", r, err) 103 } 104 } 105 rs.resources = make(map[string]Resource) 106 } 107 108 // Count returns the number of resources currently held. 109 func (rs *Resources) Count() int { 110 rs.mu.Lock() 111 defer rs.mu.Unlock() 112 return len(rs.resources) 113 } 114 115 // StringResource is just a regular 'string' that matches the Resource 116 // interface. 117 type StringResource string 118 119 func (StringResource) Stop() error { 120 return nil 121 } 122 123 func (s StringResource) String() string { 124 return string(s) 125 }