Skip to content

Commit

Permalink
Use a less efficient but way less complicated mechanism to wait for u…
Browse files Browse the repository at this point in the history
…pdates

Might be worthwhile to read more about how to do it properly with wait conditions and channels
  • Loading branch information
J12934 committed Nov 24, 2024
1 parent 6304567 commit 8cb9cdb
Show file tree
Hide file tree
Showing 3 changed files with 49 additions and 46 deletions.
41 changes: 20 additions & 21 deletions balancer/pkg/scoring/scoring.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ type ScoringService struct {
currentScoresMutex *sync.Mutex

lastSeenUpdate time.Time
newUpdate sync.Cond

challengesMap map[string](bundle.JuiceShopChallenge)
}
Expand All @@ -59,7 +58,6 @@ func NewScoringServiceWithInitialScores(b *bundle.Bundle, initialScores map[stri
currentScoresMutex: &sync.Mutex{},

lastSeenUpdate: time.Now(),
newUpdate: *sync.NewCond(&sync.Mutex{}),

challengesMap: cachedChallengesMap,
}
Expand All @@ -80,23 +78,24 @@ func (s *ScoringService) WaitForUpdatesNewerThan(ctx context.Context, lastSeenUp
}

const maxWaitTime = 25 * time.Second
done := make(chan struct{})
go func() {
s.newUpdate.Wait()
close(done)
}()

select {
// check for update by subscribing to the newUpdate condition
case <-done:
// new update was received
return s.currentScoresSorted
case <-time.After(maxWaitTime):
// timeout was reached
return nil
case <-ctx.Done():
// request was aborted
return nil
timeout := time.NewTimer(maxWaitTime)
ticker := time.NewTicker(50 * time.Millisecond)
defer timeout.Stop()
defer ticker.Stop()

for {
select {
case <-ticker.C:
if s.lastSeenUpdate.After(lastSeenUpdate) {
return s.currentScoresSorted
}
case <-timeout.C:
// Timeout was reached
return nil
case <-ctx.Done():
// Context was canceled
return nil
}
}
}

Expand Down Expand Up @@ -134,15 +133,15 @@ func (s *ScoringService) StartingScoringWorker(ctx context.Context) {
s.currentScoresMutex.Lock()
s.currentScores[score.Name] = score
s.currentScoresSorted = sortTeamsByScoreAndCalculatePositions(s.currentScores)
s.newUpdate.Broadcast()
s.lastSeenUpdate = time.Now()
s.currentScoresMutex.Unlock()
case watch.Deleted:
deployment := event.Object.(*appsv1.Deployment)
team := deployment.Labels["team"]
s.currentScoresMutex.Lock()
delete(s.currentScores, team)
s.currentScoresSorted = sortTeamsByScoreAndCalculatePositions(s.currentScores)
s.newUpdate.Broadcast()
s.lastSeenUpdate = time.Now()
s.currentScoresMutex.Unlock()
default:
s.bundle.Log.Printf("Unknown event type: %v", event.Type)
Expand Down
5 changes: 5 additions & 0 deletions balancer/routes/score-board.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,25 @@ func handleScoreBoard(bundle *b.Bundle, scoringService *scoring.ScoringService)
func(responseWriter http.ResponseWriter, req *http.Request) {
var totalTeams []*scoring.TeamScore

bundle.Log.Printf("handling score board request")
if req.URL.Query().Get("wait-for-update-after") != "" {
lastSeenUpdate, err := time.Parse(time.RFC3339, req.URL.Query().Get("wait-for-update-after"))
if err != nil {
bundle.Log.Printf("Invalid time format")
http.Error(responseWriter, "Invalid time format", http.StatusBadRequest)
return
}
totalTeams = scoringService.WaitForUpdatesNewerThan(req.Context(), lastSeenUpdate)
if totalTeams == nil {
bundle.Log.Printf("Got nothing from waiting for updates")
responseWriter.WriteHeader(http.StatusNoContent)
responseWriter.Write([]byte{})
return
}
} else {
totalTeams = scoringService.GetTopScores()
}
bundle.Log.Printf("Got %d teams", len(totalTeams))

var topTeams []*scoring.TeamScore
// limit score-board to calculate score for the top 24 teams only
Expand Down
49 changes: 24 additions & 25 deletions balancer/ui/src/pages/ScoreOverview.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,16 @@ interface Team {
challenges: string[];
}

async function fetchTeams(): Promise<Team[]> {
const response = await fetch(
`/balancer/api/score-board/top?wait-for-update-after=${new Date().toISOString()}`
);
async function fetchTeams(lastSeen: Date | null): Promise<null | Team[]> {
const url = lastSeen
? `/balancer/api/score-board/top?wait-for-update-after=${lastSeen.toISOString()}`
: "/balancer/api/score-board/top";
const response = await fetch(url);

if (response.status === 204) {
return null;
}

const { teams } = await response.json();
return teams;
}
Expand All @@ -53,43 +59,36 @@ export function ScoreOverviewPage({
activeTeam: string | null;
}) {
const [teams, setTeams] = useState<Team[]>([]);
useEffect(() => {
fetchTeams().then(setTeams);

const timer = setInterval(() => {
fetchTeams().then(setTeams);
}, 5000);

return () => {
clearInterval(timer);
};
}, []);
const [lastUpdateStarted, setLastUpdateStarted] = useState(Date.now());

let timeout: number | null = null;
async function updateScoreData() {
const updateScoreData = async (lastSuccessfulUpdate: Date | null) => {
try {
setLastUpdateStarted(Date.now());
const status = await fetchTeams();
setTeams(status);
const lastUpdateStarted = new Date();
const status = await fetchTeams(lastSuccessfulUpdate);
if (status !== null) {
setTeams(status);
}

// the request is using a http long polling mechanism to get the updates as soon as possible
// in case the request returns immediatly we wait for at least 3 seconds to ensure we aren't spamming the server
const waitTime = Math.max(3000, 5000 - (Date.now() - lastUpdateStarted));
const waitTime = Math.max(
3000,
5000 - (Date.now() - lastUpdateStarted.getTime())
);
console.log(
"Waited for",
Date.now() - lastUpdateStarted,
Date.now() - lastUpdateStarted.getTime(),
"ms for status update"
);
console.log("Waiting for", waitTime, "ms until starting next request");
timeout = window.setTimeout(() => updateScoreData(), waitTime);
timeout = window.setTimeout(() => updateScoreData(new Date()), waitTime);
} catch (err) {
console.error("Failed to fetch current teams!", err);
}
}
};

useEffect(() => {
updateScoreData();
updateScoreData(null);
return () => {
if (timeout !== null) {
clearTimeout(timeout);
Expand Down

0 comments on commit 8cb9cdb

Please sign in to comment.