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