Merge pull request #140 from dirtbags/unmutex

Remove mutex on sort operation
This commit is contained in:
int00h5525 2020-03-12 10:33:36 -05:00 committed by GitHub
commit 46c6fcd594
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 76 additions and 64 deletions

View File

@ -14,6 +14,24 @@ type Award struct {
Points int Points int
} }
type AwardList []*Award
// Implement sort.Interface on AwardList
func (awards AwardList) Len() int {
return len(awards)
}
func (awards AwardList) Less(i, j int) bool {
return awards[i].When.Before(awards[j].When)
}
func (awards AwardList) Swap(i, j int) {
tmp := awards[i]
awards[i] = awards[j]
awards[j] = tmp
}
func ParseAward(s string) (*Award, error) { func ParseAward(s string) (*Award, error) {
ret := Award{} ret := Award{}

View File

@ -2,6 +2,7 @@ package main
import ( import (
"testing" "testing"
"sort"
) )
func TestAward(t *testing.T) { func TestAward(t *testing.T) {
@ -32,3 +33,23 @@ func TestAward(t *testing.T) {
t.Error("Not throwing error on bad points") t.Error("Not throwing error on bad points")
} }
} }
func TestAwardList(t *testing.T) {
a, _ := ParseAward("1536958399 1a2b3c4d counting 1")
b, _ := ParseAward("1536958400 1a2b3c4d counting 1")
c, _ := ParseAward("1536958300 1a2b3c4d counting 1")
list := AwardList{a, b, c}
if sort.IsSorted(list) {
t.Error("Unsorted list thinks it's sorted")
}
sort.Stable(list)
if (list[0] != c) || (list[1] != a) || (list[2] != b) {
t.Error("Sorting didn't")
}
if ! sort.IsSorted(list) {
t.Error("Sorted list thinks it isn't")
}
}

View File

@ -37,7 +37,6 @@ type Instance struct {
nextAttempt map[string]time.Time nextAttempt map[string]time.Time
nextAttemptMutex *sync.RWMutex nextAttemptMutex *sync.RWMutex
mux *http.ServeMux mux *http.ServeMux
PointsMux *sync.RWMutex
} }
func (ctx *Instance) Initialize() error { func (ctx *Instance) Initialize() error {
@ -55,7 +54,6 @@ func (ctx *Instance) Initialize() error {
ctx.nextAttempt = map[string]time.Time{} ctx.nextAttempt = map[string]time.Time{}
ctx.nextAttemptMutex = new(sync.RWMutex) ctx.nextAttemptMutex = new(sync.RWMutex)
ctx.mux = http.NewServeMux() ctx.mux = http.NewServeMux()
ctx.PointsMux = new(sync.RWMutex)
ctx.BindHandlers() ctx.BindHandlers()
ctx.MaybeInitialize() ctx.MaybeInitialize()
@ -85,10 +83,7 @@ func (ctx *Instance) MaybeInitialize() {
// Remove any extant control and state files // Remove any extant control and state files
os.Remove(ctx.StatePath("until")) os.Remove(ctx.StatePath("until"))
os.Remove(ctx.StatePath("disabled")) os.Remove(ctx.StatePath("disabled"))
ctx.PointsMux.Lock()
os.Remove(ctx.StatePath("points.log")) os.Remove(ctx.StatePath("points.log"))
ctx.PointsMux.Unlock()
os.RemoveAll(ctx.StatePath("points.tmp")) os.RemoveAll(ctx.StatePath("points.tmp"))
os.RemoveAll(ctx.StatePath("points.new")) os.RemoveAll(ctx.StatePath("points.new"))
@ -158,18 +153,15 @@ func (ctx *Instance) TooFast(teamId string) bool {
return now.Before(next) return now.Before(next)
} }
func (ctx *Instance) PointsLog(teamId string) []*Award { func (ctx *Instance) PointsLog(teamId string) AwardList {
var ret []*Award awardlist := AwardList{}
fn := ctx.StatePath("points.log") fn := ctx.StatePath("points.log")
ctx.PointsMux.RLock()
defer ctx.PointsMux.RUnlock()
f, err := os.Open(fn) f, err := os.Open(fn)
if err != nil { if err != nil {
log.Printf("Unable to open %s: %s", fn, err) log.Printf("Unable to open %s: %s", fn, err)
return ret return awardlist
} }
defer f.Close() defer f.Close()
@ -184,10 +176,10 @@ func (ctx *Instance) PointsLog(teamId string) []*Award {
if len(teamId) > 0 && cur.TeamId != teamId { if len(teamId) > 0 && cur.TeamId != teamId {
continue continue
} }
ret = append(ret, cur) awardlist = append(awardlist, cur)
} }
return ret return awardlist
} }
// AwardPoints gives points to teamId in category. // AwardPoints gives points to teamId in category.

View File

@ -4,7 +4,6 @@ import (
"bufio" "bufio"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io"
"io/ioutil" "io/ioutil"
"log" "log"
"os" "os"
@ -205,20 +204,26 @@ func (ctx *Instance) readTeams() {
// collectPoints gathers up files in points.new/ and appends their contents to points.log, // collectPoints gathers up files in points.new/ and appends their contents to points.log,
// removing each points.new/ file as it goes. // removing each points.new/ file as it goes.
func (ctx *Instance) collectPoints() { func (ctx *Instance) collectPoints() {
ctx.PointsMux.Lock() points := ctx.PointsLog("")
defer ctx.PointsMux.Unlock()
logf, err := os.OpenFile(ctx.StatePath("points.log"), os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644) pointsFilename := ctx.StatePath("points.log")
pointsNewFilename := ctx.StatePath("points.log.new")
// Yo, this is delicate.
// If we have to return early, we must remove this file.
// If the file's written and we move it successfully,
// we need to remove all the little points files that built it.
newPoints, err := os.OpenFile(pointsNewFilename, os.O_CREATE|os.O_WRONLY|os.O_EXCL, 0644)
if err != nil { if err != nil {
log.Printf("Can't append to points log: %s", err) log.Printf("Can't append to points log: %s", err)
return return
} }
defer logf.Close()
files, err := ioutil.ReadDir(ctx.StatePath("points.new")) files, err := ioutil.ReadDir(ctx.StatePath("points.new"))
if err != nil { if err != nil {
log.Printf("Error reading packages: %s", err) log.Printf("Error reading packages: %s", err)
} }
removearino := make([]string, 0, len(files))
for _, f := range files { for _, f := range files {
filename := ctx.StatePath("points.new", f.Name()) filename := ctx.StatePath("points.new", f.Name())
s, err := ioutil.ReadFile(filename) s, err := ioutil.ReadFile(filename)
@ -233,7 +238,7 @@ func (ctx *Instance) collectPoints() {
} }
duplicate := false duplicate := false
for _, e := range ctx.PointsLog("") { for _, e := range points {
if award.Same(e) { if award.Same(e) {
duplicate = true duplicate = true
break break
@ -243,56 +248,33 @@ func (ctx *Instance) collectPoints() {
if duplicate { if duplicate {
log.Printf("Skipping duplicate points: %s", award.String()) log.Printf("Skipping duplicate points: %s", award.String())
} else { } else {
fmt.Fprintf(logf, "%s\n", award.String()) points = append(points, award)
}
removearino = append(removearino, filename)
} }
logf.Sync() sort.Stable(points)
for _, point := range points {
fmt.Fprintln(newPoints, point.String())
}
newPoints.Close()
if err := os.Rename(pointsNewFilename, pointsFilename); err != nil {
log.Printf("Unable to move %s to %s: %s", pointsFilename, pointsNewFilename, err)
if err := os.Remove(pointsNewFilename); err != nil {
log.Printf("Also couldn't remove %s: %s", pointsNewFilename, err)
}
return
}
for _, filename := range removearino {
if err := os.Remove(filename); err != nil { if err := os.Remove(filename); err != nil {
log.Printf("Unable to remove %s: %s", filename, err) log.Printf("Unable to remove %s: %s", filename, err)
} }
} }
} }
// Ensure that points.log is sorted chronologically
func (ctx *Instance) sortPoints() {
var points []*Award
ctx.PointsMux.Lock()
defer ctx.PointsMux.Unlock()
logf, err := os.OpenFile(ctx.StatePath("points.log"), os.O_CREATE|os.O_RDWR, 0644)
if err != nil {
log.Printf("Can't sort points.log: %s", err)
return
}
defer logf.Close()
scanner := bufio.NewScanner(logf)
for scanner.Scan() {
line := scanner.Text()
cur, err := ParseAward(line)
if err != nil {
log.Printf("Encountered malformed award line, not sorting %s: %s", line, err)
return
}
points = append(points, cur)
}
// Only sort and write to file if we need to
if ! sort.SliceIsSorted(points, func( i, j int) bool { return points[i].When.Before(points[j].When) }) {
sort.SliceStable(points, func(i, j int) bool { return points[i].When.Before(points[j].When) })
logf.Seek(0, io.SeekStart)
for i := range points {
point := points[i]
fmt.Fprintf(logf, "%s\n", point.String())
}
}
}
func (ctx *Instance) isEnabled() bool { func (ctx *Instance) isEnabled() bool {
// Skip if we've been disabled // Skip if we've been disabled
if _, err := os.Stat(ctx.StatePath("disabled")); err == nil { if _, err := os.Stat(ctx.StatePath("disabled")); err == nil {
@ -338,7 +320,6 @@ func (ctx *Instance) Maintenance(maintenanceInterval time.Duration) {
ctx.tidy() ctx.tidy()
ctx.readTeams() ctx.readTeams()
ctx.collectPoints() ctx.collectPoints()
ctx.sortPoints()
ctx.generatePuzzleList() ctx.generatePuzzleList()
ctx.generatePointsLog("") ctx.generatePointsLog("")
} }