-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathexecutor.go
More file actions
144 lines (115 loc) · 4.19 KB
/
Copy pathexecutor.go
File metadata and controls
144 lines (115 loc) · 4.19 KB
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
package executor
import (
"log"
"github.com/pkg/errors"
"sync/atomic"
"sync"
)
// Core executor struct, has a WorkerPool channel of all currently available workers as all active workers would have published there JobChannel into the WorkerPool to get next job
type Executor struct {
// A pool of workers channels that are registered with the dispatcher
JobQueue JobChannel // A channel that we can send work requests on
JobWrapperChannel JobWrapperChannel // Channel on which all executed jobs are sent by all workers
WorkerCount int
Workers []*Worker
active int32
wg sync.WaitGroup
}
// Create new executor instance
// maxWorkers specify the number of workers/agents(underneath goroutines) one would like to spawn, general idea to have as many as the number of cpu available
func NewExecutor(workers int) *Executor {
return &Executor{JobQueue: make(JobChannel), JobWrapperChannel: make(JobWrapperChannel), WorkerCount: workers, wg: sync.WaitGroup{}}
}
// Start all workers on executor
func (e *Executor) Run() {
// starting n number of workers
atomic.StoreInt32(&e.active, 1)
for i := 1; i <= e.WorkerCount; i++ {
worker := NewWorker(&e.JobQueue, &e.JobWrapperChannel) // Get pointer to a worker instance
e.Workers = append(e.Workers, worker) // Append worker instance pointer to slick of pointers to worker instances
worker.Start(&e.wg)
}
}
// Stop all workers on execute
// Gracefully stop all operation
func (e *Executor) Stop() {
// Stop all workers
atomic.StoreInt32(&e.active, 0) // mark executor inactive to take further jobs
// Close the job queue to prevent further pushing of jobs
// Closing this queue will stop all the workers eventually as they are done processing message
log.Println("Closing job queue")
close(e.JobQueue)
// As soon as the jobqueue is closed all the workers will exit after processing the pending jobs
// So wait for the workers to finish
log.Println("Waiting on workers to finish")
e.wg.Wait()
log.Println("All workers exited")
// Since all job processing are done and all workers have exited, closed the job output channel
close(e.JobWrapperChannel)
}
// Stop all workers on execute
// Hard instant shutdown
// This will stop the workers instantly after processing whatever job they are doing
func (e *Executor) Abort() {
// Stop all workers
atomic.StoreInt32(&e.active, 0) // mark executor inactive to take further jobs
// Close the job queue to prevent further pushing of jobs
// Closing this queue will stop all the workers eventually as they are done processing message
log.Println("Closing job queue")
close(e.JobQueue)
// Stop all workers
for _, worker := range e.Workers {
log.Println("Stopping worker")
worker.Stop()
}
e.wg.Wait()
close(e.JobWrapperChannel)
}
// push job to the global JobQueue or bus
func (e *Executor) QueueJob(job Job) (err error) {
err = nil
defer func() {
if recover() != nil {
err = errors.New("Executor is not active to enqueue jobs")
}
}()
if atomic.LoadInt32(&e.active) == 0 {
// this will anyway be covered in defer
panic("Executor is not active to enqueue jobs")
} else {
e.JobQueue <- job
}
return err
}
// rescale the executor worker pool
func (e *Executor) ReScale(workers int) error {
// fixme add any given point of time not more than one call to this should be allowed, make it atomic
if workers <= 0 {
return errors.New("Inavlid value for workers")
} else if e.WorkerCount != workers {
if e.WorkerCount < workers {
// add workers as current workers is less than max workers
log.Println("Scaling up workers")
for i := e.WorkerCount; i < e.WorkerCount+ (workers- e.WorkerCount); i++ {
worker := NewWorker(&e.JobQueue, &e.JobWrapperChannel)
e.Workers = append(e.Workers, worker)
worker.Start(&e.wg)
}
} else {
// reduce worker from here
log.Println("Scaling down workers")
for i := 0; i < e.WorkerCount - workers; i++ {
e.Workers[i].Stop()
}
var newWorkers []*Worker
for i:= e.WorkerCount-workers; i < e.WorkerCount; i++ {
newWorkers = append(newWorkers, e.Workers[i])
}
e.Workers = newWorkers
}
// set the new worker count after resize
// fixme should be atomic with above for loop isnt it ?
e.WorkerCount = workers
}
return nil
}