mgmt.go 2.53 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128
package server

import (
	"database/sql"
	"sync"
	"time"
)

var (
	// How often to run the user log compaction.
	compactionInterval = 600 * time.Second
	// How many log entries to keep when compacting.
	userKeepLogCount = 100

	// How often to run the log pruner.
	pruneInterval = 24 * time.Hour
	// Delete entries older than this many days.
	pruneCutoffDays = 365
)

// Call a function at semi-regular intervals.
type cronJob struct {
	f func()
	d time.Duration

	stopCh chan struct{}
	doneCh chan struct{}
}

func newCron(f func(), d time.Duration) *cronJob {
	j := &cronJob{
		f:      f,
		d:      d,
		stopCh: make(chan struct{}),
		doneCh: make(chan struct{}),
	}
	go j.run()
	return j
}

// Stop the job and wait for it to exit.
func (j *cronJob) Stop() {
	close(j.stopCh)
	<-j.doneCh
}

// Wait some time, or be interrupted by Stop().
func (j *cronJob) waitSomeTime() bool {
	t := time.NewTimer(j.d)
	defer t.Stop()
	select {
	case <-j.stopCh:
		return false
	case <-t.C:
		return true
	}
}

func (j *cronJob) run() {
	defer close(j.doneCh)
	for {
		if !j.waitSomeTime() {
			return
		}

		j.f()
	}
}

type usernameSet struct {
	mx        sync.Mutex
	usernames map[string]struct{}
}

func newUsernameSet() *usernameSet {
	return &usernameSet{
		usernames: make(map[string]struct{}),
	}
}

func (s *usernameSet) add(username string) {
	s.mx.Lock()
	s.usernames[username] = struct{}{}
	s.mx.Unlock()
}

func (s *usernameSet) foreach(f func(string)) {
	s.mx.Lock()
	for username := range s.usernames {
		delete(s.usernames, username)
		f(username)
	}
	s.mx.Unlock()
}

// Leisurely consolidate logs for users, but only when necessary, and
// proceeding at a slow pace. We wake up once per minute, and clean up
// the log history for users that have logged in since.
func compactLogs(db *sql.DB, stmts statementMap, username string, count int) error {
	tx, err := db.Begin()
	if err != nil {
		return err
	}
	if _, err := stmts.get(tx, "consolidate_userlog").Exec(username, count); err != nil {
		return err
	}
	return tx.Commit()
}

// Remove old entries (both logs and devices) from the database.
func pruneLogs(db *sql.DB, stmts statementMap) error {
	// Run each batch deletion in its own transaction, just to be nice.
	cutoff := time.Now().AddDate(0, 0, -pruneCutoffDays)
	for _, stmt := range []string{"prune_userlog", "prune_device_info"} {
		tx, err := db.Begin()
		if err != nil {
			return err
		}
		if _, err := stmts.get(tx, stmt).Exec(cutoff); err != nil {
			tx.Rollback() // nolint
			return err
		}
		if err := tx.Commit(); err != nil {
			return err
		}
	}
	return nil
}