This repository has been archived by the owner on Nov 9, 2022. It is now read-only.
forked from contribsys/faktory_worker_go
-
Notifications
You must be signed in to change notification settings - Fork 0
/
manager.go
205 lines (178 loc) · 5.19 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
package faktory_worker
import (
"context"
"log"
"math/rand"
"os"
"strconv"
"sync"
"time"
faktory "github.com/contribsys/faktory/client"
)
// Manager coordinates the processes for the worker. It is responsible for
// starting and stopping goroutines to perform work at the desired concurrency level
type Manager struct {
Concurrency int
Logger Logger
ProcessWID string
Labels []string
Pool *faktory.Pool
queues []string
middleware []MiddlewareFunc
state string // "", "quiet" or "terminate"
// The done channel will always block unless
// the system is shutting down.
done chan interface{}
shutdownWaiter *sync.WaitGroup
jobHandlers map[string]Handler
eventHandlers map[lifecycleEventType][]LifecycleEventHandler
// This only needs to be computed once. Store it here to keep things fast.
weightedPriorityQueuesEnabled bool
weightedQueues []string
}
// Register a handler for the given jobtype. It is expected that all jobtypes
// are registered upon process startup.
//
// mgr.Register("ImportantJob", ImportantFunc)
func (mgr *Manager) Register(name string, fn Perform) {
mgr.jobHandlers[name] = func(ctx context.Context, job *faktory.Job) error {
return fn(ctx, job.Args...)
}
}
// Register a callback to be fired when a process lifecycle event occurs.
// These are useful for hooking into process startup or shutdown.
func (mgr *Manager) On(event lifecycleEventType, fn LifecycleEventHandler) {
mgr.eventHandlers[event] = append(mgr.eventHandlers[event], fn)
}
// After calling Quiet(), no more jobs will be pulled
// from Faktory by this process.
func (mgr *Manager) Quiet() {
mgr.Logger.Info("Quieting...")
mgr.state = "quiet"
mgr.fireEvent(Quiet)
}
// Terminate signals that the various components should shutdown.
// Blocks on the shutdownWaiter until all components have finished.
func (mgr *Manager) Terminate(reallydie bool) {
mgr.Logger.Info("Shutting down...")
mgr.state = "terminate"
close(mgr.done)
mgr.fireEvent(Shutdown)
mgr.shutdownWaiter.Wait()
mgr.Pool.Close()
mgr.Logger.Info("Goodbye")
if reallydie {
os.Exit(0)
}
}
// NewManager returns a new manager with default values.
func NewManager() *Manager {
return &Manager{
Concurrency: 20,
Logger: NewStdLogger(),
Labels: []string{"golang"},
Pool: nil,
state: "",
queues: []string{"default"},
done: make(chan interface{}),
shutdownWaiter: &sync.WaitGroup{},
jobHandlers: map[string]Handler{},
eventHandlers: map[lifecycleEventType][]LifecycleEventHandler{
Startup: []LifecycleEventHandler{},
Quiet: []LifecycleEventHandler{},
Shutdown: []LifecycleEventHandler{},
},
weightedPriorityQueuesEnabled: false,
weightedQueues: []string{},
}
}
func (mgr *Manager) setUpWorkerProcess() {
// This will signal to Faktory that all connections from this process
// are worker connections.
if len(mgr.ProcessWID) == 0 {
rand.Seed(time.Now().UnixNano())
faktory.RandomProcessWid = strconv.FormatInt(rand.Int63(), 32)
} else {
faktory.RandomProcessWid = mgr.ProcessWID
}
// Set labels to be displayed in the UI
faktory.Labels = mgr.Labels
if mgr.Pool == nil {
pool, err := faktory.NewPool(mgr.Concurrency + 2)
if err != nil {
log.Panicf("Couldn't create Faktory connection pool: %v", err)
}
mgr.Pool = pool
}
}
// Run starts processing jobs.
// This method does not return.
func (mgr *Manager) Run() {
mgr.setUpWorkerProcess()
mgr.fireEvent(Startup)
go heartbeat(mgr)
mgr.Logger.Infof("faktory_worker_go %s PID %d now ready to process jobs", Version, os.Getpid())
for i := 0; i < mgr.Concurrency; i++ {
go process(mgr, i)
}
sigchan := hookSignals()
for {
sig := <-sigchan
mgr.handleEvent(signalMap[sig])
}
}
// One of the Process*Queues methods should be called once before Run()
func (mgr *Manager) ProcessStrictPriorityQueues(queues ...string) {
mgr.queues = queues
mgr.weightedPriorityQueuesEnabled = false
}
func (mgr *Manager) ProcessWeightedPriorityQueues(queues map[string]int) {
uniqueQueues := queueKeys(queues)
weightedQueues := expandWeightedQueues(queues)
mgr.queues = uniqueQueues
mgr.weightedQueues = weightedQueues
mgr.weightedPriorityQueuesEnabled = true
}
func (mgr *Manager) queueList() []string {
if mgr.weightedPriorityQueuesEnabled {
sq := shuffleQueues(mgr.weightedQueues)
return uniqQueues(len(mgr.queues), sq)
}
return mgr.queues
}
func (mgr *Manager) fireEvent(event lifecycleEventType) {
for _, fn := range mgr.eventHandlers[event] {
err := fn(mgr)
if err != nil {
mgr.Logger.Errorf("Error running lifecycle event handler: %v", err)
}
}
}
func (mgr *Manager) with(fn func(cl *faktory.Client) error) error {
if mgr.Pool == nil {
panic("No Pool set on Manager, have you called manager.Run() yet?")
}
return mgr.Pool.With(fn)
}
func (mgr *Manager) handleEvent(sig string) string {
if sig == mgr.state {
return mgr.state
}
if sig == "quiet" && mgr.state == "terminate" {
// this is a no-op, a terminating process is quiet already
return mgr.state
}
switch sig {
case "terminate":
go func() {
mgr.Terminate(true)
}()
case "quiet":
go func() {
mgr.Quiet()
}()
case "dump":
dumpThreads(mgr.Logger)
}
return ""
}