2021-05-24 09:12:46 +02:00
|
|
|
package main
|
|
|
|
|
|
|
|
import (
|
2022-02-23 10:23:26 +01:00
|
|
|
"context"
|
2021-05-24 09:12:46 +02:00
|
|
|
"database/sql"
|
|
|
|
"errors"
|
2022-02-23 10:23:26 +01:00
|
|
|
"log"
|
|
|
|
"sync"
|
2021-05-24 09:12:46 +02:00
|
|
|
"time"
|
|
|
|
|
|
|
|
"github.com/araddon/dateparse"
|
|
|
|
)
|
|
|
|
|
2021-07-13 17:23:10 +02:00
|
|
|
type queueItem struct {
|
2022-02-18 16:35:53 +01:00
|
|
|
schedule time.Time
|
2021-07-13 17:23:10 +02:00
|
|
|
name string
|
|
|
|
content []byte
|
2022-02-18 16:35:53 +01:00
|
|
|
id int
|
2021-07-13 17:23:10 +02:00
|
|
|
}
|
|
|
|
|
2022-03-31 14:55:36 +02:00
|
|
|
func (a *goBlog) enqueue(name string, content []byte, schedule time.Time) error {
|
2021-05-24 09:12:46 +02:00
|
|
|
if len(content) == 0 {
|
|
|
|
return errors.New("empty content")
|
|
|
|
}
|
2022-08-09 17:25:22 +02:00
|
|
|
_, err := a.db.Exec(
|
2021-11-28 09:02:09 +01:00
|
|
|
"insert into queue (name, content, schedule) values (@name, @content, @schedule)",
|
|
|
|
sql.Named("name", name),
|
|
|
|
sql.Named("content", content),
|
|
|
|
sql.Named("schedule", schedule.UTC().Format(time.RFC3339Nano)),
|
|
|
|
)
|
2022-03-31 14:55:36 +02:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
return nil
|
2021-05-24 09:12:46 +02:00
|
|
|
}
|
|
|
|
|
2022-03-31 14:55:36 +02:00
|
|
|
func (a *goBlog) reschedule(qi *queueItem, dur time.Duration) error {
|
2022-08-09 17:25:22 +02:00
|
|
|
_, err := a.db.Exec(
|
2021-11-28 09:02:09 +01:00
|
|
|
"update queue set schedule = @schedule, content = @content where id = @id",
|
|
|
|
sql.Named("schedule", qi.schedule.Add(dur).UTC().Format(time.RFC3339Nano)),
|
|
|
|
sql.Named("content", qi.content),
|
|
|
|
sql.Named("id", qi.id),
|
|
|
|
)
|
2021-05-24 09:12:46 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-31 14:55:36 +02:00
|
|
|
func (a *goBlog) dequeue(qi *queueItem) error {
|
2022-08-09 17:25:22 +02:00
|
|
|
_, err := a.db.Exec("delete from queue where id = @id", sql.Named("id", qi.id))
|
2021-05-24 09:12:46 +02:00
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
2022-03-31 14:55:36 +02:00
|
|
|
func (a *goBlog) peekQueue(ctx context.Context, name string) (*queueItem, error) {
|
2022-08-09 17:25:22 +02:00
|
|
|
row, err := a.db.QueryRowContext(
|
2022-02-23 10:23:26 +01:00
|
|
|
ctx,
|
2021-11-28 09:02:09 +01:00
|
|
|
"select id, name, content, schedule from queue where schedule <= @schedule and name = @name order by schedule asc limit 1",
|
|
|
|
sql.Named("name", name),
|
|
|
|
sql.Named("schedule", time.Now().UTC().Format(time.RFC3339Nano)),
|
|
|
|
)
|
2021-05-24 09:12:46 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
qi := &queueItem{}
|
|
|
|
var timeString string
|
|
|
|
if err = row.Scan(&qi.id, &qi.name, &qi.content, &timeString); err != nil {
|
2021-07-13 17:23:10 +02:00
|
|
|
if errors.Is(err, sql.ErrNoRows) {
|
2021-05-24 09:12:46 +02:00
|
|
|
return nil, nil
|
|
|
|
}
|
|
|
|
return nil, err
|
|
|
|
}
|
2021-07-13 17:23:10 +02:00
|
|
|
t, err := dateparse.ParseIn(timeString, time.UTC)
|
2021-05-24 09:12:46 +02:00
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
2022-02-18 16:35:53 +01:00
|
|
|
qi.schedule = t
|
2021-05-24 09:12:46 +02:00
|
|
|
return qi, nil
|
|
|
|
}
|
2022-02-23 10:23:26 +01:00
|
|
|
|
|
|
|
type queueProcessFunc func(qi *queueItem, dequeue func(), reschedule func(time.Duration))
|
|
|
|
|
|
|
|
func (a *goBlog) listenOnQueue(queueName string, wait time.Duration, process queueProcessFunc) {
|
2022-07-17 11:26:27 +02:00
|
|
|
if process == nil {
|
|
|
|
return
|
|
|
|
}
|
|
|
|
|
|
|
|
endQueue := false
|
|
|
|
queueContext, cancelQueueContext := context.WithCancel(context.Background())
|
|
|
|
var wg sync.WaitGroup
|
|
|
|
|
|
|
|
a.shutdown.Add(func() {
|
|
|
|
endQueue = true
|
|
|
|
cancelQueueContext()
|
|
|
|
wg.Wait()
|
|
|
|
})
|
|
|
|
|
|
|
|
wg.Add(1)
|
2022-02-23 10:23:26 +01:00
|
|
|
go func() {
|
2022-07-17 11:26:27 +02:00
|
|
|
queueLoop:
|
|
|
|
for {
|
|
|
|
if endQueue {
|
|
|
|
break queueLoop
|
|
|
|
}
|
|
|
|
qi, err := a.peekQueue(queueContext, queueName)
|
2022-02-23 10:23:26 +01:00
|
|
|
if err != nil {
|
2022-07-17 11:26:27 +02:00
|
|
|
log.Println("queue peek error:", err.Error())
|
|
|
|
continue queueLoop
|
2022-02-23 10:23:26 +01:00
|
|
|
}
|
|
|
|
if qi == nil {
|
|
|
|
// No item in the queue, wait a moment
|
|
|
|
select {
|
|
|
|
case <-time.After(wait):
|
2022-07-17 11:26:27 +02:00
|
|
|
continue queueLoop
|
|
|
|
case <-queueContext.Done():
|
|
|
|
break queueLoop
|
2022-02-23 10:23:26 +01:00
|
|
|
}
|
|
|
|
}
|
|
|
|
process(
|
|
|
|
qi,
|
|
|
|
func() {
|
2022-03-31 14:55:36 +02:00
|
|
|
if err := a.dequeue(qi); err != nil {
|
2022-02-23 10:23:26 +01:00
|
|
|
log.Println("queue dequeue error:", err.Error())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
func(dur time.Duration) {
|
2022-03-31 14:55:36 +02:00
|
|
|
if err := a.reschedule(qi, dur); err != nil {
|
2022-02-23 10:23:26 +01:00
|
|
|
log.Println("queue reschedule error:", err.Error())
|
|
|
|
}
|
|
|
|
},
|
|
|
|
)
|
|
|
|
}
|
2022-07-17 11:26:27 +02:00
|
|
|
log.Println("stopped queue:", queueName)
|
2022-02-23 10:23:26 +01:00
|
|
|
wg.Done()
|
|
|
|
}()
|
|
|
|
}
|