mirror of
https://github.com/acaloiaro/neoq
synced 2026-07-21 18:29:08 +00:00
Remove add multiple handlers per queue (#42)
This commit is contained in:
parent
d47845d573
commit
d8c637852c
4 changed files with 43 additions and 94 deletions
|
|
@ -31,9 +31,10 @@ type MemBackend struct {
|
||||||
types.Backend
|
types.Backend
|
||||||
config *config.Config
|
config *config.Config
|
||||||
logger logging.Logger
|
logger logging.Logger
|
||||||
handlers *sync.Map // map queue names [string] to queue handlers [*Handler]
|
handlers *sync.Map // map queue names [string] to queue handlers [Handler]
|
||||||
fingerprints *sync.Map // map fingerprints [string] to job [Job]
|
fingerprints *sync.Map // map fingerprints [string] to job [Job]
|
||||||
futureJobs *sync.Map // map jobIDs [int64] to job [Job]
|
futureJobs *sync.Map // map jobIDs [int64] to job [Job]
|
||||||
|
queues *sync.Map // map queue names [string] to queue handler channels [chan Job]
|
||||||
cron *cron.Cron
|
cron *cron.Cron
|
||||||
mu *sync.Mutex // mutext to protect mutating state on a pgWorker
|
mu *sync.Mutex // mutext to protect mutating state on a pgWorker
|
||||||
cancelFuncs []context.CancelFunc // A collection of cancel functions to be called upon Shutdown()
|
cancelFuncs []context.CancelFunc // A collection of cancel functions to be called upon Shutdown()
|
||||||
|
|
@ -46,6 +47,7 @@ func Backend(ctx context.Context, opts ...config.Option) (backend types.Backend,
|
||||||
config: config.New(),
|
config: config.New(),
|
||||||
cron: cron.New(),
|
cron: cron.New(),
|
||||||
mu: &sync.Mutex{},
|
mu: &sync.Mutex{},
|
||||||
|
queues: &sync.Map{},
|
||||||
handlers: &sync.Map{},
|
handlers: &sync.Map{},
|
||||||
futureJobs: &sync.Map{},
|
futureJobs: &sync.Map{},
|
||||||
fingerprints: &sync.Map{},
|
fingerprints: &sync.Map{},
|
||||||
|
|
@ -66,14 +68,15 @@ func Backend(ctx context.Context, opts ...config.Option) (backend types.Backend,
|
||||||
|
|
||||||
// Enqueue queues jobs to be executed asynchronously
|
// Enqueue queues jobs to be executed asynchronously
|
||||||
func (m *MemBackend) Enqueue(ctx context.Context, job *jobs.Job) (jobID int64, err error) {
|
func (m *MemBackend) Enqueue(ctx context.Context, job *jobs.Job) (jobID int64, err error) {
|
||||||
var ha any
|
var queueChan chan *jobs.Job
|
||||||
|
var qc any
|
||||||
var ok bool
|
var ok bool
|
||||||
|
|
||||||
if ha, ok = m.handlers.Load(job.Queue); !ok {
|
if qc, ok = m.queues.Load(job.Queue); !ok {
|
||||||
return jobs.UnqueuedJobID, fmt.Errorf("%w: %s", handler.ErrNoProcessorForQueue, job.Queue)
|
return jobs.UnqueuedJobID, fmt.Errorf("%w: %s", handler.ErrNoProcessorForQueue, job.Queue)
|
||||||
}
|
}
|
||||||
|
|
||||||
handlers := ha.([]*handler.Handler)
|
queueChan = qc.(chan *jobs.Job)
|
||||||
|
|
||||||
// Make sure RunAfter is set to a non-zero value if not provided by the caller
|
// Make sure RunAfter is set to a non-zero value if not provided by the caller
|
||||||
// if already set, schedule the future job
|
// if already set, schedule the future job
|
||||||
|
|
@ -107,9 +110,7 @@ func (m *MemBackend) Enqueue(ctx context.Context, job *jobs.Job) (jobID int64, e
|
||||||
jobID = m.jobCount
|
jobID = m.jobCount
|
||||||
|
|
||||||
if job.RunAfter.Equal(now) {
|
if job.RunAfter.Equal(now) {
|
||||||
for _, h := range handlers {
|
queueChan <- job
|
||||||
h.JobsChannel <- job
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
m.queueFutureJob(job)
|
m.queueFutureJob(job)
|
||||||
}
|
}
|
||||||
|
|
@ -119,22 +120,13 @@ func (m *MemBackend) Enqueue(ctx context.Context, job *jobs.Job) (jobID int64, e
|
||||||
|
|
||||||
// Start starts processing jobs with the specified queue and handler
|
// Start starts processing jobs with the specified queue and handler
|
||||||
func (m *MemBackend) Start(ctx context.Context, queue string, h handler.Handler) (err error) {
|
func (m *MemBackend) Start(ctx context.Context, queue string, h handler.Handler) (err error) {
|
||||||
var ha any
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
var queueCapacity = h.QueueCapacity
|
var queueCapacity = h.QueueCapacity
|
||||||
if queueCapacity == emptyCapacity {
|
if queueCapacity == emptyCapacity {
|
||||||
queueCapacity = defaultMemQueueCapacity
|
queueCapacity = defaultMemQueueCapacity
|
||||||
}
|
}
|
||||||
|
|
||||||
h.JobsChannel = make(chan *jobs.Job, queueCapacity)
|
m.handlers.Store(queue, h)
|
||||||
if ha, ok = m.handlers.Load(queue); ok {
|
m.queues.Store(queue, make(chan *jobs.Job, queueCapacity))
|
||||||
handlers := ha.([]*handler.Handler)
|
|
||||||
handlers = append(handlers, &h)
|
|
||||||
m.handlers.Store(queue, handlers)
|
|
||||||
} else {
|
|
||||||
m.handlers.Store(queue, []*handler.Handler{&h})
|
|
||||||
}
|
|
||||||
|
|
||||||
ctx, cancel := context.WithCancel(ctx)
|
ctx, cancel := context.WithCancel(ctx)
|
||||||
|
|
||||||
|
|
@ -142,7 +134,7 @@ func (m *MemBackend) Start(ctx context.Context, queue string, h handler.Handler)
|
||||||
m.cancelFuncs = append(m.cancelFuncs, cancel)
|
m.cancelFuncs = append(m.cancelFuncs, cancel)
|
||||||
m.mu.Unlock()
|
m.mu.Unlock()
|
||||||
|
|
||||||
err = m.start(ctx, queue, h)
|
err = m.start(ctx, queue)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -200,19 +192,35 @@ func (m *MemBackend) Shutdown(ctx context.Context) {
|
||||||
}
|
}
|
||||||
|
|
||||||
// start starts a processor that handles new incoming jobs and future jobs
|
// start starts a processor that handles new incoming jobs and future jobs
|
||||||
func (m *MemBackend) start(ctx context.Context, queue string, h handler.Handler) (err error) {
|
func (m *MemBackend) start(ctx context.Context, queue string) (err error) {
|
||||||
|
var queueChan chan *jobs.Job
|
||||||
|
var qc any
|
||||||
|
var ht any
|
||||||
|
var h handler.Handler
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if ht, ok = m.handlers.Load(queue); !ok {
|
||||||
|
return fmt.Errorf("%w: %s", handler.ErrNoHandlerForQueue, queue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if qc, ok = m.queues.Load(queue); !ok {
|
||||||
|
return fmt.Errorf("%w: %s", handler.ErrNoProcessorForQueue, queue)
|
||||||
|
}
|
||||||
|
|
||||||
go func() { m.scheduleFutureJobs(ctx, queue) }()
|
go func() { m.scheduleFutureJobs(ctx, queue) }()
|
||||||
|
|
||||||
|
h = ht.(handler.Handler)
|
||||||
|
queueChan = qc.(chan *jobs.Job)
|
||||||
|
|
||||||
for i := 0; i < h.Concurrency; i++ {
|
for i := 0; i < h.Concurrency; i++ {
|
||||||
go func() {
|
go func() {
|
||||||
var retries int
|
|
||||||
var err error
|
var err error
|
||||||
var job *jobs.Job
|
var job *jobs.Job
|
||||||
|
|
||||||
for {
|
for {
|
||||||
select {
|
select {
|
||||||
case job = <-h.JobsChannel:
|
case job = <-queueChan:
|
||||||
retries, err = m.handleJob(ctx, job, h)
|
err = m.handleJob(ctx, job, h)
|
||||||
case <-ctx.Done():
|
case <-ctx.Done():
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -223,10 +231,8 @@ func (m *MemBackend) start(ctx context.Context, queue string, h handler.Handler)
|
||||||
}
|
}
|
||||||
|
|
||||||
m.logger.Error("job failed", err, "job_id", job.ID)
|
m.logger.Error("job failed", err, "job_id", job.ID)
|
||||||
runAfter := internal.CalculateBackoff(retries)
|
runAfter := internal.CalculateBackoff(job.Retries)
|
||||||
job.RunAfter = runAfter
|
job.RunAfter = runAfter
|
||||||
job.Retries = retries
|
|
||||||
job.Error = null.StringFrom(err.Error())
|
|
||||||
m.queueFutureJob(job)
|
m.queueFutureJob(job)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -246,6 +252,7 @@ func (m *MemBackend) scheduleFutureJobs(ctx context.Context, queue string) {
|
||||||
// loop over list of future jobs, scheduling goroutines to wait for jobs that are due within the next 30 seconds
|
// loop over list of future jobs, scheduling goroutines to wait for jobs that are due within the next 30 seconds
|
||||||
m.futureJobs.Range(func(_, v any) bool {
|
m.futureJobs.Range(func(_, v any) bool {
|
||||||
job := v.(*jobs.Job)
|
job := v.(*jobs.Job)
|
||||||
|
var queueChan chan *jobs.Job
|
||||||
|
|
||||||
timeUntilRunAfter := time.Until(job.RunAfter)
|
timeUntilRunAfter := time.Until(job.RunAfter)
|
||||||
if timeUntilRunAfter <= m.config.FutureJobWindow {
|
if timeUntilRunAfter <= m.config.FutureJobWindow {
|
||||||
|
|
@ -253,11 +260,9 @@ func (m *MemBackend) scheduleFutureJobs(ctx context.Context, queue string) {
|
||||||
go func(j *jobs.Job) {
|
go func(j *jobs.Job) {
|
||||||
scheduleCh := time.After(timeUntilRunAfter)
|
scheduleCh := time.After(timeUntilRunAfter)
|
||||||
<-scheduleCh
|
<-scheduleCh
|
||||||
if ha, ok := m.handlers.Load(queue); ok {
|
if qc, ok := m.queues.Load(queue); ok {
|
||||||
handlers := ha.([]*handler.Handler)
|
queueChan = qc.(chan *jobs.Job)
|
||||||
for _, h := range handlers {
|
queueChan <- j
|
||||||
h.JobsChannel <- j
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
m.logger.Error(fmt.Sprintf("no queue processor for queue '%s'", queue), handler.ErrNoHandlerForQueue)
|
m.logger.Error(fmt.Sprintf("no queue processor for queue '%s'", queue), handler.ErrNoHandlerForQueue)
|
||||||
}
|
}
|
||||||
|
|
@ -275,16 +280,19 @@ func (m *MemBackend) scheduleFutureJobs(ctx context.Context, queue string) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (m *MemBackend) handleJob(ctx context.Context, job *jobs.Job, h handler.Handler) (retries int, err error) {
|
func (m *MemBackend) handleJob(ctx context.Context, job *jobs.Job, h handler.Handler) (err error) {
|
||||||
ctx = withJobContext(ctx, job)
|
ctx = withJobContext(ctx, job)
|
||||||
|
|
||||||
// check if the job is being retried and increment retry count accordingly
|
// check if the job is being retried and increment retry count accordingly
|
||||||
if job.Status != internal.JobStatusNew {
|
if job.Status != internal.JobStatusNew {
|
||||||
retries = job.Retries + 1
|
job.Retries++
|
||||||
}
|
}
|
||||||
|
|
||||||
// execute the queue handler of this job
|
// execute the queue handler of this job
|
||||||
err = handler.Exec(ctx, h)
|
err = handler.Exec(ctx, h)
|
||||||
|
if err != nil {
|
||||||
|
job.Error = null.StringFrom(err.Error())
|
||||||
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,8 @@ func TestingBackend(conf *config.Config,
|
||||||
config: conf,
|
config: conf,
|
||||||
cron: c,
|
cron: c,
|
||||||
mu: &sync.Mutex{},
|
mu: &sync.Mutex{},
|
||||||
handlers: &sync.Map{},
|
queues: queues,
|
||||||
|
handlers: h,
|
||||||
futureJobs: futureJobs,
|
futureJobs: futureJobs,
|
||||||
fingerprints: fingerprints,
|
fingerprints: fingerprints,
|
||||||
logger: logger,
|
logger: logger,
|
||||||
|
|
|
||||||
|
|
@ -230,62 +230,3 @@ func TestCron(t *testing.T) {
|
||||||
t.Error(err)
|
t.Error(err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestFutureMultipleHandlerOnSameQueue(t *testing.T) {
|
|
||||||
ctx := context.Background()
|
|
||||||
var done = make(chan bool, 1)
|
|
||||||
doneCnt := 0
|
|
||||||
nq, err := neoq.New(ctx, neoq.WithBackend(memory.Backend))
|
|
||||||
if err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
defer nq.Shutdown(ctx)
|
|
||||||
|
|
||||||
h1 := handler.New(func(ctx context.Context) (err error) {
|
|
||||||
done <- true
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
h2 := handler.New(func(ctx context.Context) (err error) {
|
|
||||||
done <- true
|
|
||||||
return
|
|
||||||
})
|
|
||||||
|
|
||||||
if err := nq.Start(ctx, queue, h1); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
if err := nq.Start(ctx, queue, h2); err != nil {
|
|
||||||
t.Fatal(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
jid, err := nq.Enqueue(ctx, &jobs.Job{
|
|
||||||
Queue: queue,
|
|
||||||
Payload: map[string]interface{}{
|
|
||||||
"message": "hello multie queue world",
|
|
||||||
},
|
|
||||||
})
|
|
||||||
if err != nil || jid == jobs.DuplicateJobID {
|
|
||||||
err = fmt.Errorf("job was not enqueued. either it was duplicate or this error caused it: %w", err)
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
|
|
||||||
timeout := time.After(2 * time.Second)
|
|
||||||
|
|
||||||
result_loop:
|
|
||||||
for {
|
|
||||||
select {
|
|
||||||
case <-timeout:
|
|
||||||
err = errors.New("timeout")
|
|
||||||
break result_loop
|
|
||||||
case <-done:
|
|
||||||
doneCnt++
|
|
||||||
}
|
|
||||||
if doneCnt == 2 {
|
|
||||||
break result_loop
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if err != nil {
|
|
||||||
t.Error(err)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
|
|
@ -30,7 +30,6 @@ type Handler struct {
|
||||||
Concurrency int
|
Concurrency int
|
||||||
Deadline time.Duration
|
Deadline time.Duration
|
||||||
QueueCapacity int64
|
QueueCapacity int64
|
||||||
JobsChannel chan *jobs.Job
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Option is function that sets optional configuration for Handlers
|
// Option is function that sets optional configuration for Handlers
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue