120 lines
2.7 KiB
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
|
|
}
|