Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix/query race condition #430

Merged
merged 7 commits into from
Feb 23, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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?")

Check warning on line 46 in backend/dbtools/cache_handler.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/cache_handler.go#L38-L46

Added lines #L38 - L46 were not covered by tests
}
return query.Status

Check warning on line 48 in backend/dbtools/cache_handler.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/cache_handler.go#L48

Added line #L48 was not covered by tests
}
// 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

Check warning on line 61 in backend/dbtools/cache_handler.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/cache_handler.go#L57-L61

Added lines #L57 - L61 were not covered by tests
}
}
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 @@

// 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
}

Check warning on line 26 in backend/dbtools/sortby.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/sortby.go#L18-L26

Added lines #L18 - L26 were not covered by tests
}
case Completed:

Check warning on line 28 in backend/dbtools/sortby.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/sortby.go#L28

Added line #L28 was not covered by tests
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")

Check warning on line 33 in backend/dbtools/sortby.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/sortby.go#L31-L33

Added lines #L31 - L33 were not covered by tests
}

var names []string
Expand Down Expand Up @@ -50,6 +62,27 @@
}

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
}

Check warning on line 77 in backend/dbtools/sortby.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/sortby.go#L65-L77

Added lines #L65 - L77 were not covered by tests
}
case Completed:
return
default:
// if you hit this, fuck you
panic("Invalid query state")

Check warning on line 83 in backend/dbtools/sortby.go

View check run for this annotation

Codecov / codecov/patch

backend/dbtools/sortby.go#L79-L83

Added lines #L79 - L83 were not covered by tests
}

var names []string
var liftPtr *structs.Entry
var liftPositions []int
Expand Down Expand Up @@ -84,13 +117,6 @@
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