Release validation: tests green, USB pack, fleet UX and API hardening.
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Some checks failed
CI Docker Mining Proof / Linux agent hashrate proof (push) Has been cancelled
Fix macOS agent cross-compile (SilentAVExclusion) and Calibrate E2E nav selector; expand tests and docs; refresh portable usb binary and spread/wiki assets.
This commit is contained in:
@@ -2,6 +2,8 @@ package db
|
||||
|
||||
import (
|
||||
"database/sql"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"crypto-miner-server/internal/models"
|
||||
@@ -71,6 +73,42 @@ func (d *Database) LastFleetTaskRun(agentID, taskID string) (time.Time, bool) {
|
||||
return ts, true
|
||||
}
|
||||
|
||||
// BulkLastFleetTaskRuns returns a map keyed by "agentID:taskID" with the
|
||||
// last_run_at time for every matching row. Missing pairs were never run.
|
||||
// A single query replaces O(tasks × agents) individual lookups.
|
||||
func (d *Database) BulkLastFleetTaskRuns(agentIDs, taskIDs []string) (map[string]time.Time, error) {
|
||||
if len(agentIDs) == 0 || len(taskIDs) == 0 {
|
||||
return map[string]time.Time{}, nil
|
||||
}
|
||||
args := make([]interface{}, 0, len(agentIDs)+len(taskIDs))
|
||||
for _, id := range agentIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
for _, id := range taskIDs {
|
||||
args = append(args, id)
|
||||
}
|
||||
query := fmt.Sprintf(
|
||||
`SELECT agent_id, task_id, last_run_at FROM fleet_task_runs WHERE agent_id IN (%s) AND task_id IN (%s)`,
|
||||
strings.Repeat("?,", len(agentIDs)-1)+"?",
|
||||
strings.Repeat("?,", len(taskIDs)-1)+"?",
|
||||
)
|
||||
rows, err := d.Query(query, args...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
out := make(map[string]time.Time, len(agentIDs)*len(taskIDs))
|
||||
for rows.Next() {
|
||||
var agentID, taskID string
|
||||
var ts time.Time
|
||||
if err := rows.Scan(&agentID, &taskID, &ts); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
out[agentID+":"+taskID] = ts
|
||||
}
|
||||
return out, rows.Err()
|
||||
}
|
||||
|
||||
func scanFleetTaskRow(row *sql.Row) (*models.FleetTask, error) {
|
||||
t := &models.FleetTask{}
|
||||
var enabled int
|
||||
|
||||
@@ -28,6 +28,9 @@ func New(dataDir string) (*Database, error) {
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to open database: %w", err)
|
||||
}
|
||||
// SQLite only supports one concurrent writer; a single open connection
|
||||
// avoids WAL write-lock contention and SQLITE_BUSY under load.
|
||||
db.SetMaxOpenConns(1)
|
||||
|
||||
d := &Database{db}
|
||||
if err := d.migrate(); err != nil {
|
||||
@@ -309,6 +312,9 @@ func (d *Database) ListAgents() ([]*models.Agent, error) {
|
||||
}
|
||||
agents = append(agents, a)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return agents, nil
|
||||
}
|
||||
|
||||
@@ -347,6 +353,9 @@ func (d *Database) GetRecentShares(limit int) ([]*models.Share, error) {
|
||||
s.Accepted = accepted == 1
|
||||
shares = append(shares, s)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return shares, nil
|
||||
}
|
||||
|
||||
@@ -376,6 +385,9 @@ func (d *Database) GetHashrateHistory(agentID string, limit int) ([]*models.Hash
|
||||
}
|
||||
samples = append(samples, s)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return samples, nil
|
||||
}
|
||||
|
||||
@@ -464,26 +476,32 @@ func (d *Database) GetLatestBuildForPlatform(platform string) (*models.BuildReco
|
||||
// SetPinnedBuild unpins all builds then pins the one with the given id.
|
||||
// If id is empty, all builds are unpinned. Returns an error when id is
|
||||
// non-empty but no build row matches (avoids leaving all builds unpinned).
|
||||
// Both UPDATEs run in a single transaction so a crash mid-way cannot leave
|
||||
// the table in a half-pinned state.
|
||||
func (d *Database) SetPinnedBuild(id string) error {
|
||||
_, err := d.Exec(`UPDATE builds SET pinned = 0`)
|
||||
tx, err := d.Begin()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if id == "" {
|
||||
return nil
|
||||
}
|
||||
res, err := d.Exec(`UPDATE builds SET pinned = 1 WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
defer tx.Rollback() //nolint:errcheck
|
||||
|
||||
if _, err := tx.Exec(`UPDATE builds SET pinned = 0`); err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
if id != "" {
|
||||
res, err := tx.Exec(`UPDATE builds SET pinned = 1 WHERE id = ?`, id)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
n, err := res.RowsAffected()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if n == 0 {
|
||||
return fmt.Errorf("build not found: %s", id)
|
||||
}
|
||||
}
|
||||
if n == 0 {
|
||||
return fmt.Errorf("build not found: %s", id)
|
||||
}
|
||||
return nil
|
||||
return tx.Commit()
|
||||
}
|
||||
|
||||
func (d *Database) ListBuilds(limit int) ([]*models.BuildRecord, error) {
|
||||
@@ -501,6 +519,9 @@ func (d *Database) ListBuilds(limit int) ([]*models.BuildRecord, error) {
|
||||
}
|
||||
builds = append(builds, b)
|
||||
}
|
||||
if err := rows.Err(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return builds, nil
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user