spoc-bot-vr/queue.go

120 lines
2.7 KiB
Go

package main
import (
"context"
"fmt"
"time"
"github.com/google/uuid"
)
type Queue struct {
driver Driver
topic string
}
func NewNoopQueue() Queue {
return Queue{}
}
func NewQueue(ctx context.Context, topic string, driver Driver) (Queue, error) {
if _, err := driver.ExecContext(ctx, `
CREATE TABLE IF NOT EXISTS queue (
id INTEGER PRIMARY KEY,
topic TEXT NOT NULL,
updated INTEGER NOT NULL,
reservation TEXT,
payload TEXT
);
`); err != nil {
return Queue{}, fmt.Errorf("failed to create table: %w", err)
}
return Queue{topic: topic, driver: driver}, nil
}
func (q Queue) Enqueue(ctx context.Context, b []byte) error {
if q.driver.DB == nil {
return nil
}
_, err := q.driver.ExecContext(ctx, `
INSERT INTO queue (topic, updated, payload) VALUES (?, ?, ?)
`,
q.topic,
time.Now().Unix(),
b,
)
return err
}
func (q Queue) Syn(ctx context.Context) (string, []byte, error) {
if q.driver.DB == nil {
return "", nil, nil
}
for {
reservation, m, err := q.syn(ctx)
if reservation != nil || err != nil {
return string(reservation), m, err
}
select {
case <-ctx.Done():
return "", nil, ctx.Err()
case <-time.After(time.Millisecond * 500):
}
}
}
func (q Queue) syn(ctx context.Context) ([]byte, []byte, error) {
now := time.Now().Unix()
reservation := []byte(uuid.New().String())
var payload []byte
if result, err := q.driver.ExecContext(ctx, `
UPDATE queue
SET
updated = ?, reservation = ?
WHERE
id IN (
SELECT id
FROM queue
WHERE
topic == ?
AND (
reservation IS NULL
OR ? - updated > 60
)
LIMIT 1
)
`, now, reservation, q.topic, now); err != nil {
return nil, nil, fmt.Errorf("failed to assign reservation: %w", err)
} else if n, err := result.RowsAffected(); err != nil {
return nil, nil, fmt.Errorf("failed to assign reservation: no count: %w", err)
} else if n == 0 {
return nil, nil, nil
}
row := q.driver.QueryRowContext(ctx, `
SELECT payload
FROM queue
WHERE reservation==?
LIMIT 1
`, reservation)
if err := row.Err(); err != nil {
return nil, nil, fmt.Errorf("failed to query reservation: %w", err)
} else if err := row.Scan(&payload); err != nil {
return nil, nil, fmt.Errorf("failed to parse reservation: %w", err)
}
return reservation, payload, nil
}
func (q Queue) Ack(ctx context.Context, reservation string) error {
if q.driver.DB == nil {
return nil
}
_, err := q.driver.ExecContext(ctx, `
DELETE FROM queue
WHERE reservation==?
`, reservation)
return err
}