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 }