2024-05-22 19:24:06 +00:00
|
|
|
package cron
|
2024-05-03 17:11:56 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"fmt"
|
2024-05-13 11:40:01 +00:00
|
|
|
"log/slog"
|
2024-05-03 17:11:56 +00:00
|
|
|
"math"
|
|
|
|
"time"
|
2024-05-08 14:35:04 +00:00
|
|
|
"xmr-remote-nodes/internal/database"
|
2024-05-03 17:11:56 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
type CronRepository interface {
|
2024-05-22 19:58:58 +00:00
|
|
|
RunCronProcess(chan struct{})
|
2024-05-18 13:13:00 +00:00
|
|
|
Crons() ([]Cron, error)
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
type CronRepo struct {
|
|
|
|
db *database.DB
|
|
|
|
}
|
|
|
|
|
2024-05-08 10:24:34 +00:00
|
|
|
type Cron struct {
|
2024-05-22 19:24:06 +00:00
|
|
|
ID int `json:"id" db:"id"`
|
2024-05-03 17:11:56 +00:00
|
|
|
Title string `json:"title" db:"title"`
|
|
|
|
Slug string `json:"slug" db:"slug"`
|
|
|
|
Description string `json:"description" db:"description"`
|
|
|
|
RunEvery int `json:"run_every" db:"run_every"`
|
|
|
|
LastRun int64 `json:"last_run" db:"last_run"`
|
|
|
|
NextRun int64 `json:"next_run" db:"next_run"`
|
|
|
|
RunTime float64 `json:"run_time" db:"run_time"`
|
|
|
|
CronState int `json:"cron_state" db:"cron_state"`
|
|
|
|
IsEnabled int `json:"is_enabled" db:"is_enabled"`
|
|
|
|
}
|
|
|
|
|
|
|
|
var rerunTimeout = 300
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func New() CronRepository {
|
|
|
|
return &CronRepo{db: database.GetDB()}
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-22 19:58:58 +00:00
|
|
|
func (r *CronRepo) RunCronProcess(c chan struct{}) {
|
2024-05-03 17:11:56 +00:00
|
|
|
for {
|
2024-05-22 19:58:58 +00:00
|
|
|
select {
|
|
|
|
case <-time.After(60 * time.Second):
|
|
|
|
slog.Info("[CRON] Running cron cycle...")
|
|
|
|
list, err := r.queueList()
|
|
|
|
if err != nil {
|
|
|
|
slog.Warn(fmt.Sprintf("[CRON] Error parsing queue list to struct: %s", err))
|
2024-05-03 17:11:56 +00:00
|
|
|
continue
|
|
|
|
}
|
2024-05-22 19:58:58 +00:00
|
|
|
for _, task := range list {
|
|
|
|
startTime := time.Now()
|
|
|
|
currentTs := startTime.Unix()
|
|
|
|
delayedTask := currentTs - task.NextRun
|
|
|
|
if task.CronState == 1 && delayedTask <= int64(rerunTimeout) {
|
|
|
|
slog.Debug(fmt.Sprintf("[CRON] Skipping task %s because it is already running", task.Slug))
|
|
|
|
continue
|
|
|
|
}
|
2024-05-03 17:11:56 +00:00
|
|
|
|
2024-05-22 19:58:58 +00:00
|
|
|
r.preRunTask(task.ID, currentTs)
|
|
|
|
r.execCron(task.Slug)
|
2024-05-03 17:11:56 +00:00
|
|
|
|
2024-05-22 19:58:58 +00:00
|
|
|
runTime := math.Ceil(time.Since(startTime).Seconds()*1000) / 1000
|
|
|
|
slog.Info(fmt.Sprintf("[CRON] Task %s done in %f seconds", task.Slug, runTime))
|
|
|
|
nextRun := currentTs + int64(task.RunEvery)
|
2024-05-03 17:11:56 +00:00
|
|
|
|
2024-05-22 19:58:58 +00:00
|
|
|
r.postRunTask(task.ID, nextRun, runTime)
|
|
|
|
}
|
|
|
|
slog.Info("[CRON] Cron cycle done!")
|
|
|
|
case <-c:
|
|
|
|
slog.Info("[CRON] Shutting down cron...")
|
|
|
|
return
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) Crons() ([]Cron, error) {
|
2024-05-18 13:13:00 +00:00
|
|
|
var tasks []Cron
|
2024-05-22 19:24:06 +00:00
|
|
|
err := r.db.Select(&tasks, `
|
2024-05-19 20:08:48 +00:00
|
|
|
SELECT
|
|
|
|
id,
|
|
|
|
title,
|
|
|
|
slug,
|
|
|
|
description,
|
|
|
|
run_every,
|
|
|
|
last_run,
|
|
|
|
next_run,
|
|
|
|
run_time,
|
|
|
|
cron_state,
|
|
|
|
is_enabled
|
|
|
|
FROM
|
|
|
|
tbl_cron`)
|
2024-05-18 13:13:00 +00:00
|
|
|
return tasks, err
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) queueList() ([]Cron, error) {
|
2024-05-08 10:24:34 +00:00
|
|
|
tasks := []Cron{}
|
2024-05-19 20:08:48 +00:00
|
|
|
query := `
|
|
|
|
SELECT
|
|
|
|
id,
|
|
|
|
run_every,
|
|
|
|
last_run,
|
|
|
|
slug,
|
|
|
|
next_run,
|
|
|
|
cron_state
|
|
|
|
FROM
|
|
|
|
tbl_cron
|
|
|
|
WHERE
|
|
|
|
is_enabled = ?
|
|
|
|
AND next_run <= ?`
|
2024-05-22 19:24:06 +00:00
|
|
|
err := r.db.Select(&tasks, query, 1, time.Now().Unix())
|
2024-05-03 17:11:56 +00:00
|
|
|
|
|
|
|
return tasks, err
|
|
|
|
}
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) preRunTask(id int, lastRunTs int64) {
|
2024-05-19 20:08:48 +00:00
|
|
|
query := `
|
|
|
|
UPDATE tbl_cron
|
|
|
|
SET
|
|
|
|
cron_state = ?,
|
|
|
|
last_run = ?
|
|
|
|
WHERE
|
|
|
|
id = ?`
|
2024-05-22 19:24:06 +00:00
|
|
|
row, err := r.db.Query(query, 1, lastRunTs, id)
|
2024-05-03 17:11:56 +00:00
|
|
|
if err != nil {
|
2024-05-13 11:40:01 +00:00
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to update pre cron state: %s", err))
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
defer row.Close()
|
|
|
|
}
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) postRunTask(id int, nextRun int64, runtime float64) {
|
2024-05-19 20:08:48 +00:00
|
|
|
query := `
|
|
|
|
UPDATE tbl_cron
|
|
|
|
SET
|
|
|
|
cron_state = ?,
|
|
|
|
next_run = ?,
|
|
|
|
run_time = ?
|
|
|
|
WHERE
|
|
|
|
id = ?`
|
2024-05-22 19:24:06 +00:00
|
|
|
row, err := r.db.Query(query, 0, nextRun, runtime, id)
|
2024-05-03 17:11:56 +00:00
|
|
|
if err != nil {
|
2024-05-13 11:40:01 +00:00
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to update post cron state: %s", err))
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
defer row.Close()
|
|
|
|
}
|
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) execCron(slug string) {
|
2024-05-03 17:11:56 +00:00
|
|
|
switch slug {
|
2024-05-06 11:40:09 +00:00
|
|
|
case "delete_old_probe_logs":
|
2024-05-13 11:40:01 +00:00
|
|
|
slog.Info(fmt.Sprintf("[CRON] Start running task: %s", slug))
|
2024-05-22 19:24:06 +00:00
|
|
|
r.deleteOldProbeLogs()
|
2024-05-31 09:28:21 +00:00
|
|
|
case "calculate_majority_fee":
|
|
|
|
slog.Info(fmt.Sprintf("[CRON] Start running task: %s", slug))
|
|
|
|
r.calculateMajorityFee()
|
2024-05-03 17:11:56 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-06 11:40:09 +00:00
|
|
|
|
2024-05-22 19:24:06 +00:00
|
|
|
func (r *CronRepo) deleteOldProbeLogs() {
|
2024-05-08 12:28:42 +00:00
|
|
|
// for now, we only delete stats older than 1 month +2 days
|
|
|
|
startTs := time.Now().AddDate(0, -1, -2).Unix()
|
2024-05-06 11:40:09 +00:00
|
|
|
query := `DELETE FROM tbl_probe_log WHERE date_checked < ?`
|
2024-05-22 19:24:06 +00:00
|
|
|
_, err := r.db.Exec(query, startTs)
|
2024-05-06 11:40:09 +00:00
|
|
|
if err != nil {
|
2024-05-13 11:40:01 +00:00
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to delete old probe logs: %s", err))
|
2024-05-06 11:40:09 +00:00
|
|
|
}
|
|
|
|
}
|
2024-05-31 09:28:21 +00:00
|
|
|
|
|
|
|
func (r *CronRepo) calculateMajorityFee() {
|
|
|
|
netTypes := [3]string{"mainnet", "stagenet", "testnet"}
|
|
|
|
for _, net := range netTypes {
|
|
|
|
row, err := r.db.Query(`
|
|
|
|
SELECT
|
|
|
|
COUNT(id) AS node_count,
|
|
|
|
nettype,
|
|
|
|
estimate_fee
|
|
|
|
FROM
|
|
|
|
tbl_node
|
|
|
|
WHERE
|
|
|
|
nettype = ?
|
|
|
|
GROUP BY
|
|
|
|
estimate_fee
|
|
|
|
ORDER BY
|
|
|
|
node_count DESC
|
|
|
|
LIMIT 1`, net)
|
|
|
|
if err != nil {
|
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to calculate majority fee: %s", err))
|
|
|
|
}
|
|
|
|
defer row.Close()
|
|
|
|
|
|
|
|
var (
|
|
|
|
nettype string
|
|
|
|
estimateFee int
|
|
|
|
nodeCount int
|
|
|
|
)
|
|
|
|
|
|
|
|
for row.Next() {
|
|
|
|
err = row.Scan(&nodeCount, &nettype, &estimateFee)
|
|
|
|
if err != nil {
|
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to calculate majority fee: %s", err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
|
|
|
|
query := `UPDATE tbl_fee SET estimate_fee = ?, node_count = ? WHERE nettype = ?`
|
|
|
|
_, err = r.db.Exec(query, estimateFee, nodeCount, nettype)
|
|
|
|
if err != nil {
|
|
|
|
slog.Error(fmt.Sprintf("[CRON] Failed to update majority fee: %s", err))
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|