144 lines
4.3 KiB
Go
144 lines
4.3 KiB
Go
// Package mysql stores cron-service scheduler leases and run history.
|
|
package mysql
|
|
|
|
import (
|
|
"context"
|
|
"database/sql"
|
|
"strings"
|
|
"time"
|
|
|
|
_ "github.com/go-sql-driver/mysql"
|
|
"hyapp/pkg/xerr"
|
|
)
|
|
|
|
// Repository owns cron-service metadata only; business data stays in owner services.
|
|
type Repository struct {
|
|
db *sql.DB
|
|
}
|
|
|
|
// TaskRun is the durable audit row for one scheduled batch attempt.
|
|
type TaskRun struct {
|
|
RunID string
|
|
TaskName string
|
|
AppCode string
|
|
OwnerNodeID string
|
|
Status string
|
|
ClaimedCount int
|
|
ProcessedCount int
|
|
SuccessCount int
|
|
FailureCount int
|
|
StartedAtMs int64
|
|
FinishedAtMs int64
|
|
DurationMs int64
|
|
ErrorMessage string
|
|
}
|
|
|
|
// Open creates the cron metadata connection pool and verifies connectivity.
|
|
func Open(ctx context.Context, dsn string) (*Repository, error) {
|
|
if strings.TrimSpace(dsn) == "" {
|
|
return nil, xerr.New(xerr.InvalidArgument, "mysql_dsn is required")
|
|
}
|
|
db, err := sql.Open("mysql", dsn)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if err := db.PingContext(ctx); err != nil {
|
|
_ = db.Close()
|
|
return nil, err
|
|
}
|
|
return &Repository{db: db}, nil
|
|
}
|
|
|
|
// Close releases the cron metadata connection pool.
|
|
func (r *Repository) Close() error {
|
|
if r == nil || r.db == nil {
|
|
return nil
|
|
}
|
|
return r.db.Close()
|
|
}
|
|
|
|
// Ping verifies that cron metadata storage is currently reachable.
|
|
func (r *Repository) Ping(ctx context.Context) error {
|
|
if r == nil || r.db == nil {
|
|
return xerr.New(xerr.Unavailable, "cron repository is not configured")
|
|
}
|
|
return r.db.PingContext(ctx)
|
|
}
|
|
|
|
// TryAcquireTaskLease serializes schedule triggers per task and app_code.
|
|
func (r *Repository) TryAcquireTaskLease(ctx context.Context, taskName string, appCode string, ownerNodeID string, nowMs int64, leaseTTL time.Duration) (bool, error) {
|
|
if r == nil || r.db == nil {
|
|
return false, xerr.New(xerr.Unavailable, "cron repository is not configured")
|
|
}
|
|
if leaseTTL <= 0 {
|
|
leaseTTL = 30 * time.Second
|
|
}
|
|
|
|
leaseUntilMs := nowMs + leaseTTL.Milliseconds()
|
|
result, err := r.db.ExecContext(ctx, `
|
|
INSERT IGNORE INTO cron_task_leases (task_name, app_code, owner_node_id, lease_until_ms, updated_at_ms)
|
|
VALUES (?, ?, ?, ?, ?)
|
|
`, taskName, appCode, ownerNodeID, leaseUntilMs, nowMs)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
inserted, err := result.RowsAffected()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if inserted > 0 {
|
|
return true, nil
|
|
}
|
|
|
|
result, err = r.db.ExecContext(ctx, `
|
|
UPDATE cron_task_leases
|
|
SET owner_node_id = ?, lease_until_ms = ?, updated_at_ms = ?
|
|
WHERE task_name = ? AND app_code = ? AND (lease_until_ms <= ? OR owner_node_id = ?)
|
|
`, ownerNodeID, leaseUntilMs, nowMs, taskName, appCode, nowMs, ownerNodeID)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
updated, err := result.RowsAffected()
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
return updated > 0, nil
|
|
}
|
|
|
|
// StartTaskRun inserts a running audit row before calling the owner service.
|
|
func (r *Repository) StartTaskRun(ctx context.Context, run TaskRun) error {
|
|
if r == nil || r.db == nil {
|
|
return xerr.New(xerr.Unavailable, "cron repository is not configured")
|
|
}
|
|
_, err := r.db.ExecContext(ctx, `
|
|
INSERT INTO cron_task_runs (
|
|
run_id, task_name, app_code, owner_node_id, status,
|
|
claimed_count, processed_count, success_count, failure_count,
|
|
started_at_ms, finished_at_ms, duration_ms, error_message
|
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, NULL, 0, '')
|
|
`, run.RunID, run.TaskName, run.AppCode, run.OwnerNodeID, run.Status, run.ClaimedCount, run.ProcessedCount, run.SuccessCount, run.FailureCount, run.StartedAtMs)
|
|
return err
|
|
}
|
|
|
|
// FinishTaskRun records the final outcome for one scheduled batch attempt.
|
|
func (r *Repository) FinishTaskRun(ctx context.Context, run TaskRun) error {
|
|
if r == nil || r.db == nil {
|
|
return xerr.New(xerr.Unavailable, "cron repository is not configured")
|
|
}
|
|
_, err := r.db.ExecContext(ctx, `
|
|
UPDATE cron_task_runs
|
|
SET status = ?, claimed_count = ?, processed_count = ?, success_count = ?, failure_count = ?,
|
|
finished_at_ms = ?, duration_ms = ?, error_message = ?
|
|
WHERE run_id = ?
|
|
`, run.Status, run.ClaimedCount, run.ProcessedCount, run.SuccessCount, run.FailureCount, run.FinishedAtMs, run.DurationMs, truncateError(run.ErrorMessage), run.RunID)
|
|
return err
|
|
}
|
|
|
|
func truncateError(value string) string {
|
|
value = strings.TrimSpace(value)
|
|
if len(value) <= 512 {
|
|
return value
|
|
}
|
|
return value[:512]
|
|
}
|