Skip to content

Commit

Permalink
Merge pull request #430 from euanwm/fix/query_race_condition
Browse files Browse the repository at this point in the history
fix/query race condition
  • Loading branch information
euanwm authored Feb 23, 2025
2 parents 32e3d5b + 66f6819 commit 9062ee8
Show file tree
Hide file tree
Showing 4 changed files with 87 additions and 110 deletions.
78 changes: 46 additions & 32 deletions backend/dbtools/cache_handler.go
Original file line number Diff line number Diff line change
@@ -1,54 +1,68 @@
package dbtools

import (
"backend/enum"
"backend/structs"
"sync"
)

type QueryState int

const (
None QueryState = iota
Working
Completed
)

type QueryCache struct {
Store []Query
HashStore sync.Map // [structs.LeaderboardPayload]Query
}

type Query struct {
Filter structs.LeaderboardPayload
DataPositions []int
Status QueryState
}

// AddQuery - Adds a query to the cache.
func (q *QueryCache) AddQuery(query structs.LeaderboardPayload, dataPositions []int) {
q.Store = append(q.Store, Query{Filter: query, DataPositions: dataPositions})
query.Start, query.Stop = 0, 0
q.HashStore.Store(query, Query{dataPositions, Completed})
}

// CheckQuery - Checks if the query has been run before, if so, return the data positions.
// A true value indicates that the query has been run before.
// A false value indicates that the query has not been run before.
// And a false value with a non-nil slice indicates that a similar query has been run before and the data positions are returned that should be used to filer from.
// Hoping that the last case makes things a bit faster to reduce having to run multiple startup caching queries.
func (q *QueryCache) CheckQuery(query structs.LeaderboardPayload) (bool, []int) {
for _, cacheQuery := range q.Store {
if cacheQuery.Filter == query {
return true, cacheQuery.DataPositions
}
// checks for exact match
if cacheQuery.Filter.SortBy == query.SortBy && cacheQuery.Filter.Federation == query.Federation && cacheQuery.Filter.WeightClass == query.WeightClass && cacheQuery.Filter.Year == query.Year {
return true, cacheQuery.DataPositions
func (q *QueryCache) InitQuery(query structs.LeaderboardPayload) {
q.HashStore.Store(query, Query{
DataPositions: nil,
Status: Working,
})
}

func (q *QueryCache) QueryStatus(query structs.LeaderboardPayload) QueryState {
query.Start, query.Stop = 0, 0
queryStuff, ok := q.HashStore.Load(query)
if !ok {
return None
} else {
query, ok := queryStuff.(Query)
if !ok {
panic("how the fuck did you fuck this up?")
}
return query.Status
}
// if we get here, we haven't found a match, so we'll do some partial matching
for _, cacheQuery := range q.Store {
// all years for the same total/sinclair, federation and weight class
if cacheQuery.Filter.SortBy == query.SortBy && cacheQuery.Filter.Federation == query.Federation && cacheQuery.Filter.WeightClass == query.WeightClass && cacheQuery.Filter.Year == enum.AllYearsStr {
return false, cacheQuery.DataPositions
}
// all years for the same total/sinclair, federation, and all gendered weight classes
if cacheQuery.Filter.SortBy == query.SortBy && cacheQuery.Filter.Federation == query.Federation && cacheQuery.Filter.Year == enum.AllYearsStr {
if query.WeightClass[0] == 'M' && cacheQuery.Filter.WeightClass == "MALL" {
return false, cacheQuery.DataPositions
}
if query.WeightClass[0] == 'F' && cacheQuery.Filter.WeightClass == "FALL" {
return false, cacheQuery.DataPositions
}
}

// CheckQuery - Checks if the query has been run before, if so, return the query state and data positions if they exist
func (q *QueryCache) CheckQuery(query structs.LeaderboardPayload) (state QueryState, positions []int) {
query.Start, query.Stop = 0, 0
loadedData, ok := q.HashStore.Load(query)
if ok {
storedQuery, ok := loadedData.(Query)
if ok && (storedQuery.Status == Completed) || (storedQuery.Status == Working) {
state = storedQuery.Status
positions = storedQuery.DataPositions
return
}
}
return false, nil

state = None
positions = []int{}
return state, positions
}
63 changes: 0 additions & 63 deletions backend/dbtools/cache_handler_test.go

This file was deleted.

54 changes: 40 additions & 14 deletions backend/dbtools/sortby.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,15 +10,27 @@ import (

// FilterLifts - Returns a slice of structs relating to the selected filter selection
func FilterLifts(bigData []structs.Entry, filterQuery structs.LeaderboardPayload, weightCat structs.WeightClass, cache *QueryCache) (filteredData structs.LeaderboardResponse) {
exists, positions := cache.CheckQuery(filterQuery)

if exists {
queryState, positions := cache.CheckQuery(filterQuery)

switch queryState {
case None:
cache.InitQuery(filterQuery)
case Working:
state := cache.QueryStatus(filterQuery)
for state == Working {
time.Sleep(100 * time.Millisecond)
state = cache.QueryStatus(filterQuery)
if state == Completed {
filteredData.Data, filteredData.Size = fetchLifts(&bigData, positions, &filterQuery)
return
}
}
case Completed:
filteredData.Data, filteredData.Size = fetchLifts(&bigData, positions, &filterQuery)
return
}

if !exists && positions != nil {
bigData = fetchLiftsAll(&bigData, positions)
default:
// if you hit this, fuck you
panic("Invalid query state")
}

var names []string
Expand Down Expand Up @@ -50,6 +62,27 @@ func FilterLifts(bigData []structs.Entry, filterQuery structs.LeaderboardPayload
}

func PreCacheFilter(bigData []structs.Entry, filterQuery structs.LeaderboardPayload, weightCat structs.WeightClass, cache *QueryCache) {
queryState, _ := cache.CheckQuery(filterQuery)

switch queryState {
case None:
cache.InitQuery(filterQuery)
case Working:
state := cache.QueryStatus(filterQuery)
for state == Working {
time.Sleep(100 * time.Millisecond)
state = cache.QueryStatus(filterQuery)
if state == Completed {
return
}
}
case Completed:
return
default:
// if you hit this, fuck you
panic("Invalid query state")
}

var names []string
var liftPtr *structs.Entry
var liftPositions []int
Expand Down Expand Up @@ -84,13 +117,6 @@ func fetchLifts(bigData *[]structs.Entry, pos []int, query *structs.LeaderboardP
return
}

func fetchLiftsAll(bigData *[]structs.Entry, pos []int) (lifts []structs.Entry) {
for _, p := range pos {
lifts = append(lifts, (*bigData)[p])
}
return
}

// SortSinclair Descending order by entry sinclair
func SortSinclair(sliceStructs []structs.Entry) {
sort.Slice(sliceStructs, func(i, j int) bool {
Expand Down
2 changes: 1 addition & 1 deletion backend/endpoints.go
Original file line number Diff line number Diff line change
Expand Up @@ -175,7 +175,7 @@ func LifterHistory(c *gin.Context) {
// @Success 200 {object} structs.LeaderboardResponse
// @Router /leaderboard [post]
func Leaderboard(c *gin.Context) {
sortby, exists := c.GetQuery("sortby")
sortby, exists := c.GetQuery("sortBy")
if !exists {
sortby = "total"
}
Expand Down

0 comments on commit 9062ee8

Please sign in to comment.