-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathmanager.go
285 lines (220 loc) · 9.25 KB
/
manager.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
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
// Created by Clayton Brown. See "LICENSE" file in root for more info.
package managers
import (
"errors"
"fmt"
"sync"
)
///////////////////////////
// PUBLIC MANAGERS STATE //
///////////////////////////
// Internal managers struct used for public requests. This data is just
// for storing a link from a manager name to the manager. The main use case
// for this is to allow managers to be created and used without tracking the
// the handle to the manager.
var managersMap = make(map[string]*Manager)
var managersLock = sync.Mutex{}
// Used to determine whether or not errors which occurred during a manager processing
// a function are logged to the console or not.
var LOG_PROCESSING_ERRORS = true
// getManager is an internal function to grab a manager from the managersMap.
// This function uses the managersLock to ensure thread safety.
func getManager(managerName string) (*Manager, bool) {
managersLock.Lock()
defer managersLock.Unlock()
manager, ok := managersMap[managerName]
return manager, ok
}
// deleteManager is an internal function for deleting a manager from the managersMap.
// This function uses the managersLock to ensure thread safety.
func deleteManager(managerName string) {
managersLock.Lock()
defer managersLock.Unlock()
delete(managersMap, managerName)
}
/////////////
// MANAGER //
/////////////
// Manager is the struct used to process and respond to requests. The object itself is
// quite simple. See below descriptions for what each attribute does.
type Manager struct {
// Name is just a user defined name for the manager. This is the only public
// value because it is not read anywhere internally, so we can pass it on to the users
// to handle.
Name string
// Requests is a channel used to keep track of everything the manager has
// been asked to do.
requests chan *Request
// Whether or not the manager is currently processing
running bool
// Functions is a map of request type to respective processing function.
// These functions will take in a request interface and respond with a response interface.
functions map[string]func(managerState any, request any) any
// stateLock determines whether or not values in the Manager can be read or editted.
// The only exception is the Name, which the "managers" package doesn't care about.
// We will let clients control access to this.
stateLock sync.Mutex
}
// Start will start the processing function for the manager. The for loop below is the
// loop which handles the process. It's very straightforward. Just loop through and process
// each request as they come through until a kill request is sent. This function is blocking
// and you should detach it if you want the manager to function correctly.
func (manager *Manager) Start(managerState any) {
// Freeze the state so that the manager can be set to running. Then unfreeze so
// the rest of the data can be read (like the functions)
manager.stateLock.Lock()
manager.running = true
manager.stateLock.Unlock()
// Big for loop for the manager to handle incoming requests.
for {
// Wait for a request to come in before parsing it
// and deciding what to do based on the route.
request := <-manager.requests
// Response object data. Initialize to nil values. The response
// will be populated with data as the route function is processed.
response := responseStruct{
Data: nil,
Error: nil,
}
// Internal kill command for the manager. When manager.Kill() is called, it
// will send this route. This will just store an arbitrary response and then
// break out of the processing loop. Technically a user shouldn't be allowed
// to attach a route with this name, but since it won't break anything, we won't
// strictly enforce it.
if request.Route == "state|kill-manager" {
// Signify the request was processed and then break out of the processing loop.
request.storeResponse(response)
break
// User defined commands will end up here
} else {
// Check to see if that route was added.
// If it wasn't, return an error.
// If it was, process the job .
function, ok := manager.getFunction(request.Route)
if !ok {
response.Error = errors.New("No function named " + request.Route + " added to " + manager.Name + " manager.")
} else {
// If here, it's time to process the job. We simply send the current managerState
// to the processing function along with the requested data.
response.Data = function(managerState, request.Data)
// If there is an error with the process, set the error appropriately. Also
// remove the original response data as it was an error.
if err, ok := response.Data.(error); ok {
response.Data = nil
response.Error = err
}
}
// If there is an error, just let the user know about it. (If they have logging enabled that is.)
if response.Error != nil && LOG_PROCESSING_ERRORS {
fmt.Println("Error in manager, " + manager.Name + ":")
fmt.Println(response.Error)
}
// Add the response to the request. All this does is send the response in the
// response channel on the request. This allows the "Wait" function on the
// request to respond appropriately.
request.storeResponse(response)
}
}
// Freeze the state so that the manager can be set to not running
manager.stateLock.Lock()
manager.running = false
manager.stateLock.Unlock()
}
// IsRunning will just return the value of manager.running. Simple binding so that we
// can ensure thread safety of manager attributes.
func (manager *Manager) IsRunning() bool {
manager.stateLock.Lock()
defer manager.stateLock.Unlock()
return manager.running
}
///////////////////////
// REQUEST FUNCTIONS //
///////////////////////
// Send will send a job to the manager and not wait for completion. See Request.Send()
// for a more detailed description of how this works.
func (manager *Manager) Send(route string, data any) *Request {
// Create a new request object
request := NewRequest(route, data)
// Send the job to the manager
manager.requests <- request
// Respond with the request
return request
}
// SendRequest will queue a premade request to the manager. This is mainly just to ensure
// that the .requests field can stay hidden and unaccessible to users. However, it can also
// be utilized if a user wishes to interact with it in a different way.
func (manager *Manager) SendRequest(request *Request) {
manager.requests <- request
}
// Await will send a job to the manager and await completion. See Request.Await()
// for a more detailed description of how this works.
func (manager *Manager) Await(route string, data any) (any, error) {
// Create and send the request to the manager
request := manager.Send(route, data)
// Wait for the request to complete
return request.Wait()
}
// AwaitRequest will queue a premade request to the manager. This is mainly just to ensure
// that the .requests field can stay hidden.
func (manager *Manager) AwaitRequest(request *Request) (any, error) {
manager.SendRequest(request)
return request.Wait()
}
/////////////
// CONTROL //
/////////////
// Kill is an internal request which will halt the manager. This is blocking and will wait
// for the manager to actually stop processing. Just detach in a go-routine if you'd like to
// kill without waiting for a success.
func (manager *Manager) Kill() error {
// Just send a kill request and wait for completion
_, err := manager.Await("state|kill-manager", nil)
return err
}
// Remove is the function which will remove the manager from the public map.
// Once this is done, the manager should be deleted/removed from memory.
func (manager *Manager) Remove() error {
// Can only remove if the manager is not running
if manager.IsRunning() {
return errors.New("Unable to remove manager " + manager.Name + " because it is currently running.")
}
deleteManager(manager.Name)
return nil
}
// Kill is a default request which will halt the manager AND remove it from the map
func (manager *Manager) KillAndRemove() error {
// Just send a kill request and wait for completion
_, err := manager.Await("state|kill-manager", nil)
if err != nil {
return err
}
// Afterwards, remove the manager from the manager map
return manager.Remove()
}
///////////////
// FUNCTIONS //
///////////////
// Attach will attach a function to a manager at a specific route. Once a function is
// attached, requests sent to the manager are able to find and use the function.
func (manager *Manager) Attach(route string, function func(managerState any, request any) any) {
// This is simple as just attaching the function
manager.stateLock.Lock()
defer manager.stateLock.Unlock()
manager.functions[route] = function
}
// Detach will remove a specified route from a manager. Once a function is detached,
// requests sent to the manager will no longer be accessible.
func (manager *Manager) Detach(route string) {
manager.stateLock.Lock()
defer manager.stateLock.Unlock()
delete(manager.functions, route)
}
// getFunction returns the function of a given name. This is just an internal function
// to handle race conditions.
func (manager *Manager) getFunction(route string) (func(managerState any, request any) any, bool) {
// This is simple as just returning the function
manager.stateLock.Lock()
defer manager.stateLock.Unlock()
function, ok := manager.functions[route]
return function, ok
}