simple queues working
This commit is contained in:
parent
c5de4d4bb6
commit
72f0e52a78
5
go.mod
Normal file
5
go.mod
Normal file
@ -0,0 +1,5 @@
|
||||
module github.com/rayaman/simply-jobber
|
||||
|
||||
go 1.23.0
|
||||
|
||||
require github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 // indirect
|
||||
2
go.sum
Normal file
2
go.sum
Normal file
@ -0,0 +1,2 @@
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3 h1:zN2lZNZRflqFyxVaTIU61KNKQ9C0055u9CAfpmqUvo4=
|
||||
github.com/golang-collections/collections v0.0.0-20130729185459-604e922904d3/go.mod h1:nPpo7qLxd6XL3hWJG/O60sR8ZKfMCiIoNap5GvD12KU=
|
||||
124
main.go
Normal file
124
main.go
Normal file
@ -0,0 +1,124 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"os"
|
||||
"time"
|
||||
|
||||
"github.com/rayaman/simply-jobber/pkg/api/queues"
|
||||
"github.com/rayaman/simply-jobber/pkg/models"
|
||||
)
|
||||
|
||||
type Data struct {
|
||||
Message string `json:"message"`
|
||||
}
|
||||
|
||||
type Data2 struct {
|
||||
Msg string `json:"message"`
|
||||
}
|
||||
|
||||
var (
|
||||
Job1 models.JobType = "Test 1"
|
||||
Job2 models.JobType = "Test 2"
|
||||
)
|
||||
|
||||
func main() {
|
||||
var queue models.Queue = queues.NewSimple(0)
|
||||
|
||||
myJob := models.Job{
|
||||
Type: Job1,
|
||||
Data: Data{
|
||||
Message: "Job message :D",
|
||||
},
|
||||
}
|
||||
|
||||
myJob2 := models.Job{
|
||||
Type: Job2,
|
||||
Data: Data2{
|
||||
Msg: "Job message :D",
|
||||
},
|
||||
}
|
||||
|
||||
err := queue.AddHandle(Job1, func(j models.Job) (models.JobResponse, error) {
|
||||
data, err := models.GetDataFromStruct(j, Data{})
|
||||
|
||||
if err != nil {
|
||||
return models.JobResponse{}, fmt.Errorf("cannot get data from job")
|
||||
}
|
||||
|
||||
fmt.Printf("Got data 1 %v\n", data.Message)
|
||||
time.Sleep(time.Second)
|
||||
|
||||
return models.JobResponse{}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
err = queue.AddHandle(Job2, func(j models.Job) (models.JobResponse, error) {
|
||||
data, err := models.GetDataFromStruct(j, Data2{})
|
||||
|
||||
if err != nil {
|
||||
return models.JobResponse{}, fmt.Errorf("cannot get data from job")
|
||||
}
|
||||
|
||||
fmt.Printf("Got data 2 %v\n", data.Msg)
|
||||
time.Sleep(time.Second * 2)
|
||||
|
||||
return models.JobResponse{}, nil
|
||||
})
|
||||
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
// queue.SetProcessor(func(j models.Job) (models.JobResponse, error) {
|
||||
// switch j.Type {
|
||||
// case "test":
|
||||
// data, err := models.GetDataFromStruct(j, Data{})
|
||||
// if err != nil {
|
||||
// return models.JobResponse{}, fmt.Errorf("cannot get data from job")
|
||||
// }
|
||||
// include := "N/A"
|
||||
// if data.Message == "Job message :D" {
|
||||
// include = "Special+"
|
||||
// }
|
||||
// return models.JobResponse{
|
||||
// Type: "test",
|
||||
// Data: Data{
|
||||
// Message: "Completed job :D " + include,
|
||||
// },
|
||||
// }, nil
|
||||
// default:
|
||||
// return models.JobResponse{}, fmt.Errorf("cannot process job")
|
||||
// }
|
||||
// })
|
||||
|
||||
// queue.OnJobResponse(func(jr models.JobResponse, i int) {
|
||||
// data, err := models.GetDataFromStruct(models.Job(jr), Data{})
|
||||
// if err != nil {
|
||||
// return
|
||||
// }
|
||||
// fmt.Printf("JobID: %v Message: %v\n", i, data.Message)
|
||||
// })
|
||||
go func() {
|
||||
time.Sleep(time.Second * 10)
|
||||
queue.Scale(1)
|
||||
time.Sleep(time.Second * 5)
|
||||
queue.Scale(5)
|
||||
time.Sleep(time.Second * 5)
|
||||
queue.Scale(0)
|
||||
time.Sleep(time.Second * 5)
|
||||
queue.Scale(5)
|
||||
time.Sleep(time.Second * 5)
|
||||
os.Exit(0)
|
||||
}()
|
||||
|
||||
for {
|
||||
time.Sleep(time.Second * 1)
|
||||
queue.Send(myJob)
|
||||
time.Sleep(time.Second * 1)
|
||||
queue.Send(myJob2)
|
||||
}
|
||||
}
|
||||
136
pkg/api/queues/simple.go
Normal file
136
pkg/api/queues/simple.go
Normal file
@ -0,0 +1,136 @@
|
||||
// Handles job queues
|
||||
package queues
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/golang-collections/collections/queue"
|
||||
"github.com/rayaman/simply-jobber/pkg/models"
|
||||
)
|
||||
|
||||
type Simple struct {
|
||||
queue *queue.Queue
|
||||
processJob models.ProcessFunc
|
||||
mutex sync.Mutex
|
||||
mutexq sync.Mutex
|
||||
jid int
|
||||
responses []models.ResponseFunc
|
||||
handles map[models.JobType]models.ProcessFunc
|
||||
workers uint
|
||||
context *context.Context
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
type pkg struct {
|
||||
models.Job
|
||||
jid int
|
||||
}
|
||||
|
||||
func NewSimple(workers uint) *Simple {
|
||||
sq := &Simple{workers: workers, handles: map[models.JobType]models.ProcessFunc{}, queue: queue.New(), mutex: sync.Mutex{}, mutexq: sync.Mutex{}, processJob: func(j models.Job) (models.JobResponse, error) { return models.JobResponse{}, nil }}
|
||||
defer func() {
|
||||
if r := recover(); r != nil {
|
||||
fmt.Println("Recovered!")
|
||||
}
|
||||
}()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sq.context = &ctx
|
||||
sq.cancel = cancel
|
||||
sq.genWorkers(workers)
|
||||
return sq
|
||||
}
|
||||
|
||||
func (sq *Simple) Len() int {
|
||||
sq.mutexq.Lock()
|
||||
defer sq.mutexq.Unlock()
|
||||
return sq.queue.Len()
|
||||
}
|
||||
|
||||
func (sq *Simple) genWorkers(workers uint) {
|
||||
for range workers {
|
||||
go func() {
|
||||
ctx := sq.context
|
||||
for {
|
||||
time.Sleep(time.Millisecond)
|
||||
select {
|
||||
case <-(*ctx).Done():
|
||||
return
|
||||
default:
|
||||
if p, ok := sq.dequeue(); ok {
|
||||
var err error
|
||||
var res models.JobResponse
|
||||
sq.mutex.Lock()
|
||||
if handle, ok := sq.handles[p.Type]; ok {
|
||||
sq.mutex.Unlock()
|
||||
res, err = handle(p.Job)
|
||||
} else {
|
||||
sq.mutex.Unlock()
|
||||
res, err = sq.processJob(p.Job)
|
||||
}
|
||||
if err == nil {
|
||||
for _, jr := range sq.responses {
|
||||
jr(res, p.jid)
|
||||
}
|
||||
} else {
|
||||
// Todo log
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
}
|
||||
|
||||
func (sq *Simple) Scale(workers uint) {
|
||||
sq.mutex.Lock()
|
||||
defer sq.mutex.Unlock()
|
||||
sq.cancel()
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
sq.context = &ctx
|
||||
sq.cancel = cancel
|
||||
sq.genWorkers(workers)
|
||||
}
|
||||
|
||||
func (sq *Simple) dequeue() (pkg, bool) {
|
||||
sq.mutexq.Lock()
|
||||
defer sq.mutexq.Unlock()
|
||||
if sq.queue.Len() > 0 {
|
||||
return sq.queue.Dequeue().(pkg), true
|
||||
} else {
|
||||
return pkg{}, false
|
||||
}
|
||||
}
|
||||
|
||||
func (sq *Simple) SetProcessor(fn models.ProcessFunc, args ...string) error {
|
||||
sq.mutex.Lock()
|
||||
defer sq.mutex.Unlock()
|
||||
sq.processJob = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sq *Simple) OnJobResponse(jr models.ResponseFunc) {
|
||||
sq.mutex.Lock()
|
||||
defer sq.mutex.Unlock()
|
||||
sq.responses = append(sq.responses, jr)
|
||||
}
|
||||
|
||||
func (sq *Simple) AddHandle(t models.JobType, fn models.ProcessFunc) error {
|
||||
sq.mutex.Lock()
|
||||
defer sq.mutex.Unlock()
|
||||
if _, ok := sq.handles[t]; ok {
|
||||
return fmt.Errorf("job type [%v] already exists", t)
|
||||
}
|
||||
sq.handles[t] = fn
|
||||
return nil
|
||||
}
|
||||
|
||||
func (sq *Simple) Send(j models.Job) (int, error) {
|
||||
sq.mutexq.Lock()
|
||||
defer sq.mutexq.Unlock()
|
||||
sq.queue.Enqueue(pkg{Job: j, jid: sq.jid})
|
||||
sq.jid++
|
||||
return sq.jid, nil
|
||||
}
|
||||
50
pkg/models/job.go
Normal file
50
pkg/models/job.go
Normal file
@ -0,0 +1,50 @@
|
||||
package models
|
||||
|
||||
import "encoding/json"
|
||||
|
||||
type JobName string
|
||||
type JobType string
|
||||
type JobResponse Job
|
||||
|
||||
type Job struct {
|
||||
// Type of job
|
||||
Type JobType `json:"type"`
|
||||
// The data you are packaging with the job
|
||||
Data any `json:"data"`
|
||||
}
|
||||
|
||||
// Takes in raw job data and a reference struct for it's data
|
||||
func GetJobFromData[T any](j []byte, data T) (*Job, T, error) {
|
||||
job := &Job{}
|
||||
err := json.Unmarshal(j, job)
|
||||
|
||||
if err != nil {
|
||||
return nil, data, err
|
||||
}
|
||||
|
||||
dat, err := json.Marshal(job.Data)
|
||||
|
||||
if err != nil {
|
||||
return nil, data, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(dat, &data)
|
||||
|
||||
job.Data = data
|
||||
|
||||
return job, data, err
|
||||
}
|
||||
|
||||
func GetDataFromStruct[T any](job Job, data T) (T, error) {
|
||||
dat, err := json.Marshal(job.Data)
|
||||
|
||||
if err != nil {
|
||||
return data, err
|
||||
}
|
||||
|
||||
err = json.Unmarshal(dat, &data)
|
||||
|
||||
job.Data = data
|
||||
|
||||
return data, err
|
||||
}
|
||||
18
pkg/models/queue.go
Normal file
18
pkg/models/queue.go
Normal file
@ -0,0 +1,18 @@
|
||||
package models
|
||||
|
||||
type Queue interface {
|
||||
Send(Job) (int, error)
|
||||
// Callback function that is triggered when a job has been processed. Can view the job response
|
||||
OnJobResponse(ResponseFunc)
|
||||
// Sets a function that will be triggered by all jobs not processed by a handle
|
||||
SetProcessor(ProcessFunc, ...string) error
|
||||
// Adds a handle for a certain job type. this func is only triggered by a piticular job type
|
||||
AddHandle(JobType, ProcessFunc) error
|
||||
// Allows you to scale the queue
|
||||
Scale(uint)
|
||||
// Number of queued jobs
|
||||
Len() int
|
||||
}
|
||||
|
||||
type ProcessFunc func(Job) (JobResponse, error)
|
||||
type ResponseFunc func(JobResponse, int)
|
||||
Loading…
x
Reference in New Issue
Block a user