Initial cloud-services repo - gateway service + pkg modules
This commit is contained in:
35
pkg/utils/threadpool/callable.go
Normal file
35
pkg/utils/threadpool/callable.go
Normal file
@@ -0,0 +1,35 @@
|
||||
package threadpool
|
||||
|
||||
// Callable the tasks which returns the output after exit should implement this interface
|
||||
type Callable interface {
|
||||
Call() interface{}
|
||||
}
|
||||
|
||||
// Future is the handle returned after submitting a callable task to the thread threadpool
|
||||
type Future struct {
|
||||
response chan interface{}
|
||||
done bool
|
||||
}
|
||||
|
||||
// callableTask is internally used to wrap the callable and future together
|
||||
// So that the worker can send the response back through channel provided in Future object
|
||||
type callableTask struct {
|
||||
Task Callable
|
||||
Handle *Future
|
||||
}
|
||||
|
||||
// Get returns the response of the Callable task when done
|
||||
// Is is the blocking call it waits for the execution to complete
|
||||
func (f *Future) Get() interface{} {
|
||||
return <-f.response
|
||||
}
|
||||
|
||||
// IsDone returns true if the execution is already done
|
||||
func (f *Future) IsDone() bool {
|
||||
return f.done
|
||||
}
|
||||
|
||||
// Runnable is interface for the jobs that will be executed by the threadpool
|
||||
type Runnable interface {
|
||||
Run()
|
||||
}
|
||||
85
pkg/utils/threadpool/threadpool.go
Normal file
85
pkg/utils/threadpool/threadpool.go
Normal file
@@ -0,0 +1,85 @@
|
||||
package threadpool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrQueueFull = fmt.Errorf("queue is full, not able add the task")
|
||||
ErrNoWorkers = fmt.Errorf("worker pool is empty")
|
||||
)
|
||||
|
||||
type ThreadPool struct {
|
||||
workersTopLimit int
|
||||
workerPool chan chan interface{}
|
||||
closeHandle chan bool
|
||||
wgReceivers sync.WaitGroup
|
||||
}
|
||||
|
||||
func NewThreadPool(workersLimit int) *ThreadPool {
|
||||
threadPool := &ThreadPool{workersTopLimit: workersLimit}
|
||||
threadPool.workerPool = make(chan chan interface{}, workersLimit)
|
||||
threadPool.closeHandle = make(chan bool)
|
||||
threadPool.wgReceivers = sync.WaitGroup{}
|
||||
threadPool.wgReceivers.Add(workersLimit)
|
||||
threadPool.createPool()
|
||||
return threadPool
|
||||
|
||||
}
|
||||
func (t *ThreadPool) Close() {
|
||||
close(t.closeHandle) // Stops all the routines
|
||||
t.wgReceivers.Wait()
|
||||
close(t.workerPool) // Closes the Job threadpool
|
||||
}
|
||||
|
||||
func (t *ThreadPool) createPool() {
|
||||
for i := 0; i < t.workersTopLimit; i++ {
|
||||
worker := NewWorker(t.workerPool, t.closeHandle, &t.wgReceivers)
|
||||
worker.Start()
|
||||
}
|
||||
go t.dispatch()
|
||||
}
|
||||
|
||||
func (t *ThreadPool) submitTask(task interface{}) error {
|
||||
// Add the task to the job queue
|
||||
//Find a worker for the job
|
||||
if t.workerPool == nil || t.workersTopLimit == 0 {
|
||||
return ErrNoWorkers
|
||||
}
|
||||
jobChannel := <-t.workerPool
|
||||
//Submit job to the worker
|
||||
jobChannel <- task
|
||||
return nil
|
||||
}
|
||||
|
||||
// Execute submits the job to available worker
|
||||
func (t *ThreadPool) Execute(task Runnable) error {
|
||||
return t.submitTask(task)
|
||||
}
|
||||
|
||||
// ExecuteFuture will submit the task to the threadpool and return the response handle
|
||||
func (t *ThreadPool) ExecuteFuture(task Callable) (*Future, error) {
|
||||
// Create future and task
|
||||
if t.workerPool == nil || t.workersTopLimit == 0 {
|
||||
return nil, ErrNoWorkers
|
||||
}
|
||||
handle := &Future{response: make(chan interface{})}
|
||||
futureTask := callableTask{Task: task, Handle: handle}
|
||||
err := t.submitTask(futureTask)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return futureTask.Handle, nil
|
||||
}
|
||||
|
||||
// dispatch listens to the jobqueue and handles the jobs to the workers
|
||||
func (t *ThreadPool) dispatch() {
|
||||
for {
|
||||
select {
|
||||
case <-t.closeHandle:
|
||||
// Close thread threadpool
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
119
pkg/utils/threadpool/threadpool_test.go
Normal file
119
pkg/utils/threadpool/threadpool_test.go
Normal file
@@ -0,0 +1,119 @@
|
||||
package threadpool
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
const (
|
||||
NumberOfWorkers = 20
|
||||
QueueSize = int64(1000)
|
||||
)
|
||||
|
||||
var (
|
||||
threadpool *ThreadPool
|
||||
)
|
||||
|
||||
func TestNewThreadPool(t *testing.T) {
|
||||
threadpool = NewThreadPool(NumberOfWorkers)
|
||||
}
|
||||
|
||||
func TestThreadPool_Execute(t *testing.T) {
|
||||
threadpool = NewThreadPool(NumberOfWorkers)
|
||||
data := &TestData{Val: "pristine"}
|
||||
task := &TestTask{TestData: data}
|
||||
threadpool.Execute(task)
|
||||
|
||||
time.Sleep(2 * time.Second)
|
||||
fmt.Println("")
|
||||
|
||||
if data.Val != "changed" {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestThreadPool_ExecuteFuture(t *testing.T) {
|
||||
threadpool = NewThreadPool(NumberOfWorkers)
|
||||
|
||||
task := &TestTaskFuture{}
|
||||
handle, _ := threadpool.ExecuteFuture(task)
|
||||
response := handle.Get()
|
||||
if !handle.IsDone() {
|
||||
t.Fail()
|
||||
}
|
||||
fmt.Println("Thread done ", response)
|
||||
}
|
||||
|
||||
func TestThreadPool_Close(t *testing.T) {
|
||||
threadpool = NewThreadPool(NumberOfWorkers)
|
||||
|
||||
threadpool.Close()
|
||||
}
|
||||
|
||||
func TestQueueFullError(t *testing.T) {
|
||||
threadpool = NewThreadPool(30)
|
||||
before := time.Now()
|
||||
|
||||
data := &TestData{Val: "pristine"}
|
||||
task := &TestTask{TestData: data}
|
||||
for i := 0; i < 30; i++ {
|
||||
err := threadpool.Execute(task)
|
||||
if err != nil {
|
||||
t.Fail()
|
||||
}
|
||||
}
|
||||
threadpool.Close()
|
||||
after := time.Now()
|
||||
t.Logf("time start %d", after.Sub(before))
|
||||
t.Log("success")
|
||||
}
|
||||
|
||||
// func TestQueueFullError_Future(t *testing.T) {
|
||||
// threadpool = NewThreadPool(NumberOfWorkers)
|
||||
|
||||
// threadpool := NewThreadPool(1)
|
||||
|
||||
// task := &TestLongTaskFuture{}
|
||||
|
||||
// _, err := threadpool.ExecuteFuture(task)
|
||||
// if err != nil {
|
||||
// t.Fail()
|
||||
// }
|
||||
|
||||
// _, err = threadpool.ExecuteFuture(task)
|
||||
|
||||
// threadpool.Close()
|
||||
// }
|
||||
|
||||
type TestTask struct {
|
||||
TestData *TestData
|
||||
}
|
||||
|
||||
type TestData struct {
|
||||
Val string
|
||||
}
|
||||
|
||||
func (t *TestTask) Run() {
|
||||
time.Sleep(1 * time.Second)
|
||||
t.TestData.Val = "changed"
|
||||
}
|
||||
|
||||
type TestLongTask struct{}
|
||||
|
||||
func (t TestLongTask) Run() {
|
||||
time.Sleep(5 * time.Second)
|
||||
}
|
||||
|
||||
type TestTaskFuture struct{}
|
||||
|
||||
func (t *TestTaskFuture) Call() interface{} {
|
||||
return "Done"
|
||||
}
|
||||
|
||||
type TestLongTaskFuture struct{}
|
||||
|
||||
func (t *TestLongTaskFuture) Call() interface{} {
|
||||
time.Sleep(5 * time.Second)
|
||||
return "Done"
|
||||
}
|
||||
60
pkg/utils/threadpool/worker.go
Normal file
60
pkg/utils/threadpool/worker.go
Normal file
@@ -0,0 +1,60 @@
|
||||
package threadpool
|
||||
|
||||
import (
|
||||
"sync"
|
||||
)
|
||||
|
||||
// Worker type holds the job channel and passed worker threadpool
|
||||
type Worker struct {
|
||||
jobChannel chan interface{}
|
||||
workerPool chan chan interface{}
|
||||
closeHandle chan bool
|
||||
receiver *sync.WaitGroup
|
||||
}
|
||||
|
||||
// NewWorker creates the new worker
|
||||
func NewWorker(workerPool chan chan interface{}, closeHandle chan bool, waitGroup *sync.WaitGroup) *Worker {
|
||||
|
||||
return &Worker{workerPool: workerPool,
|
||||
jobChannel: make(chan interface{}),
|
||||
closeHandle: closeHandle,
|
||||
receiver: waitGroup,
|
||||
}
|
||||
}
|
||||
|
||||
// Start starts the worker by listening to the job channel
|
||||
func (w Worker) Start() {
|
||||
go func() {
|
||||
defer w.receiver.Done()
|
||||
for {
|
||||
|
||||
// Put the worker to the worker threadpool
|
||||
w.workerPool <- w.jobChannel
|
||||
|
||||
select {
|
||||
// Wait for the job
|
||||
case job := <-w.jobChannel:
|
||||
// Got the job
|
||||
w.executeJob(job)
|
||||
case <-w.closeHandle:
|
||||
// Exit the go routine when the closeHandle channel is closed
|
||||
return
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// executeJob executes the job based on the type
|
||||
func (w Worker) executeJob(job interface{}) {
|
||||
// Execute the job based on the task type
|
||||
switch task := job.(type) {
|
||||
case Runnable:
|
||||
task.Run()
|
||||
break
|
||||
case callableTask:
|
||||
response := task.Task.Call()
|
||||
task.Handle.done = true
|
||||
task.Handle.response <- response
|
||||
break
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user